diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index eecb5bd88..a9b912817 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -47,6 +47,8 @@ '3': logging.DEBUG } +VALID_FORMATS = ['raw', 'table', 'json'] + class CommandParser(object): def __init__(self, env): @@ -164,7 +166,7 @@ def main(args=sys.argv[1:], env=Environment()): data = command.execute(client, command_args) if data: format = command_args.get('--format', 'table') - if format not in ['raw', 'table']: + if format not in VALID_FORMATS: raise ArgumentError('Invalid format "%s"' % format) s = format_output(data, fmt=format) if s: diff --git a/SoftLayer/CLI/helpers.py b/SoftLayer/CLI/helpers.py index afe3e4530..b0a0c3cff 100644 --- a/SoftLayer/CLI/helpers.py +++ b/SoftLayer/CLI/helpers.py @@ -7,39 +7,60 @@ :license: BSD, see LICENSE for more details. """ import os +import json from SoftLayer.CLI.environment import CLIRunnableType from SoftLayer.utils import NestedDict from prettytable import PrettyTable, FRAME, NONE -__all__ = ['Table', 'CLIRunnable', 'FormattedItem', 'valid_response', - 'confirm', 'no_going_back', 'mb_to_gb', 'gb', 'listing', 'CLIAbort', - 'NestedDict', 'resolve_id', 'format_output'] +__all__ = ['Table', 'KeyValueTable', 'CLIRunnable', 'FormattedItem', + 'valid_response', 'confirm', 'no_going_back', 'mb_to_gb', 'gb', + 'listing', 'CLIAbort', 'NestedDict', 'resolve_id', 'format_output'] def format_output(data, fmt='table'): + """ Given some data, will format it for output + + :param data: One of: String, Table, FormattedItem, List, Tuple, + SequentialOutput + :param string fmt (optional): One of: table, raw, json, python + """ if isinstance(data, basestring): return data - if isinstance(data, Table): + # responds to .prettytable() + if hasattr(data, 'prettytable'): if fmt == 'table': return str(format_prettytable(data)) elif fmt == 'raw': return str(format_no_tty(data)) - if fmt != 'raw' and isinstance(data, FormattedItem): - return str(data.formatted) + # responds to .to_python() + if hasattr(data, 'to_python'): + if fmt == 'json': + return json.dumps( + format_output(data, fmt='python'), + indent=4, + cls=CLIJSONEncoder) + elif fmt == 'python': + return data.to_python() + + # responds to .formatted + if hasattr(data, 'formatted'): + if fmt == 'table': + return str(data.formatted) - if isinstance(data, SequentialOutput): - output = [format_output(d, fmt=fmt) for d in data] - if not data.blanks: - output = [x for x in output if len(x)] - return format_output(output, fmt=fmt) + # responds to .separator + if hasattr(data, 'separator'): + output = [format_output(d, fmt=fmt) for d in data if d] + return str(SequentialOutput(data.separator, output)) + # is iterable if isinstance(data, list) or isinstance(data, tuple): output = [format_output(d, fmt=fmt) for d in data] return format_output(listing(output, separator=os.linesep)) + # fallback, convert this odd object to a string return str(data) @@ -56,6 +77,9 @@ def format_prettytable(table): def format_no_tty(table): + for i, row in enumerate(table.rows): + for j, item in enumerate(row): + table.rows[i][j] = format_output(item, fmt='raw') t = table.prettytable() for col in table.columns: t.align[col] = 'l' @@ -75,53 +99,51 @@ def __init__(self, original, formatted=None): else: self.formatted = self.original + def to_python(self): + return self.original + def __str__(self): + if self.original is None: + return 'NULL' return str(self.original) __repr__ = __str__ -def resolve_id(resolver, identifier, name='object'): - """ Resolves a single id using an id resolver function which returns a list - of ids. - - :param resolver: function that resolves ids. Should return None or a list - of ids. - :param string identifier: a string identifier used to resolve ids - :param string name: the object type, to be used in error messages +def mb_to_gb(megabytes): + """ Takes in the number of megabytes and returns a FormattedItem that + displays gigabytes. + :param int megabytes: number of megabytes """ - ids = resolver(identifier) - - if len(ids) == 0: - raise CLIAbort("Error: Unable to find %s '%s'" % (name, identifier)) - - if len(ids) > 1: - raise CLIAbort( - "Error: Multiple %s found for '%s': %s" % - (name, identifier, ', '.join([str(_id) for _id in ids]))) - - return ids[0] - - -def mb_to_gb(megabytes): return FormattedItem(megabytes, "%dG" % (float(megabytes) / 1024)) def gb(gigabytes): + """ Takes in the number of gigabytes and returns a FormattedItem that + displays gigabytes. + + :param int gigabytes: number of gigabytes + """ return FormattedItem(int(float(gigabytes)) * 1024, "%dG" % int(float(gigabytes))) def blank(): """ Returns FormatedItem to make pretty output use a dash - and raw formatting to use NULL""" - return FormattedItem('NULL', '-') + and raw formatting to use NULL + """ + return FormattedItem(None, '-') -def listing(item, separator=','): - l = separator.join((str(i) for i in item)) - return FormattedItem(l, l) +def listing(items, separator=','): + """ Given an iterable, returns a FormatedItem which display a list of + items + + :param items: An iterable that outputs strings + :param string separator: the separator to use + """ + return SequentialOutput(separator, items) class CLIRunnable(object): @@ -138,6 +160,29 @@ def execute(client, args): pass +def resolve_id(resolver, identifier, name='object'): + """ Resolves a single id using an id resolver function which returns a list + of ids. + + :param resolver: function that resolves ids. Should return None or a list + of ids. + :param string identifier: a string identifier used to resolve ids + :param string name: the object type, to be used in error messages + + """ + ids = resolver(identifier) + + if len(ids) == 0: + raise CLIAbort("Error: Unable to find %s '%s'" % (name, identifier)) + + if len(ids) > 1: + raise CLIAbort( + "Error: Multiple %s found for '%s': %s" % + (name, identifier, ', '.join([str(_id) for _id in ids]))) + + return ids[0] + + def valid_response(prompt, *valid): ans = raw_input(prompt).lower() @@ -202,6 +247,19 @@ def __init__(self, columns): def add_row(self, row): self.rows.append(row) + def _format_python_value(self, value): + if hasattr(value, 'to_python'): + return value.to_python() + return value + + def to_python(self): + # Adding rows + l = [] + for row in self.rows: + formatted_row = [self._format_python_value(v) for v in row] + l.append(dict(zip(self.columns, formatted_row))) + return l + def prettytable(self): """ Returns a new prettytable instance. """ t = PrettyTable(self.columns) @@ -216,6 +274,28 @@ def prettytable(self): return t +class KeyValueTable(Table): + def to_python(self): + d = {} + for row in self.rows: + d[row[0]] = self._format_python_value(row[1]) + return d + + class SequentialOutput(list): - def __init__(self, blanks=True, *args, **kwargs): - self.blanks = blanks + def __init__(self, separator=os.linesep, *args, **kwargs): + self.separator = separator + super(SequentialOutput, self).__init__(*args, **kwargs) + + def to_python(self): + return self + + def __str__(self): + return self.separator.join(str(x) for x in self) + + +class CLIJSONEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, FormattedItem): + return obj.to_python() + return super(CLIJSONEncoder, self).default(obj) diff --git a/SoftLayer/CLI/modules/bmetal.py b/SoftLayer/CLI/modules/bmetal.py index 89b69569b..1c3ea0b0e 100644 --- a/SoftLayer/CLI/modules/bmetal.py +++ b/SoftLayer/CLI/modules/bmetal.py @@ -15,7 +15,8 @@ import re from os import linesep from SoftLayer.CLI import ( - CLIRunnable, Table, no_going_back, confirm, listing, FormattedItem) + CLIRunnable, Table, KeyValueTable, no_going_back, confirm, listing, + FormattedItem) from SoftLayer.CLI.helpers import (CLIAbort, SequentialOutput) from SoftLayer import HardwareManager @@ -54,7 +55,7 @@ class BMetalCreateOptions(CLIRunnable): @classmethod def execute(cls, client, args): - t = Table(['Name', 'Value']) + t = KeyValueTable(['Name', 'Value']) t.align['Name'] = 'r' t.align['Value'] = 'l' @@ -78,31 +79,39 @@ def execute(cls, client, args): if args['--cpu'] or args['--memory'] or show_all: results = cls.get_create_options(bmi_options, 'cpu') - + memory_cpu_table = Table(['memory', 'cpu']) for result in results: - t.add_row([result[0], listing( - item[0] for item in sorted(result[1], - key=lambda x: int(x[0])))]) + memory_cpu_table.add_row([ + result[0], + listing( + [item[0] for item in sorted( + result[1], key=lambda x: int(x[0]) + )])]) + t.add_row(['memory/cpu', memory_cpu_table]) if args['--os'] or show_all: results = cls.get_create_options(bmi_options, 'os') for result in results: - t.add_row([result[0], linesep.join( - item[0] for item in sorted(result[1]))]) + t.add_row([ + result[0], + listing( + [item[0] for item in sorted(result[1])], + separator=linesep + )]) if args['--disk'] or show_all: results = cls.get_create_options(bmi_options, 'disk')[0] t.add_row([results[0], listing( - item[0] for item in sorted(results[1]))]) + [item[0] for item in sorted(results[1])])]) if args['--nic'] or show_all: results = cls.get_create_options(bmi_options, 'nic') for result in results: t.add_row([result[0], listing( - item[0] for item in sorted(result[1],))]) + [item[0] for item in sorted(result[1],)])]) return t @@ -143,7 +152,7 @@ def get_create_options(cls, bmi_options, section, pretty=True): key = memory if pretty: - key = 'cpus (%s gb ram)' % memory + key = memory results.append((key, mem_options[memory])) @@ -389,7 +398,7 @@ def execute(cls, client, args): if args.get('--hourly'): billing_rate = 'hourly' t.add_row(['Total %s cost' % billing_rate, "%.2f" % total]) - output = SequentialOutput(blanks=False) + output = SequentialOutput() output.append(t) output.append(FormattedItem( '', @@ -400,7 +409,7 @@ def execute(cls, client, args): "This action will incur charges on your account. Continue?"): result = mgr.place_order(**order) - t = Table(['name', 'value']) + t = KeyValueTable(['name', 'value']) t.align['name'] = 'r' t.align['value'] = 'l' t.add_row(['id', result['orderId']]) diff --git a/SoftLayer/CLI/modules/cci.py b/SoftLayer/CLI/modules/cci.py index e6e7a0279..7dd084e7b 100755 --- a/SoftLayer/CLI/modules/cci.py +++ b/SoftLayer/CLI/modules/cci.py @@ -31,7 +31,8 @@ CLIRunnable, Table, no_going_back, confirm, mb_to_gb, listing, FormattedItem) from SoftLayer.CLI.helpers import ( - CLIAbort, ArgumentError, SequentialOutput, NestedDict, blank, resolve_id) + CLIAbort, ArgumentError, SequentialOutput, NestedDict, blank, resolve_id, + KeyValueTable) class ListCCIs(CLIRunnable): @@ -97,7 +98,8 @@ def execute(client, args): guest = NestedDict(guest) t.add_row([ guest['id'], - guest['datacenter']['name'] or blank(), + FormattedItem(guest['datacenter']['name'], + guest['datacenter']['longName']), guest['fullyQualifiedDomainName'], guest['maxCpu'], mb_to_gb(guest['maxMemory']), @@ -125,8 +127,7 @@ class CCIDetails(CLIRunnable): @staticmethod def execute(client, args): cci = CCIManager(client) - - t = Table(['Name', 'Value']) + t = KeyValueTable(['Name', 'Value']) t.align['Name'] = 'r' t.align['Value'] = 'l' @@ -136,9 +137,12 @@ def execute(client, args): t.add_row(['id', result['id']]) t.add_row(['hostname', result['fullyQualifiedDomainName']]) - t.add_row(['status', result['status']['name']]) - t.add_row(['state', result['powerState']['name']]) - t.add_row(['datacenter', result['datacenter']['name'] or blank()]) + t.add_row(['status', FormattedItem( + result['status']['keyName'], result['status']['name'])]) + t.add_row(['state', FormattedItem( + result['powerState']['keyName'], result['powerState']['name'])]) + t.add_row(['datacenter', FormattedItem( + result['datacenter']['name'], result['datacenter']['longName'])]) t.add_row(['cores', result['maxCpu']]) t.add_row(['memory', mb_to_gb(result['maxMemory'])]) t.add_row(['public_ip', result['primaryIpAddress'] or blank()]) @@ -163,11 +167,10 @@ def execute(client, args): t.add_row(['price rate', result['billingItem']['recurringFee']]) if args.get('--passwords'): - user_strs = [] + pass_table = Table(['username', 'password']) for item in result['operatingSystem']['passwords']: - user_strs.append( - "%s %s" % (item['username'], item['password'])) - t.add_row(['users', listing(user_strs)]) + pass_table.add_row([item['username'], item['password']]) + t.add_row(['users', pass_table]) tag_row = [] for tag in result['tagReferences']: @@ -218,7 +221,7 @@ def execute(cls, client, args): if args['--all']: show_all = True - t = Table(['Name', 'Value']) + t = KeyValueTable(['Name', 'Value']) t.align['Name'] = 'r' t.align['Value'] = 'l' @@ -443,7 +446,7 @@ def execute(client, args): if args.get('--hourly'): billing_rate = 'hourly' t.add_row(['Total %s cost' % billing_rate, "%.2f" % total]) - output = SequentialOutput(blanks=False) + output = SequentialOutput() output.append(t) output.append(FormattedItem( '', @@ -455,7 +458,7 @@ def execute(client, args): "This action will incur charges on your account. Continue?"): result = cci.create_instance(**data) - t = Table(['name', 'value']) + t = KeyValueTable(['name', 'value']) t.align['name'] = 'r' t.align['value'] = 'l' t.add_row(['id', result['id']]) diff --git a/SoftLayer/CLI/modules/config.py b/SoftLayer/CLI/modules/config.py index 8a79213ea..21e2f5249 100644 --- a/SoftLayer/CLI/modules/config.py +++ b/SoftLayer/CLI/modules/config.py @@ -14,12 +14,13 @@ from SoftLayer import ( Client, SoftLayerAPIError, API_PUBLIC_ENDPOINT, API_PRIVATE_ENDPOINT) -from SoftLayer.CLI import CLIRunnable, CLIAbort, Table, confirm, format_output +from SoftLayer.CLI import ( + CLIRunnable, CLIAbort, KeyValueTable, confirm, format_output) import ConfigParser def config_table(env): - t = Table(['Name', 'Value']) + t = KeyValueTable(['Name', 'Value']) t.align['Name'] = 'r' t.align['Value'] = 'l' config = env.config diff --git a/SoftLayer/CLI/modules/hardware.py b/SoftLayer/CLI/modules/hardware.py index 71d307c2f..aa274fb7a 100644 --- a/SoftLayer/CLI/modules/hardware.py +++ b/SoftLayer/CLI/modules/hardware.py @@ -22,8 +22,9 @@ import os from os import linesep from SoftLayer.CLI.helpers import ( - CLIRunnable, Table, FormattedItem, NestedDict, CLIAbort, blank, listing, - SequentialOutput, gb, no_going_back, resolve_id, confirm, ArgumentError) + CLIRunnable, Table, KeyValueTable, FormattedItem, NestedDict, CLIAbort, + blank, listing, SequentialOutput, gb, no_going_back, resolve_id, confirm, + ArgumentError) from SoftLayer import HardwareManager @@ -88,7 +89,8 @@ def execute(client, args): server = NestedDict(server) t.add_row([ server['id'], - server['datacenter']['name'] or blank(), + FormattedItem(server['datacenter']['name'], + server['datacenter']['longName']), server['fullyQualifiedDomainName'], server['processorCoreAmount'], gb(server['memoryCapacity']), @@ -115,7 +117,7 @@ class HardwareDetails(CLIRunnable): def execute(client, args): hardware = HardwareManager(client) - t = Table(['Name', 'Value']) + t = KeyValueTable(['Name', 'Value']) t.align['Name'] = 'r' t.align['Value'] = 'l' @@ -127,7 +129,9 @@ def execute(client, args): t.add_row(['id', result['id']]) t.add_row(['hostname', result['fullyQualifiedDomainName']]) t.add_row(['status', result['hardwareStatus']['status']]) - t.add_row(['datacenter', result['datacenter']['name'] or blank()]) + t.add_row(['datacenter', + FormattedItem(result['datacenter']['name'], + result['datacenter']['longName'])]) t.add_row(['cores', result['processorCoreAmount']]) t.add_row(['memory', gb(result['memoryCapacity'])]) t.add_row(['public_ip', result['primaryIpAddress'] or blank()]) @@ -141,7 +145,7 @@ def execute(client, args): result['operatingSystem']['softwareLicense'] ['softwareDescription']['name'] or blank() )]) - t.add_row(['created', result['provisionDate']]) + t.add_row(['created', result['provisionDate'] or blank()]) if result.get('notes'): t.add_row(['notes', result['notes']]) @@ -211,12 +215,12 @@ class CancelHardware(CLIRunnable): action = 'cancel' options = ['confirm'] - @staticmethod - def execute(client, args): + @classmethod + def execute(cls, client, args): hw = HardwareManager(client) hw_id = resolve_id(hw, args.get('')) - print "(Optional) Add a cancellation comment:", + cls.env.out("(Optional) Add a cancellation comment:", nl=False) comment = raw_input() reason = args.get('--reason') @@ -345,7 +349,7 @@ class HardwareCreateOptions(CLIRunnable): def execute(cls, client, args): mgr = HardwareManager(client) - t = Table(['Name', 'Value']) + t = KeyValueTable(['Name', 'Value']) t.align['Name'] = 'r' t.align['Value'] = 'l' @@ -370,9 +374,10 @@ def execute(cls, client, args): if args['--cpu'] or show_all: results = cls.get_create_options(ds_options, 'cpu') + cpu_table = Table(['id', 'description']) for result in sorted(results): - t.add_row([result[0], listing( - item[0] for item in sorted(result[1]))]) + cpu_table.add_row([result[1], result[0]]) + t.add_row(['cpu', cpu_table]) if args['--memory'] or show_all: results = cls.get_create_options(ds_options, 'memory')[0] @@ -384,14 +389,22 @@ def execute(cls, client, args): results = cls.get_create_options(ds_options, 'os') for result in results: - t.add_row([result[0], linesep.join( - item[0] for item in sorted(result[1]))]) + t.add_row([ + result[0], + listing( + [item[0] for item in sorted(result[1])], + separator=linesep + )]) if args['--disk'] or show_all: results = cls.get_create_options(ds_options, 'disk')[0] - t.add_row([results[0], linesep.join( - item[0] for item in sorted(results[1],))]) + t.add_row([ + results[0], + listing( + [item[0] for item in sorted(results[1])], + separator=linesep + )]) if args['--nic'] or show_all: results = cls.get_create_options(ds_options, 'nic') @@ -427,16 +440,12 @@ def get_create_options(cls, ds_options, section, pretty=True): return [('datacenter', datacenters)] elif 'cpu' == section: results = [] - cpu_regex = re.compile('\s(\w+)\s(\d+)\s+\-\s+([\d\.]+GHz)' - '\s+\([\w ]+\)\s+\-\s+(.+)$') for item in ds_options['categories']['server']['items']: - cpu = cpu_regex.search(item['description']) - text = 'cpu: ' + cpu.group(1) + ' ' + cpu.group(2) + ' (' \ - + cpu.group(3) + ', ' + cpu.group(4) + ')' - - if cpu: - results.append((text, [(cpu.group(2), item['price_id'])])) + results.append(( + item['description'], + item['price_id'] + )) return results elif 'memory' == section: @@ -612,6 +621,7 @@ class CreateHardware(CLIRunnable): --dry-run, --test Do not create the server, just get a quote """ action = 'create' + options = ['confirm'] @classmethod def execute(cls, client, args): @@ -699,7 +709,7 @@ def execute(cls, client, args): if args.get('--hourly'): billing_rate = 'hourly' t.add_row(['Total %s cost' % billing_rate, "%.2f" % total]) - output = SequentialOutput(blanks=False) + output = SequentialOutput() output.append(t) output.append(FormattedItem( '', @@ -710,7 +720,7 @@ def execute(cls, client, args): "This action will incur charges on your account. Continue?"): result = mgr.place_order(**order) - t = Table(['name', 'value']) + t = KeyValueTable(['name', 'value']) t.align['name'] = 'r' t.align['value'] = 'l' t.add_row(['id', result['orderId']]) diff --git a/SoftLayer/CLI/modules/image.py b/SoftLayer/CLI/modules/image.py index 45b27ee79..4623750d1 100644 --- a/SoftLayer/CLI/modules/image.py +++ b/SoftLayer/CLI/modules/image.py @@ -4,7 +4,7 @@ Manage compute and flex images The available commands are: - list List active vlans with firewalls + list List images """ # :copyright: (c) 2013, SoftLayer Technologies, Inc. All rights reserved. # :license: BSD, see LICENSE for more details. @@ -17,7 +17,7 @@ class ListImages(CLIRunnable): """ usage: sl image list [--public | --private] [options] -List images on the account +List images Options: --public Display only public images diff --git a/SoftLayer/CLI/modules/metadata.py b/SoftLayer/CLI/modules/metadata.py index f70757687..dfee6893b 100644 --- a/SoftLayer/CLI/modules/metadata.py +++ b/SoftLayer/CLI/modules/metadata.py @@ -24,7 +24,7 @@ # :license: BSD, see LICENSE for more details. from SoftLayer import MetadataManager -from SoftLayer.CLI import CLIRunnable, Table, listing, CLIAbort +from SoftLayer.CLI import CLIRunnable, KeyValueTable, listing, CLIAbort class BackendMacAddresses(CLIRunnable): @@ -200,7 +200,7 @@ class Network(CLIRunnable): def execute(client, args): meta = MetadataManager() if args['']: - t = Table(['Name', 'Value']) + t = KeyValueTable(['Name', 'Value']) t.align['Name'] = 'r' t.align['Value'] = 'l' network = meta.public_network() @@ -217,7 +217,7 @@ def execute(client, args): return t if args['']: - t = Table(['Name', 'Value']) + t = KeyValueTable(['Name', 'Value']) t.align['Name'] = 'r' t.align['Value'] = 'l' network = meta.private_network() diff --git a/SoftLayer/managers/cci.py b/SoftLayer/managers/cci.py index 21ccc24cc..47aaad6b9 100644 --- a/SoftLayer/managers/cci.py +++ b/SoftLayer/managers/cci.py @@ -52,12 +52,12 @@ def list_instances(self, hourly=True, monthly=True, tags=None, cpus=None, 'primaryBackendIpAddress', 'primaryIpAddress', 'lastKnownPowerState.name', - 'powerState.name', + 'powerState', 'maxCpu', 'maxMemory', - 'datacenter.name', + 'datacenter', 'activeTransaction.transactionStatus[friendlyName,name]', - 'status.name', + 'status', ]) kwargs['mask'] = "mask[%s]" % ','.join(items) @@ -136,15 +136,14 @@ def get_instance(self, id, **kwargs): 'networkComponents[id, status, speed, maxSpeed, name,' 'macAddress, primaryIpAddress, port, primarySubnet]', 'lastKnownPowerState.name', - 'powerState.name', + 'powerState', 'maxCpu', 'maxMemory', - 'datacenter.name', + 'datacenter', 'activeTransaction.id', 'blockDevices', 'blockDeviceTemplateGroup[id, name]', 'userData', - 'status.name', 'operatingSystem.softwareLicense.' 'softwareDescription[manufacturer,name,version,referenceCode]', 'operatingSystem.passwords[username,password]', diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index ad2e638b5..c0544eb35 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -101,7 +101,7 @@ def list_hardware(self, tags=None, cpus=None, memory=None, hostname=None, 'memoryCapacity', 'primaryBackendIpAddress', 'primaryIpAddress', - 'datacenter.name', + 'datacenter', ]) kwargs['mask'] = "mask[%s]" % ','.join(items) @@ -218,7 +218,7 @@ def get_hardware(self, id, **kwargs): 'primaryBackendIpAddress', 'primaryIpAddress', 'userData', - 'datacenter.name', + 'datacenter', 'networkComponents[id, status, speed, maxSpeed, name,' 'ipmiMacAddress, ipmiIpAddress, macAddress, primaryIpAddress,' 'port, primarySubnet]', diff --git a/SoftLayer/tests/CLI/helper_tests.py b/SoftLayer/tests/CLI/helper_tests.py index 11911be16..da8f808c3 100644 --- a/SoftLayer/tests/CLI/helper_tests.py +++ b/SoftLayer/tests/CLI/helper_tests.py @@ -7,6 +7,7 @@ """ import sys import os +import json try: import unittest2 as unittest except ImportError: @@ -21,6 +22,22 @@ raw_input_path = '__builtin__.raw_input' +class CLIJSONEncoderTest(unittest.TestCase): + def test_default(self): + out = json.dumps({ + 'formattedItem': cli.helpers.FormattedItem('normal', 'formatted') + }, cls=cli.helpers.CLIJSONEncoder) + self.assertEqual(out, '{"formattedItem": "normal"}') + + out = json.dumps({'normal': 'string'}, cls=cli.helpers.CLIJSONEncoder) + self.assertEqual(out, '{"normal": "string"}') + + def test_fail(self): + self.assertRaises( + TypeError, + json.dumps, {'test': object()}, cls=cli.helpers.CLIJSONEncoder) + + class PromptTests(unittest.TestCase): @patch(raw_input_path) @@ -134,8 +151,37 @@ def test_gb(self): def test_blank(self): item = cli.helpers.blank() - self.assertEqual('NULL', item.original) + self.assertEqual(None, item.original) self.assertEqual('-', item.formatted) + self.assertEqual('NULL', str(item)) + + +class FormattedListTests(unittest.TestCase): + def test_init(self): + l = cli.listing([1, 'two'], separator=':') + self.assertEqual([1, 'two'], list(l)) + self.assertEqual(':', l.separator) + + l = cli.listing([]) + self.assertEqual(',', l.separator) + + def test_to_python(self): + l = cli.listing([1, 'two']) + result = l.to_python() + self.assertEqual([1, 'two'], result) + + l = cli.listing(x for x in [1, 'two']) + result = l.to_python() + self.assertEqual([1, 'two'], result) + + def test_str(self): + l = cli.listing([1, 'two']) + result = str(l) + self.assertEqual('1,two', result) + + l = cli.listing((x for x in [1, 'two']), separator=':') + result = str(l) + self.assertEqual('1:two', result) class CLIAbortTests(unittest.TestCase): @@ -196,6 +242,31 @@ def test_format_output_raw(self): self.assertNotIn('nothing', str(ret)) self.assertIn('testdata', str(ret)) + def test_format_output_json(self): + t = cli.Table(['nothing']) + t.align['nothing'] = 'c' + t.add_row(['testdata']) + t.add_row([cli.helpers.blank()]) + t.sortby = 'nothing' + ret = cli.helpers.format_output(t, 'json') + self.assertEqual('''[ + { + "nothing": "testdata" + }, + { + "nothing": null + } +]''', ret) + + def test_format_output_json_keyvaluetable(self): + t = cli.KeyValueTable(['key', 'value']) + t.add_row(['nothing', cli.helpers.blank()]) + t.sortby = 'nothing' + ret = cli.helpers.format_output(t, 'json') + self.assertEqual('''{ + "nothing": null +}''', ret) + def test_format_output_formatted_item(self): item = cli.FormattedItem('test', 'test_formatted') ret = cli.helpers.format_output(item, 'table') @@ -221,7 +292,7 @@ def test_unknown(self): self.assertEqual('{}', t) def test_sequentialoutput(self): - t = cli.helpers.SequentialOutput(blanks=False) + t = cli.helpers.SequentialOutput() self.assertTrue(hasattr(t, 'append')) t.append('This is a test') t.append('') @@ -229,6 +300,6 @@ def test_sequentialoutput(self): output = cli.helpers.format_output(t) self.assertEqual("This is a test\nMore tests", output) - t.blanks = True + t.separator = ',' output = cli.helpers.format_output(t) - self.assertEqual("This is a test\n\nMore tests", output) + self.assertEqual("This is a test,More tests", output)