src/modules/misc.py
changeset 2510 f48530fd135d
parent 2453 330443795456
child 2566 32ef5a3b4c12
--- a/src/modules/misc.py	Fri Aug 12 15:46:57 2011 -0700
+++ b/src/modules/misc.py	Mon Aug 15 15:10:42 2011 -0700
@@ -31,12 +31,10 @@
 import hashlib
 import locale
 import os
-import pkg.client.api_errors as api_errors
-import pkg.portable as portable
-import pkg.version as version
 import platform
 import re
 import shutil
+import simplejson as json
 import stat
 import struct
 import sys
@@ -45,6 +43,9 @@
 import urlparse
 import zlib
 
+import pkg.client.api_errors as api_errors
+import pkg.portable as portable
+
 from pkg import VERSION
 from pkg.client import global_settings
 from pkg.client.debugvalues import DebugValues
@@ -1083,12 +1084,12 @@
                     for fname in fnames
                 )
         except EnvironmentError, e:
-                raise apx._convert_error(e)
+                raise api_errors._convert_error(e)
 
-def get_col_listing(desired_field_order, field_data, field_values, out_format,
+def get_listing(desired_field_order, field_data, field_values, out_format,
     def_fmt, omit_headers, escape_output=True):
-        """Returns a string containing a columnar listing defined by provided
-        values.
+        """Returns a string containing a listing defined by provided values
+        in the specified output format.
 
         'desired_field_order' is the list of the fields to show in the order
         they should be output left to right.
@@ -1109,19 +1110,22 @@
             field_nameN: field_value
           }
 
-        'out_format' is the format to use for output.  Currently 'default' and
-        'tsv' are supported.  The first is intended for human-readable output,
-        the latter for parseable output.
+        'out_format' is the format to use for output.  Currently 'default',
+        'tsv', 'json', and 'json-formatted' are supported.  The first is
+        intended for columnar, human-readable output, and the others for
+        parsable output.
 
-        'def_fmt' is the default Python formatting string to use for output.  It
-        must match the fields defined in 'field_data'.
+        'def_fmt' is the default Python formatting string to use for the
+        'default' human-readable output.  It must match the fields defined
+        in 'field_data'.
 
         'omit_headers' is a boolean specifying whether headers should be
-        included in the listing.
+        included in the listing.  (If applicable to the specified output
+        format.)
 
         'escape_output' is an optional boolean indicating whether shell
         metacharacters or embedded control sequences should be escaped
-        before display.
+        before display.  (If applicable to the specified output format.)
         """
 
         # Custom sort function for preserving field ordering
@@ -1156,7 +1160,7 @@
         def set_value(entry):
                 val = entry[1]
                 multi_value = False
-                if isinstance(val, (list, set)):
+                if isinstance(val, (list, tuple, set, frozenset)):
                         multi_value = True
                 elif val == "":
                         entry[0][2] = '""'
@@ -1196,9 +1200,44 @@
         elif out_format == "tsv":
                 # Create a formatting string for the tsv output
                 # format.
-                num_fields = len(field_data.keys())
+                num_fields = sum(
+                    1 for k in field_data
+                    if filter_tsv(field_data[k])
+                )
                 fmt = "\t".join('%s' for x in xrange(num_fields))
                 filter_func = filter_tsv
+        elif out_format == "json" or out_format == "json-formatted":
+                args = { "sort_keys": True }
+                if out_format == "json-formatted":
+                        args["indent"] = 2
+
+                # 'json' formats always include any extra fields returned;
+                # any explicitly named fields are only included if 'json'
+                # is explicitly listed.
+                def fmt_val(v):
+                        if isinstance(v, basestring):
+                                return v
+                        if isinstance(v, (list, tuple, set, frozenset)):
+                                return [fmt_val(e) for e in v]
+                        if isinstance(v, dict):
+                                for k, e in v.items():
+                                        v[k] = fmt_val(e)
+                                return v
+                        return str(v)
+
+                output = json.dumps([
+                    dict(
+                        (k, fmt_val(entry[k]))
+                        for k in entry
+                        if k not in field_data or "json" in field_data[k][0]
+                    )
+                    for entry in field_values
+                ], **args)
+
+                if out_format == "json-formatted":
+                        # Include a trailing newline for readability.
+                        return output + "\n"
+                return output
 
         # Extract the list of headers from the field_data dictionary.  Ensure
         # they are extracted in the desired order by using the custom sort
@@ -1216,6 +1255,7 @@
                 map(set_value, (
                     (field_data[f], v)
                     for f, v in entry.iteritems()
+                    if f in field_data
                 ))
                 values = map(get_value, sorted(filter(filter_func,
                     field_data.values()), sort_fields))