diff --git a/azure-cli.pyproj b/azure-cli.pyproj
index 131d0a662ba..729ddbf4a9c 100644
--- a/azure-cli.pyproj
+++ b/azure-cli.pyproj
@@ -126,26 +126,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Code
@@ -161,9 +141,6 @@
Code
-
-
-
@@ -220,11 +197,6 @@
-
-
-
-
-
@@ -251,20 +223,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/azure/cli/_help.py b/src/azure/cli/_help.py
index b588174bea8..a59c964c6f5 100644
--- a/src/azure/cli/_help.py
+++ b/src/azure/cli/_help.py
@@ -75,10 +75,18 @@ def print_arguments(help_file):
required_tag = L(' [Required]')
max_name_length = max(len(p.name) + (len(required_tag) if p.required else 0)
for p in help_file.parameters)
- for p in sorted(help_file.parameters, key=lambda p: str(not p.required) + p.name):
+ last_group_name = None
+ for p in sorted(help_file.parameters,
+ key=lambda p: str(p.group_name or 'A')
+ + str(not p.required) + p.name):
indent = 1
required_text = required_tag if p.required else ''
p.short_summary = (p.short_summary if p.short_summary else '') + _get_choices_str(p)
+ if p.group_name != last_group_name:
+ if p.group_name:
+ print('')
+ print(p.group_name)
+ last_group_name = p.group_name
_print_indent('{0}{1}{2}{3}'.format(p.name,
_get_column_indent(p.name + required_text,
max_name_length),
@@ -92,7 +100,6 @@ def print_arguments(help_file):
_print_indent('{0}'.format(p.long_summary.rstrip()), indent)
if p.value_sources:
- _print_indent('')
_print_indent(L("Values from: {0}").format(', '.join(p.value_sources)), indent)
if p.long_summary or p.value_sources:
@@ -105,7 +112,7 @@ def _print_header(help_file):
_print_indent(L('Command') if help_file.type == 'command' else L('Group'), indent)
indent += 1
- _print_indent('{0}{1}'.format(help_file.command,
+ _print_indent('{0}{1}'.format('az ' + help_file.command,
': ' + help_file.short_summary
if help_file.short_summary
else ''),
@@ -138,6 +145,7 @@ def _get_choices_str(p):
def _print_examples(help_file):
indent = 0
+ print('')
_print_indent(L('Examples'), indent)
for e in help_file.examples:
@@ -218,7 +226,11 @@ def __init__(self, delimiters, parser):
action.help,
required=action.required,
choices=action.choices,
- default=action.default))
+ default=action.default,
+ group_name=action.container.description))
+
+ help_param = next(p for p in self.parameters if p.name == '--help -h')
+ help_param.group_name = 'Global Arguments'
def _load_from_data(self, data):
super(CommandHelpFile, self)._load_from_data(data)
@@ -243,7 +255,8 @@ def _load_from_data(self, data):
class HelpParameter(object): #pylint: disable=too-few-public-methods, too-many-instance-attributes
- def __init__(self, param_name, description, required, choices=None, default=None): #pylint: disable=too-many-arguments
+ def __init__(self, param_name, description, required, choices=None, #pylint: disable=too-many-arguments
+ default=None, group_name=None):
self.name = param_name
self.required = required
self.type = 'string'
@@ -252,6 +265,7 @@ def __init__(self, param_name, description, required, choices=None, default=None
self.value_sources = []
self.choices = choices
self.default = default
+ self.group_name = group_name
def update_from_data(self, data):
if self.name != data.get('name'):
diff --git a/src/azure/cli/application.py b/src/azure/cli/application.py
index edcecfc487f..06f7cc11474 100644
--- a/src/azure/cli/application.py
+++ b/src/azure/cli/application.py
@@ -50,7 +50,8 @@ def __init__(self, configuration):
azure.cli.extensions.register_extensions(self)
self.global_parser = AzCliCommandParser(prog='az', add_help=False)
- self.raise_event(self.GLOBAL_PARSER_CREATED, self.global_parser)
+ global_group = self.global_parser.add_argument_group('global', 'Global Arguments')
+ self.raise_event(self.GLOBAL_PARSER_CREATED, global_group)
self.parser = AzCliCommandParser(prog='az', parents=[self.global_parser])
self.raise_event(self.COMMAND_PARSER_CREATED, self.parser)
@@ -134,17 +135,20 @@ def _enable_autocomplete(parser):
argcomplete.autocomplete(parser)
@staticmethod
- def _register_builtin_arguments(parser):
- parser.add_argument('--subscription', dest='_subscription_id', help=argparse.SUPPRESS)
- parser.add_argument('--output', '-o', dest='_output_format',
- choices=['list', 'json', 'tsv'],
- default='list',
- help='Output format')
+ def _register_builtin_arguments(global_group):
+ global_group.add_argument('--subscription', dest='_subscription_id', help=argparse.SUPPRESS)
+ global_group.add_argument('--output', '-o', dest='_output_format',
+ choices=['list', 'json', 'tsv'],
+ default='list',
+ help='Output format')
# The arguments for verbosity don't get parsed by argparse but we add it here for help.
- parser.add_argument('--verbose', dest='_log_verbosity_verbose',
- help='Increase logging verbosity. Use --debug for full debug logs.')
- parser.add_argument('--debug', dest='_log_verbosity_debug',
- help='Increase logging verbosity to show all debug logs.')
+ global_group.add_argument('--verbose', dest='_log_verbosity_verbose',
+ help='Increase logging verbosity.'
+ ' Use --debug for full debug logs.',
+ action='store_true')
+ global_group.add_argument('--debug', dest='_log_verbosity_debug',
+ help='Increase logging verbosity to show all debug logs.',
+ action='store_true')
def _handle_builtin_arguments(self, args):
self.configuration.output_format = args._output_format #pylint: disable=protected-access
diff --git a/src/azure/cli/extensions/query.py b/src/azure/cli/extensions/query.py
index 46aa8302301..a54579d22db 100644
--- a/src/azure/cli/extensions/query.py
+++ b/src/azure/cli/extensions/query.py
@@ -1,9 +1,9 @@
import collections
-def _register_global_parameter(parser):
+def _register_global_parameter(global_group):
# Let the program know that we are adding a parameter --query
- parser.add_argument('--query', dest='_jmespath_query', metavar='JMESPATH',
- help='JMESPath query string. See http://jmespath.org/ for more information and examples.') # pylint: disable=line-too-long
+ global_group.add_argument('--query', dest='_jmespath_query', metavar='JMESPATH',
+ help='JMESPath query string. See http://jmespath.org/ for more information and examples.') # pylint: disable=line-too-long
def register(application):
def handle_query_parameter(args):
diff --git a/src/azure/cli/tests/test_help.py b/src/azure/cli/tests/test_help.py
index 55c9788d341..1cbbd37c58d 100644
--- a/src/azure/cli/tests/test_help.py
+++ b/src/azure/cli/tests/test_help.py
@@ -106,7 +106,7 @@ def test_handler(args):
with self.assertRaises(SystemExit):
app.execute('n1 -h'.split())
- self.assertEqual(True, io.getvalue().startswith('\nCommand\n n1\n long description'))
+ self.assertEqual(True, io.getvalue().startswith('\nCommand\n az n1\n long description'))
@redirect_io
def test_help_long_description_and_short_description(self):
@@ -131,7 +131,7 @@ def test_handler(args):
with self.assertRaises(SystemExit):
app.execute('n1 -h'.split())
- self.assertEqual(True, io.getvalue().startswith('\nCommand\n n1: short description\n long description'))
+ self.assertEqual(True, io.getvalue().startswith('\nCommand\n az n1: short description\n long description'))
@redirect_io
def test_help_docstring_description_overrides_short_description(self):
@@ -185,7 +185,7 @@ def test_handler(args):
with self.assertRaises(SystemExit):
app.execute('n1 -h'.split())
- self.assertEqual(True, io.getvalue().startswith('\nCommand\n n1\n line1\n line2'))
+ self.assertEqual(True, io.getvalue().startswith('\nCommand\n az n1\n line1\n line2'))
@redirect_io
@mock.patch('azure.cli.application.Application.register', return_value=None)
@@ -228,7 +228,7 @@ def test_handler(args):
app.execute('n1 -h'.split())
s = '''
Command
- n1
+ az n1
Arguments
--foobar2 -fb2 [Required]: one line partial sentence
@@ -236,10 +236,11 @@ def test_handler(args):
--foobar -fb : one line partial sentence
text, markdown, etc.
-
Values from: az vm list, default
--foobar3 -fb3 : the foobar3
+
+Global Arguments
--help -h : show this help message and exit
'''
self.assertEqual(s, io.getvalue())
@@ -291,7 +292,7 @@ def test_handler(args):
app.execute('n1 -h'.split())
s = '''
Command
- n1: this module does xyz one-line or so
+ az n1: this module does xyz one-line or so
this module.... kjsdflkj... klsfkj paragraph1
this module.... kjsdflkj... klsfkj paragraph2
@@ -301,10 +302,12 @@ def test_handler(args):
--foobar -fb : one line partial sentence
text, markdown, etc.
-
Values from: az vm list, default
+
+Global Arguments
--help -h : show this help message and exit
+
Examples
foo example
example details
@@ -377,41 +380,42 @@ def test_handler(args):
'.*Extra help param --foobar -fb.*',
lambda: app.execute('n1 -h'.split()))
-# Will uncomment when partial params don't bypass help (help behaviors implementation) task #115631559
-# @redirect_io
-# def test_help_with_param_specified(self):
-# app = Application(Configuration([]))
-# def test_handler(args):
-# pass
-
-# cmd_table = {
-# test_handler: {
-# 'name': 'n1',
-# 'arguments': [
-# {'name': '--arg -a', 'required': False},
-# {'name': '-b', 'required': False}
-# ]
-# }
-# }
-# config = Configuration([])
- #config.get_command_table = lambda: cmd_table
- #app = Application(config)
+ @redirect_io
+ @mock.patch('azure.cli.application.Application.register', return_value=None)
+ def test_help_with_param_specified(self, _):
+ app = Application(Configuration([]))
+ def test_handler(args):
+ pass
-# with self.assertRaises(SystemExit):
-# cmd_result = app.execute('n1 --arg -h'.split())
+ cmd_table = {
+ test_handler: {
+ 'name': 'n1',
+ 'arguments': [
+ {'name': '--arg -a', 'required': False},
+ {'name': '-b', 'required': False}
+ ]
+ }
+ }
+ config = Configuration([])
+ config.get_command_table = lambda: cmd_table
+ app = Application(config)
-# s = '''
-#Command
-# n1
+ with self.assertRaises(SystemExit):
+ cmd_result = app.execute('n1 --arg foo -h'.split())
-#Arguments
-# --arg -a
+ s = '''
+Command
+ az n1
-# -b
+Arguments
+ --arg -a
+ -b
-#'''
+Global Arguments
+ --help -h: show this help message and exit
+'''
-# self.assertEqual(s, io.getvalue())
+ self.assertEqual(s, io.getvalue())
@redirect_io
def test_help_group_children(self):
@@ -443,39 +447,42 @@ def test_handler2(args):
with self.assertRaises(SystemExit):
app.execute('group1 -h'.split())
- s = '\nGroup\n group1\n\nSub-Commands\n group2\n group3\n\n'
+ s = '\nGroup\n az group1\n\nSub-Commands\n group2\n group3\n\n'
self.assertEqual(s, io.getvalue())
- # Will uncomment when all errors are shown at once (help behaviors implementation) task #115631559
- #@redirect_io
- #def test_help_extra_missing_params(self):
- # app = Application(Configuration([]))
- # def test_handler(args):
- # pass
-
- # cmd_table = {
- # test_handler: {
- # 'name': 'n1',
- # 'arguments': [
- # {'name': '--foobar -fb', 'required': False},
- # {'name': '--foobar2 -fb2', 'required': True}
- # ]
- # }
- # }
- # config = Configuration([])
- #config.get_command_table = lambda: cmd_table
- #app = Application(config)
-
- # with self.assertRaises(SystemExit):
- # app.execute('n1 -fb a --foobar3 bad'.split())
-
- # with open(r'C:\temp\value.txt', 'w') as f:
- # f.write(io.getvalue())
-
- # self.assertTrue('required' in io.getvalue()
- # and '--foobar/-fb' not in io.getvalue()
- # and '--foobar2/-fb' in io.getvalue()
- # and 'unrecognized arguments: --foobar3' in io.getvalue())
+ @redirect_io
+ def test_help_extra_missing_params(self):
+ app = Application(Configuration([]))
+ def test_handler(args):
+ pass
+
+ cmd_table = {
+ test_handler: {
+ 'name': 'n1',
+ 'arguments': [
+ {'name': '--foobar -fb', 'required': False},
+ {'name': '--foobar2 -fb2', 'required': True}
+ ]
+ }
+ }
+ config = Configuration([])
+ config.get_command_table = lambda: cmd_table
+ app = Application(config)
+
+ # there is an argparse bug on <2.7.10 where SystemExit is not thrown on missing required param
+ if sys.version_info < (2, 7, 10):
+ app.execute('n1 -fb a --foobar value'.split())
+ app.execute('n1 -fb a --foobar2 value --foobar3 extra'.split())
+ else:
+ with self.assertRaises(SystemExit):
+ app.execute('n1 -fb a --foobar value'.split())
+ with self.assertRaises(SystemExit):
+ app.execute('n1 -fb a --foobar2 value --foobar3 extra'.split())
+
+ self.assertTrue('required' in io.getvalue()
+ and '--foobar/-fb' not in io.getvalue()
+ and '--foobar2/-fb2' in io.getvalue()
+ and 'unrecognized arguments: --foobar3 extra' in io.getvalue())
@redirect_io
def test_help_group_help(self):
@@ -520,19 +527,74 @@ def test_handler(args):
app.execute('test_group1 test_group2 --help'.split())
s = '''
Group
- test_group1 test_group2: this module does xyz one-line or so
+ az test_group1 test_group2: this module does xyz one-line or so
this module.... kjsdflkj... klsfkj paragraph1
this module.... kjsdflkj... klsfkj paragraph2
Sub-Commands
n1: this module does xyz one-line or so
+
Examples
foo example
example details
'''
self.assertEqual(s, io.getvalue())
+ @redirect_io
+ @mock.patch('azure.cli.application.Application.register', return_value=None)
+ @mock.patch('azure.cli.extensions.register_extensions', return_value=None)
+ def test_help_global_params(self, mock_register_extensions, _):
+ def register_globals(global_group):
+ global_group.add_argument('--query2', dest='_jmespath_query', metavar='JMESPATH',
+ help='JMESPath query string. See http://jmespath.org/ '
+ 'for more information and examples.')
+
+ mock_register_extensions.return_value = None
+ mock_register_extensions.side_effect = lambda app: \
+ app._event_handlers[app.GLOBAL_PARSER_CREATED].append(register_globals)
+
+ def test_handler(args):
+ pass
+
+ cmd_table = {
+ test_handler: {
+ 'name': 'n1',
+ 'help_file': '''
+ long-summary: |
+ line1
+ line2
+ ''',
+ 'arguments': [
+ {'name': '--arg -a', 'required': False},
+ {'name': '-b', 'required': False}
+ ]
+ }
+ }
+ config = Configuration([])
+ config.get_command_table = lambda: cmd_table
+ app = Application(config)
+
+ with self.assertRaises(SystemExit):
+ app.execute('n1 -h'.split())
+
+ s = """
+Command
+ az n1
+ line1
+ line2
+
+Arguments
+ --arg -a
+ -b
+
+Global Arguments
+ --help -h: show this help message and exit
+ --query2 : JMESPath query string. See http://jmespath.org/ for more information and examples.
+"""
+
+ self.assertEqual(s, io.getvalue())
+
if __name__ == '__main__':
unittest.main()