From 677ddcbb04b85541f7b39b95541f5b46817ea510 Mon Sep 17 00:00:00 2001 From: Joseph Kocherhans Date: Fri, 11 Sep 2009 23:15:59 +0000 Subject: [PATCH] Fixed #10752. Added more advanced bash completion. Thanks, Arthur Koziel. git-svn-id: http://code.djangoproject.com/svn/django/trunk@11526 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 1 + django/core/management/__init__.py | 77 +++++++++++++++++ extras/django_bash_completion | 134 +---------------------------- 3 files changed, 82 insertions(+), 130 deletions(-) diff --git a/AUTHORS b/AUTHORS index 58179e111a..907deb0df2 100644 --- a/AUTHORS +++ b/AUTHORS @@ -241,6 +241,7 @@ answer newbie questions, and generally made Django that much better: Igor Kolar Gasper Koren Martin Kosír + Arthur Koziel Meir Kriheli Bruce Kroeze krzysiek.pawlik@silvermedia.pl diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py index 026e426862..60dcf727e4 100644 --- a/django/core/management/__init__.py +++ b/django/core/management/__init__.py @@ -261,6 +261,82 @@ class ManagementUtility(object): sys.exit(1) return klass + def autocomplete(self): + """ + Output completion suggestions for BASH. + + The output of this function is passed to BASH's `COMREPLY` variable and + treated as completion suggestions. `COMREPLY` expects a space + separated string as the result. + + The `COMP_WORDS` and `COMP_CWORD` BASH environment variables are used + to get information about the cli input. Please refer to the BASH + man-page for more information about this variables. + + Subcommand options are saved as pairs. A pair consists of + the long option string (e.g. '--exclude') and a boolean + value indicating if the option requires arguments. When printing to + stdout, a equal sign is appended to options which require arguments. + + Note: If debugging this function, it is recommended to write the debug + output in a separate file. Otherwise the debug output will be treated + and formatted as potential completion suggestions. + """ + # Don't complete if user hasn't sourced bash_completion file. + if not os.environ.has_key('DJANGO_AUTO_COMPLETE'): + return + + cwords = os.environ['COMP_WORDS'].split()[1:] + cword = int(os.environ['COMP_CWORD']) + + try: + curr = cwords[cword-1] + except IndexError: + curr = '' + + subcommands = get_commands().keys() + ['help'] + options = [('--help', None)] + + # subcommand + if cword == 1: + print ' '.join(filter(lambda x: x.startswith(curr), subcommands)) + # subcommand options + # special case: the 'help' subcommand has no options + elif cwords[0] in subcommands and cwords[0] != 'help': + subcommand_cls = self.fetch_command(cwords[0]) + # special case: 'runfcgi' stores additional options as + # 'key=value' pairs + if cwords[0] == 'runfcgi': + from django.core.servers.fastcgi import FASTCGI_OPTIONS + options += [(k, 1) for k in FASTCGI_OPTIONS] + # special case: add the names of installed apps to options + elif cwords[0] in ('dumpdata', 'reset', 'sql', 'sqlall', + 'sqlclear', 'sqlcustom', 'sqlindexes', + 'sqlreset', 'sqlsequencereset', 'test'): + try: + from django.conf import settings + # Get the last part of the dotted path as the app name. + options += [(a.split('.')[-1], 0) for a in settings.INSTALLED_APPS] + except ImportError: + # Fail silently if DJANGO_SETTINGS_MODULE isn't set. The + # user will find out once they execute the command. + pass + options += [(s_opt.get_opt_string(), s_opt.nargs) for s_opt in + subcommand_cls.option_list] + # filter out previously specified options from available options + prev_opts = [x.split('=')[0] for x in cwords[1:cword-1]] + options = filter(lambda (x, v): x not in prev_opts, options) + + # filter options by current input + options = [(k, v) for k, v in options if k.startswith(curr)] + for option in options: + opt_label = option[0] + # append '=' to options which require args + if option[1]: + opt_label += '=' + print opt_label + sys.exit(1) + def execute(self): """ Given the command-line arguments, this figures out which subcommand is @@ -272,6 +348,7 @@ class ManagementUtility(object): parser = LaxOptionParser(usage="%prog subcommand [options] [args]", version=get_version(), option_list=BaseCommand.option_list) + self.autocomplete() try: options, args = parser.parse_args(self.argv) handle_default_options(options) diff --git a/extras/django_bash_completion b/extras/django_bash_completion index b805af9e6f..420553fcfa 100755 --- a/extras/django_bash_completion +++ b/extras/django_bash_completion @@ -31,136 +31,10 @@ # # To uninstall, just remove the line from your .bash_profile and .bashrc. -# Enable extended pattern matching operators. -shopt -s extglob - _django_completion() { - local cur prev opts actions action_shell_opts action_runfcgi_opts - COMPREPLY=() - cur="${COMP_WORDS[COMP_CWORD]}" - prev="${COMP_WORDS[COMP_CWORD-1]}" - - # Standalone options - opts="--help --settings --pythonpath --noinput --noreload --format --indent --verbosity --adminmedia --version --locale --domain" - # Actions - actions="createcachetable createsuperuser compilemessages \ - dbshell diffsettings dumpdata flush inspectdb loaddata \ - makemessages reset runfcgi runserver shell sql sqlall sqlclear \ - sqlcustom sqlflush sqlindexes sqlreset sqlsequencereset startapp \ - startproject syncdb test validate" - # Action's options - action_shell_opts="--plain" - action_runfcgi_opts="host port socket method maxspare minspare maxchildren daemonize pidfile workdir" - - if [[ # django-admin.py, django-admin, ./manage, manage.py - ( ${COMP_CWORD} -eq 1 && - ( ${COMP_WORDS[0]} == django-admin.py || - ${COMP_WORDS[0]} == django-admin || - ${COMP_WORDS[0]} == ./manage.py || - ${COMP_WORDS[0]} == manage.py ) ) - || - # python manage.py, /some/path/python manage.py (if manage.py exists) - ( ${COMP_CWORD} -eq 2 && - ( $( basename -- ${COMP_WORDS[0]} ) == python?([1-9]\.[0-9]) ) && - ( $( basename -- ${COMP_WORDS[1]} ) == manage.py) && - ( -r ${COMP_WORDS[1]} ) ) - || - ( ${COMP_CWORD} -eq 2 && - ( $( basename -- ${COMP_WORDS[0]} ) == python?([1-9]\.[0-9]) ) && - ( $( basename -- ${COMP_WORDS[1]} ) == django-admin.py) && - ( -r ${COMP_WORDS[1]} ) ) - || - ( ${COMP_CWORD} -eq 2 && - ( $( basename -- ${COMP_WORDS[0]} ) == python?([1-9]\.[0-9]) ) && - ( $( basename -- ${COMP_WORDS[1]} ) == django-admin) && - ( -r ${COMP_WORDS[1]} ) ) ]] ; then - - case ${cur} in - -*) - COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) - action=$COMPREPLY - return 0 - ;; - *) - COMPREPLY=( $(compgen -W "${actions}" -- ${cur}) ) - action=$COMPREPLY - return 0 - ;; - esac - else - case ${prev} in - dumpdata|reset| \ - sql|sqlall|sqlclear|sqlcustom|sqlindexes| \ - sqlreset|sqlsequencereset|test) - # App completion - settings="" - # If settings.py in the PWD, use that - if [ -e settings.py ] ; then - settings="$PWD/settings.py" - else - # Use the ENV variable if it is set - if [ $DJANGO_SETTINGS_MODULE ] ; then - settings=$DJANGO_SETTINGS_MODULE - fi - fi - # Couldn't find settings so return nothing - if [ -z $settings ] ; then - COMPREPLY=() - # Otherwise inspect settings.py file - else - apps=`sed -n "/INSTALLED_APPS = (/,/)/p" $settings | \ - grep -v "django.contrib" | - sed -n "s/^[ ]*'\(.*\.\)*\(.*\)'.*$/\2 /pg" | \ - tr -d "\n"` - COMPREPLY=( $(compgen -W "${apps}" -- ${cur}) ) - fi - return 0 - ;; - - createcachetable|cleanup|compilemessages|dbshell| \ - diffsettings|inspectdb|makemessages| \ - runserver|startapp|startproject|syncdb| \ - validate) - COMPREPLY=() - return 0 - ;; - shell) - COMPREPLY=( $(compgen -W "$action_shell_opts" -- ${cur}) ) - return 0 - ;; - runfcgi) - COMPREPLY=( $(compgen -W "$action_runfcgi_opts" -- ${cur}) ) - return 0 - ;; - host*|port*|socket*|method*|maxspare*|minspare*|maxchildren*|daemonize*|pidfile*|workdir*) - if [ "$action" == "runfcgi" ] ; then - COMPREPLY=( $(compgen -W "$action_runfcgi_opts" -- ${cur}) ) - return 0 - fi - return 0 - ;; - *) - #COMPREPLY=( $(compgen -W "auth core" -- ${cur}) ) - COMPREPLY=() - return 0 - ;; - esac - fi + COMPREPLY=( $( COMP_WORDS="${COMP_WORDS[*]}" \ + COMP_CWORD=$COMP_CWORD \ + DJANGO_AUTO_COMPLETE=1 $1 ) ) } - -complete -F _django_completion django-admin.py manage.py django-admin - -# Support for multiple interpreters. -unset pythons -if command -v whereis &>/dev/null; then - python_interpreters=$(whereis python | cut -d " " -f 2-) - for python in $python_interpreters; do - pythons="${pythons} $(basename -- $python)" - done - pythons=$(echo $pythons | tr " " "\n" | sort -u | tr "\n" " ") -else - pythons=python -fi - -complete -F _django_completion -o default $pythons +complete -F _django_completion -o default django-admin.py manage.py