diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py index bb03082be0..6edfc1264c 100644 --- a/django/core/management/__init__.py +++ b/django/core/management/__init__.py @@ -1,3 +1,4 @@ +import collections import os import sys from optparse import OptionParser, NO_DEFAULT @@ -5,6 +6,7 @@ import imp import warnings from django.core.management.base import BaseCommand, CommandError, handle_default_options +from django.core.management.color import color_style from django.utils.importlib import import_module # For backwards compatibility: get_version() used to be in this module. @@ -209,16 +211,32 @@ class ManagementUtility(object): self.argv = argv or sys.argv[:] self.prog_name = os.path.basename(self.argv[0]) - def main_help_text(self): + def main_help_text(self, commands_only=False): """ Returns the script's main help text, as a string. """ - usage = ['',"Type '%s help ' for help on a specific subcommand." % self.prog_name,''] - usage.append('Available subcommands:') - commands = get_commands().keys() - commands.sort() - for cmd in commands: - usage.append(' %s' % cmd) + if commands_only: + usage = sorted(get_commands().keys()) + else: + usage = [ + "", + "Type '%s help ' for help on a specific subcommand." % self.prog_name, + "", + "Available subcommands:", + ] + commands_dict = collections.defaultdict(lambda: []) + for name, app in get_commands().iteritems(): + if app == 'django.core': + app = 'django' + else: + app = app.rpartition('.')[-1] + commands_dict[app].append(name) + style = color_style() + for app in sorted(commands_dict.keys()): + usage.append("") + usage.append(style.NOTICE("[%s]" % app)) + for name in sorted(commands_dict[app]): + usage.append(" %s" % name) return '\n'.join(usage) def fetch_command(self, subcommand): @@ -340,12 +358,15 @@ class ManagementUtility(object): subcommand = 'help' # Display help if no arguments were given. if subcommand == 'help': - if len(args) > 2: - self.fetch_command(args[2]).print_help(self.prog_name, args[2]) - else: + if len(args) <= 2: parser.print_lax_help() sys.stdout.write(self.main_help_text() + '\n') - sys.exit(1) + elif args[2] == '--commands': + sys.stdout.write(self.main_help_text(commands_only=True) + '\n') + else: + self.fetch_command(args[2]).print_help(self.prog_name, args[2]) + elif subcommand == 'version': + sys.stdout.write(parser.get_version() + '\n') # Special-cases: We want 'django-admin.py --version' and # 'django-admin.py --help' to work, for backwards compatibility. elif self.argv[1:] == ['--version']: diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index aa514606e2..41f9bf649d 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -47,11 +47,16 @@ for the given command. Getting runtime help -------------------- -.. django-admin-option:: --help +.. django-admin:: help -Run ``django-admin.py help`` to display a list of all available commands. -Run ``django-admin.py help `` to display a description of the -given command and a list of its available options. +Run ``django-admin.py help`` to display usage information and a list of the +commands provided by each application. + +Run ``django-admin.py help --commands`` to display a list of all available +commands. + +Run ``django-admin.py help `` to display a description of the given +command and a list of its available options. App names --------- @@ -63,9 +68,9 @@ contains the string ``'mysite.blog'``, the app name is ``blog``. Determining the version ----------------------- -.. django-admin-option:: --version +.. django-admin:: version -Run ``django-admin.py --version`` to display the current Django version. +Run ``django-admin.py version`` to display the current Django version. Examples of output:: diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt index 464ba571a5..e004aefd60 100644 --- a/docs/releases/1.4.txt +++ b/docs/releases/1.4.txt @@ -989,6 +989,14 @@ after tests' execution, then you can restore the previous behavior by subclassing ``DjangoTestRunner`` and overriding its ``teardown_databases()`` method. +Output of :djadmin:`manage.py help ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:djadmin:`manage.py help ` now groups available commands by application. +If you depended on its output, for instance if you parsed it, you must update +your scripts. To obtain the list of all available management commands in a +script, you can use :djadmin:`manage.py help --commands ` instead. + Features deprecated in 1.4 ========================== diff --git a/tests/regressiontests/admin_scripts/tests.py b/tests/regressiontests/admin_scripts/tests.py index e981f54c23..dd7e4b3d15 100644 --- a/tests/regressiontests/admin_scripts/tests.py +++ b/tests/regressiontests/admin_scripts/tests.py @@ -173,6 +173,10 @@ class AdminScriptTestCase(unittest.TestCase): "Utility assertion: assert that the given message exists in the output" self.assertTrue(msg in stream, "'%s' does not match actual output text '%s'" % (msg, stream)) + def assertNotInOutput(self, stream, msg): + "Utility assertion: assert that the given message doesn't exist in the output" + self.assertFalse(msg in stream, "'%s' matches actual output text '%s'" % (msg, stream)) + ########################################################################## # DJANGO ADMIN TESTS # This first series of test classes checks the environment processing @@ -1173,25 +1177,47 @@ class CommandTypes(AdminScriptTestCase): self.remove_settings('settings.py') def test_version(self): - "--version is handled as a special case" - args = ['--version'] + "version is handled as a special case" + args = ['version'] out, err = self.run_manage(args) self.assertNoOutput(err) self.assertOutput(out, get_version()) - def test_help(self): - "--help is handled as a special case" - args = ['--help'] - out, err = self.run_manage(args) - self.assertOutput(out, "Usage: manage.py subcommand [options] [args]") - self.assertOutput(out, "Type 'manage.py help ' for help on a specific subcommand.") + def test_version_alternative(self): + "--version is equivalent to version" + args1, args2 = ['version'], ['--version'] + self.assertEqual(self.run_manage(args1), self.run_manage(args2)) - def test_short_help(self): - "-h is handled as a short form of --help" - args = ['-h'] + def test_help(self): + "help is handled as a special case" + args = ['help'] out, err = self.run_manage(args) self.assertOutput(out, "Usage: manage.py subcommand [options] [args]") self.assertOutput(out, "Type 'manage.py help ' for help on a specific subcommand.") + self.assertOutput(out, '[django]') + self.assertOutput(out, 'startapp') + self.assertOutput(out, 'startproject') + + def test_help_commands(self): + "help --commands shows the list of all available commands" + args = ['help', '--commands'] + out, err = self.run_manage(args) + self.assertNotInOutput(out, 'Usage:') + self.assertNotInOutput(out, 'Options:') + self.assertNotInOutput(out, '[django]') + self.assertOutput(out, 'startapp') + self.assertOutput(out, 'startproject') + self.assertNotInOutput(out, '\n\n') + + def test_help_alternative(self): + "--help is equivalent to help" + args1, args2 = ['help'], ['--help'] + self.assertEqual(self.run_manage(args1), self.run_manage(args2)) + + def test_help_short_altert(self): + "-h is handled as a short form of --help" + args1, args2 = ['--help'], ['-h'] + self.assertEqual(self.run_manage(args1), self.run_manage(args2)) def test_specific_help(self): "--help can be used on a specific command"