Skip to content
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
115 changes: 115 additions & 0 deletions src/azure/cli/_output.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
from __future__ import print_function, unicode_literals

import sys
import json

try:
# Python 3
from io import StringIO
except ImportError:
# Python 2
from StringIO import StringIO

class OutputFormatException(Exception):
pass

def format_json(obj):
input_dict = obj.__dict__ if hasattr(obj, '__dict__') else obj
return json.dumps(input_dict, indent=2, sort_keys=True, separators=(',', ': '))

def format_table(obj):
obj_list = obj if isinstance(obj, list) else [obj]
to = TableOutput()
try:
for item in obj_list:
for item_key in sorted(item):
to.cell(item_key, item[item_key])
to.end_row()
return to.dump()
except TypeError:
return ''

def format_text(obj):
obj_list = obj if isinstance(obj, list) else [obj]
to = TextOutput()
try:
for item in obj_list:
for item_key in sorted(item):
to.add(item_key, item[item_key])
return to.dump()
except TypeError:
return ''

class OutputProducer(object):

def __init__(self, formatter=format_json, file=sys.stdout):
self.formatter = formatter
self.file = file

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

class TableOutput(object):
def __init__(self):
self._rows = [{}]
self._columns = {}
self._column_order = []

def dump(self):
if len(self._rows) == 1:
return

with StringIO() as io:
cols = [(c, self._columns[c]) for c in self._column_order]
io.write(' | '.join(c.center(w) for c, w in cols))
io.write('\n')
io.write('-|-'.join('-' * w for c, w in cols))
io.write('\n')
for r in self._rows[:-1]:
io.write(' | '.join(r[c].ljust(w) for c, w in cols))
io.write('\n')
return io.getvalue()

@property
def any_rows(self):
return len(self._rows) > 1

def cell(self, name, value):
n = str(name)
v = str(value)
max_width = self._columns.get(n)
if max_width is None:
self._column_order.append(n)
max_width = len(n)
self._rows[-1][n] = v
self._columns[n] = max(max_width, len(v))

def end_row(self):
self._rows.append({})

class TextOutput(object):

def __init__(self):
self.identifiers = {}

def add(self, identifier, value):
if identifier in self.identifiers:
self.identifiers[identifier].append(value)
else:
self.identifiers[identifier] = [value]

def dump(self):
with StringIO() as io:
for id in sorted(self.identifiers):
io.write(id.upper())
io.write('\t')
for col in self.identifiers[id]:
if isinstance(col, str):
io.write(col)
else:
# TODO: Handle complex objects
io.write("null")
io.write('\t')
io.write('\n')
return io.getvalue()

2 changes: 1 addition & 1 deletion src/azure/cli/_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def normalize_properties(user, subscriptions):
consolidated = []
for s in subscriptions:
consolidated.append({
'id': s.id.split('/')[-1],
'id': s.id.rpartition('/')[2],
'name': s.display_name,
'state': s.state,
'user': user,
Expand Down
39 changes: 2 additions & 37 deletions src/azure/cli/_util.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,3 @@
import types

class TableOutput(object):
def __enter__(self):
self._rows = [{}]
self._columns = {}
self._column_order = []
return self

def __exit__(self, ex_type, ex_value, ex_tb):
if ex_type:
return
if len(self._rows) == 1:
return

cols = [(c, self._columns[c]) for c in self._column_order]
print(' | '.join(c.center(w) for c, w in cols))
print('-|-'.join('-' * w for c, w in cols))
for r in self._rows[:-1]:
print(' | '.join(r[c].ljust(w) for c, w in cols))
print()

@property
def any_rows(self):
return len(self._rows) > 1

def cell(self, name, value):
n = str(name)
v = str(value)
max_width = self._columns.get(n)
if max_width is None:
self._column_order.append(n)
max_width = len(n)
self._rows[-1][n] = v
self._columns[n] = max(max_width, len(v))

def end_row(self):
self._rows.append({})
def normalize_newlines(str_to_normalize):
return str_to_normalize.replace('\r\n', '\n')
10 changes: 1 addition & 9 deletions src/azure/cli/commands/account.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from .._profile import Profile
from .._util import TableOutput
from ..commands import command, description, option

@command('account list')
Expand All @@ -8,14 +7,7 @@ def list_subscriptions(args, unexpected):
profile = Profile()
subscriptions = profile.load_subscriptions()

with TableOutput() as to:
for subscription in subscriptions:
to.cell('Name', subscription['name'])
to.cell('Active', bool(subscription['active']))
to.cell('User', subscription['user'])
to.cell('Subscription Id', subscription['id'])
to.cell('State', subscription['state'])
to.end_row()
return subscriptions

@command('account set')
@description(_('Set the current subscription'))
Expand Down
16 changes: 5 additions & 11 deletions src/azure/cli/commands/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
from azure.mgmt.resource.subscriptions import SubscriptionClient, \
SubscriptionClientConfiguration

from msrest import Serializer

from .._profile import Profile
from .._util import TableOutput
from ..commands import command, description, option

CLIENT_ID = '04b07795-8ddb-461a-bbee-02f9e1bf7b46'
Expand All @@ -27,18 +28,11 @@ def login(args, unexpected):
if not subscriptions:
raise RuntimeError(_('No subscriptions found for this account.'))

#keep useful properties and not json serializable
serializable = Serializer().serialize_data(subscriptions, "[Subscription]")

#keep useful properties and not json serializable
profile = Profile()
consolidated = Profile.normalize_properties(username, subscriptions)
profile.set_subscriptions(consolidated, credentials.token['access_token'])

with TableOutput() as to:
for subscription in consolidated:
to.cell('Name', subscription['name'])
to.cell('Active', bool(subscription['active']))
to.cell('User', subscription['user'])
to.cell('Subscription Id', subscription['id'])
to.cell('State', subscription['state'])
to.end_row()

return serializable
15 changes: 4 additions & 11 deletions src/azure/cli/commands/storage.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from msrest import Serializer

from ..main import SESSION
from .._logging import logging
from .._util import TableOutput
from ..commands import command, description, option
from .._profile import Profile

Expand All @@ -24,15 +24,8 @@ def list_accounts(args, unexpected):
else:
accounts = smc.storage_accounts.list()

with TableOutput() as to:
for acc in accounts:
assert isinstance(acc, StorageAccount)
to.cell('Name', acc.name)
to.cell('Type', acc.account_type)
to.cell('Location', acc.location)
to.end_row()
if not to.any_rows:
print('No storage accounts defined')
serializable = Serializer().serialize_data(accounts, "[StorageAccount]")
return serializable

@command('storage account check')
@option('--account-name <name>')
Expand Down
7 changes: 6 additions & 1 deletion src/azure/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from ._locale import install as locale_install
from ._logging import configure_logging, logger
from ._session import Session
from ._output import OutputProducer

# CONFIG provides external configuration options
CONFIG = Session()
Expand Down Expand Up @@ -37,7 +38,11 @@ def main(args):
commands.add_to_parser(parser)

try:
parser.execute(args)
result = parser.execute(args)
# Commands can return a dictionary/list of results
# If they do, we print the results.
if result:
OutputProducer().out(result)
except RuntimeError as ex:
logger.error(ex.args[0])
return ex.args[1] if len(ex.args) >= 2 else -1
Expand Down
57 changes: 57 additions & 0 deletions src/azure/cli/tests/test_output.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from __future__ import print_function

import unittest

try:
# Python 3
from io import StringIO
except ImportError:
# Python 2
from StringIO import StringIO

from azure.cli._output import OutputProducer, OutputFormatException, format_json, format_table, format_text
import azure.cli._util as util

class TestOutput(unittest.TestCase):

@classmethod
def setUpClass(cls):
pass

@classmethod
def tearDownClass(cls):
pass

def setUp(self):
self.io = StringIO()

def tearDown(self):
self.io.close()

def test_out_json_valid(self):
"""
The JSON output when the input is a dict should be the dict serialized to JSON
"""
output_producer = OutputProducer(formatter=format_json, file=self.io)
output_producer.out({'active': True, 'id': '0b1f6472'})
self.assertEqual(util.normalize_newlines(self.io.getvalue()), util.normalize_newlines(
"""{
"active": true,
"id": "0b1f6472"
}
"""))

def test_out_table_valid(self):
"""
"""
output_producer = OutputProducer(formatter=format_table, file=self.io)
output_producer.out({'active': True, 'id': '0b1f6472'})
self.assertEqual(util.normalize_newlines(self.io.getvalue()), util.normalize_newlines(
"""active | id
-------|---------
True | 0b1f6472

"""))

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