Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow user to specify an output format [114821361] #40

Merged
merged 1 commit into from
Mar 7, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 21 additions & 6 deletions src/azure/cli/_argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from ._locale import L, get_file as locale_get_file
from ._logging import logger
from ._output import OutputProducer

# Named arguments are prefixed with one of these strings
ARG_PREFIXES = sorted(('-', '--', '/'), key=len, reverse=True)
Expand Down Expand Up @@ -57,6 +58,10 @@ def _index(string, char, default=sys.maxsize):
except ValueError:
return default

class ArgumentParserResult(object): #pylint: disable=too-few-public-methods
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that we'll have to end up with a generic session configuration/state mechanism rather than returning an argument parser result. There are more "global" parameters (i.e. verbosity level, subscription id etc.) that will have to be extracted before we hit individual command instance execution.

We can merge this in in it's current state as getting the output format cleaned up is high prio, but we should add an item to the backlog to make it more generic...

def __init__(self, result, output_format=None):
self.result = result
self.output_format = output_format

class ArgumentParser(object):
def __init__(self, prog):
Expand Down Expand Up @@ -125,6 +130,7 @@ def add_command(self,
#pylint: disable=too-many-branches
#pylint: disable=too-many-statements
#pylint: disable=too-many-locals
#pylint: disable=too-many-return-statements
def execute(self,
args,
show_usage=False,
Expand Down Expand Up @@ -177,9 +183,9 @@ def not_global(a):
show_usage = True

if show_completions:
return self._display_completions(m, args, out)
return ArgumentParserResult(self._display_completions(m, args, out))
if show_usage:
return self._display_usage(nouns, m, out)
return ArgumentParserResult(self._display_usage(nouns, m, out))

parsed = Arguments()
others = Arguments()
Expand All @@ -199,7 +205,7 @@ def not_global(a):
if value is not None:
print(L("argument '{0}' does not take a value").format(key_n),
file=out)
return self._display_usage(nouns, m, out)
return ArgumentParserResult(self._display_usage(nouns, m, out))
parsed.add_from_dotted(target_value[0], True)
else:
# Arg with a value
Expand All @@ -217,15 +223,24 @@ def not_global(a):
parsed[a]
except KeyError:
print(L("Missing required argument {}".format(a)))
return self._display_usage(nouns, m, out)
return ArgumentParserResult(self._display_usage(nouns, m, out))

output_format = None
if others and 'output' in others:
if others['output'] in OutputProducer.format_dict:
output_format = others['output']
del others['output']
else:
print(L("Invalid output format '{}' specified".format(others['output'])))
return ArgumentParserResult(self._display_usage(nouns, m, out))

old_stdout = sys.stdout
try:
sys.stdout = out
return handler(parsed, others)
return ArgumentParserResult(handler(parsed, others), output_format)
except IncorrectUsageError as ex:
print(str(ex), file=out)
return self._display_usage(nouns, m, out)
return ArgumentParserResult(self._display_usage(nouns, m, out))
finally:
sys.stdout = old_stdout

Expand Down
10 changes: 10 additions & 0 deletions src/azure/cli/_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,23 @@ def format_text(obj):

class OutputProducer(object): #pylint: disable=too-few-public-methods

format_dict = {
'json': format_json,
'table': format_table,
'text': format_text
}

def __init__(self, formatter=format_json, file=sys.stdout): #pylint: disable=redefined-builtin
self.formatter = formatter
self.file = file

def out(self, obj):
print(self.formatter(obj), file=self.file)

@staticmethod
def get_formatter(format_type):
return OutputProducer.format_dict.get(format_type, format_json)

class TableOutput(object):
def __init__(self):
self._rows = [{}]
Expand Down
7 changes: 4 additions & 3 deletions src/azure/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,12 @@ def main(args):
commands.add_to_parser(parser)

try:
result = parser.execute(args)
cmd_res = parser.execute(args)
# Commands can return a dictionary/list of results
# If they do, we print the results.
if result:
OutputProducer().out(result)
if cmd_res.result:
formatter = OutputProducer.get_formatter(cmd_res.output_format)
OutputProducer(formatter=formatter).out(cmd_res.result)
except RuntimeError as ex:
logger.error(ex.args[0])
return ex.args[1] if len(ex.args) >= 2 else -1
Expand Down
58 changes: 46 additions & 12 deletions src/azure/cli/tests/test_argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,31 +46,38 @@ def test_args(self):
p = ArgumentParser('test')
p.add_command(lambda a, b: (a, b), 'n1', args=[('--arg -a', '', False), ('-b <v>', '', False)])

res, other = p.execute('n1 -a x'.split())
cmd_res = p.execute('n1 -a x'.split())
res, other = cmd_res.result
self.assertTrue(res.arg)
self.assertSequenceEqual(res.positional, ['x'])

# Should recognize args with alternate prefix
res, other = p.execute('n1 /a'.split())
cmd_res = p.execute('n1 /a'.split())
res, other = cmd_res.result
self.assertTrue(res.arg)
res, other = p.execute('n1 /arg'.split())
cmd_res = p.execute('n1 /arg'.split())
res, other = cmd_res.result
self.assertTrue(res.arg)

# Should not recognize "------a"
res, other = p.execute('n1 ------a'.split())
cmd_res = p.execute('n1 ------a'.split())
res, other = cmd_res.result
self.assertNotIn('arg', res)
# First two '--' match, so '----a' is added to dict
self.assertIn('----a', other)

res = p.execute('n1 -a:x'.split())
cmd_res = p.execute('n1 -a:x'.split())
res = cmd_res.result
self.assertIsNone(res)

res, other = p.execute('n1 -b -a x'.split())
cmd_res = p.execute('n1 -b -a x'.split())
res, other = cmd_res.result
self.assertEqual(res.b, '-a')
self.assertSequenceEqual(res.positional, ['x'])
self.assertRaises(IncorrectUsageError, lambda: res.arg)

res, other = p.execute('n1 -b:-a x'.split())
cmd_res = p.execute('n1 -b:-a x'.split())
res, other = cmd_res.result
self.assertEqual(res.b, '-a')
self.assertSequenceEqual(res.positional, ['x'])
self.assertRaises(IncorrectUsageError, lambda: res.arg)
Expand All @@ -79,15 +86,18 @@ def test_unexpected_args(self):
p = ArgumentParser('test')
p.add_command(lambda a, b: (a, b), 'n1', args=[('-a', '', False)])

res, other = p.execute('n1 -b=2'.split())
cmd_res = p.execute('n1 -b=2'.split())
res, other = cmd_res.result
self.assertFalse(res)
self.assertEqual('2', other.b)

res, other = p.execute('n1 -b.c.d=2'.split())
cmd_res = p.execute('n1 -b.c.d=2'.split())
res, other = cmd_res.result
self.assertFalse(res)
self.assertEqual('2', other.b.c.d)

res, other = p.execute('n1 -b.c.d 2 -b.c.e:3'.split())
cmd_res = p.execute('n1 -b.c.d 2 -b.c.e:3'.split())
res, other = cmd_res.result
self.assertFalse(res)
self.assertEqual('2', other.b.c.d)
self.assertEqual('3', other.b.c.e)
Expand All @@ -96,11 +106,12 @@ def test_required_args(self):
p = ArgumentParser('test')
p.add_command(lambda a, b: (a, b), 'n1', args=[('--arg -a', '', True), ('-b <v>', '', False)])

res, other = p.execute('n1 -a x'.split())
cmd_res = p.execute('n1 -a x'.split())
res, other = cmd_res.result
self.assertTrue(res.arg)
self.assertSequenceEqual(res.positional, ['x'])

self.assertIsNone(p.execute('n1 -b x'.split()))
self.assertIsNone(p.execute('n1 -b x'.split()).result)

def test_args_completion(self):
p = ArgumentParser('test')
Expand Down Expand Up @@ -129,6 +140,29 @@ def test_args_completion(self):
candidates = util.normalize_newlines(io.getvalue())
self.assertEqual(candidates, 'n1\n')

def test_specify_output_format(self):
p = ArgumentParser('test')
p.add_command(lambda a, b: (a, b), 'n1', args=[('--arg -a', '', True), ('-b <v>', '', False)])

cmd_res = p.execute('n1 -a x'.split())
self.assertEqual(cmd_res.output_format, None)

cmd_res = p.execute('n1 -a x --output json'.split())
self.assertEqual(cmd_res.output_format, 'json')

cmd_res = p.execute('n1 -a x --output table'.split())
self.assertEqual(cmd_res.output_format, 'table')

cmd_res = p.execute('n1 -a x --output text'.split())
self.assertEqual(cmd_res.output_format, 'text')

# Invalid format
cmd_res = p.execute('n1 -a x --output unknown'.split())
self.assertEqual(cmd_res.output_format, None)

# Invalid format
cmd_res = p.execute('n1 -a x --output'.split())
self.assertEqual(cmd_res.output_format, None)

if __name__ == '__main__':
unittest.main()
4 changes: 2 additions & 2 deletions src/azure/cli/tests/test_autocommand.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ def testfunc(args, _):
p = ArgumentParser('automcommandtest')
add_to_parser(p, 'test')

result = p.execute(command_name.split(' ') + '--tre wombat'.split(' '))
self.assertEqual(result, func)
cmd_res = p.execute(command_name.split(' ') + '--tre wombat'.split(' '))
self.assertEqual(cmd_res.result, func)

if __name__ == '__main__':
unittest.main()