Adding ONOS Segment Routing CLI files to new repo
diff --git a/cli/prettyprint.py b/cli/prettyprint.py
new file mode 100755
index 0000000..47f119b
--- /dev/null
+++ b/cli/prettyprint.py
@@ -0,0 +1,529 @@
+#
+# Copyright (c) 2010,2011,2012,2013 Big Switch Networks, Inc.
+#
+# Licensed under the Eclipse Public License, Version 1.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+#      http://www.eclipse.org/legal/epl-v10.html
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# permissions and limitations under the License.
+#
+
+#
+# PRETTYPRINT
+#
+# This module contains classes that help formatting text for the CLI,
+# including tables, individual records etc.
+# 
+# Formatting information is stored in table_info, a dict of dicts.
+
+import model_info_list
+import os
+import array
+import datetime
+import utif
+import re
+import fmtcnv
+
+class PrettyPrinter():
+    table_info = None   # Annotated table format. This is a dict of dicts. Set by cli.
+
+    def __init__(self, bs):
+        self.sdnsh = bs
+        self.table_info = {}
+        self.format_version = {}
+        self.format_added_modules = {}
+
+        self.add_format('MODEL',
+                        'model_info',
+                        model_info_list.model_info_dict)
+
+
+    def add_format(self, name, origin, format_dict):
+        """
+        Add a format to the saved formats, from the dictionary
+        with 'name'.   The 'name' can help identify the source
+        for this format when errors arise
+        """
+
+        # very interesting items in a format are the field_orderings,
+        # and the fields description.   It could make sense to only
+        # allow specific items from the fields, to prevent field-output
+        # formatting from using uninnteded database fields.
+        for (format_name, format) in format_dict.items():
+            if not format_name in self.table_info:
+                self.table_info[format_name] = { }
+            # combine details: top level items
+            for (item_name, item_value) in format.items():
+                if item_name == 'fields':
+                    if not item_name in self.table_info[format_name]:
+                        self.table_info[format_name][item_name] = {}
+                    for (term_name, term_value) in item_value.items():
+                        if not term_name in self.table_info[format_name][item_name]:
+                            self.table_info[format_name][item_name][term_name] = {}
+                        self.table_info[format_name][item_name][term_name].update(term_value)
+                if item_name == 'field-orderings':
+                    if not item_name in self.table_info[format_name]:
+                        self.table_info[format_name][item_name] = {}
+                    self.table_info[format_name][item_name].update(item_value)
+            #
+            # save the name of the source for this format
+            if not 'self' in self.table_info[format_name]:
+                self.table_info[format_name]['self'] = name
+            if not 'origin' in self.table_info[format_name]:
+                self.table_info[format_name]['origin'] = origin
+            #
+            # Add the 'Idx' field if it's not there.
+            if format_name in self.table_info:
+                if not 'fields' in self.table_info[format_name]:
+                    self.table_info[format_name]['fields'] = {}
+                if not 'Idx' in self.table_info[format_name]['fields']:
+                    self.table_info[format_name]['fields']['Idx'] = {
+                            'verbose-name' : '#',
+                            'type' : 'CharField'
+                        }
+
+
+    def add_module_name(self, version, module):
+        """
+        Save some state about the version/module
+        """
+        if version not in self.format_version:
+            self.format_version[version] = [module]
+        else:
+            self.format_version[version].append(module)
+
+
+    def add_formats_from_module(self, version, module):
+        self.add_module_name(version, module)
+        for name in dir(module):
+            if re.match(r'.*_FORMAT$', name):
+                if module.__name__ not in self.format_added_modules:
+                    self.format_added_modules[module.__name__] = [name]
+                if name not in self.format_added_modules[module.__name__]:
+                    self.format_added_modules[module.__name__].append(name)
+                self.add_format(name, module.__name__, getattr(module, name))
+
+
+    # Utility Functions
+    def get_field_info(self, obj_type_info, field_name):
+        return obj_type_info['fields'].get(field_name, None)
+
+
+    def format_to_alias_update(self, display_format, update):
+        """
+        Given a format, find all the formatters, and for each
+        formatter, determine which aliases need to be updated.
+        """
+        if display_format not in self.table_info:
+            # this will fail soon enough when final output is attempted
+            return
+        format_dict = self.table_info[display_format]
+        if not 'fields' in format_dict:
+            return
+
+        for (field_name, field_dict) in format_dict['fields'].items():
+            if 'formatter' in field_dict:
+                fmtcnv.formatter_to_alias_update(field_dict['formatter'], update)
+            elif 'entry_formatter' in field_dict:
+                fmtcnv.formatter_to_alias_update(field_dict['entry-formatter'], update)
+        return update
+
+
+    def formats(self):
+        return self.table_info.keys()
+
+    def format_details(self):
+        """
+        Return a table of formats suitable as a format_table parameter
+        """
+        return [ { 'format'      : x,
+                   'format dict' : self.table_info[x]['self'],
+                   'origin'      : self.table_info[x]['origin'] }
+
+                  for x in self.formats()]
+
+
+    def format_as_header(self, field):
+        # LOOK! could prob do something fancy to handle camelCapStrings or underscore_strings
+        return field[0].capitalize() + field[1:]
+
+
+    def get_header_for_field(self, obj_type_info, field_name):
+        field_info = self.get_field_info(obj_type_info, field_name)
+        if not field_info:
+            return self.format_as_header(field_name)
+        default_header = self.format_as_header(field_name)
+        return field_info.get('verbose-name', default_header)
+
+
+    def format_table(self, data_list, display_format = None, field_ordering="default"):
+        """
+        Takes in list of dicts and generates nice table, e.g.
+
+        id slice_id MAC Address       ip    Switch ID    
+        ------------------------------------------------
+        1  1        00:00:00:00:01:03 10013 150861407404
+        2  2        00:00:00:00:01:01 10011 150861407404
+        3  1        00:00:00:00:02:03 10023 150866955514
+
+        @param data_list - a list of dicts
+        @param format describes the format of the output table to display
+        @param field_ordering is the field_ordering identifier in the model,
+          there can be multiple ("default", "brief", "detailed")
+       
+        """
+
+        #
+        # first, determine a list of fields to be printed, then using that list,
+        # determine the fields width of each field, including calling any formatting
+        # function, then format the result
+        #
+        # during the format computation, replace the value with the 'formatted value'
+        # to prevent multiple calls to the same formatting funcition
+        #
+        if not data_list or not type(data_list) == list:
+            if type(data_list) == dict and "error_type" in data_list:
+                return data_list.get("description", "Internal error")
+            return "None."
+
+        format_info = self.table_info.get(display_format, None)
+
+        if self.sdnsh.description:
+            if format_info == None:
+                print 'format_table: missing format %s' % display_format
+            else:
+                format_from = format_info.get('self', '')
+                print 'format_table: %s %s %d entries' % (
+                        display_format, format_from, len(data_list))
+
+        field_widths = {}
+        field_headers = {}
+        fields_to_print = []
+
+        #
+        # do the 'figur'n for which fields will get printed.
+        #
+        # to determine the length, call the formatting function, and replace the
+        # value for that field with the updated value; then 'cypher the length.
+        #
+        # note that field_widths.keys() are all the possible fields
+        # check if the headers makes any field wider and set fields_to_print
+        #
+        if format_info:
+            if 'field-orderings' in format_info:
+                fields_to_print = format_info['field-orderings'].get(field_ordering, [])
+            if len(fields_to_print) == 0: # either no field_orderings or couldn't find specific
+                fields_to_print = format_info['fields'].keys()
+            for f in fields_to_print:
+                header = self.get_header_for_field(format_info, f)
+                field_headers[f] = header
+                field_widths[f] = max(len(header), field_widths.get(f, 0))
+            # LOOK! not done now... add in extra fields discovered in data_list if desired
+            # right now, fields_to_print is a projection on the data
+        else:
+            # get fields_to_print from the field names in data_list,
+            # which is (intended to be) a list of dictionaries
+            all_fields = utif.unique_list_from_list(sum([x.keys()
+                                                        for x in data_list], []))
+            fields_to_print = sorted(all_fields)
+
+        if self.sdnsh.description:
+            print 'format_table: field order "%s" fields %s' % \
+                   (field_ordering, fields_to_print)
+
+        #
+        # generate a fields_to_print ordered list with field_widths for each
+        # by going through all data and then using field_ordering if avail.
+        #
+        row_index = 0
+        for row in data_list:
+            row_index += 1
+            if not 'Idx' in row:
+                row['Idx'] = row_index
+            for key in fields_to_print:
+            #for (k,v) in row.items():
+                if format_info:
+                    # don't worry about header here - do that soon below
+                    info = self.get_field_info(format_info, key)
+                    if info and info.get('formatter'):
+                        row[key] = str(info['formatter'](row.get(key, ''), row))
+                        w = len(row[key])
+                    else:
+                        w = len(str(row.get(key, '')))
+                else:
+                    field_headers[key] = self.format_as_header(key)
+                    w = max(len(str(row[key])), len(field_headers[key]))
+                field_widths[key] = max(w, field_widths.get(key, 0))
+
+        #
+        # generate the format_str and header lines based on fields_to_print
+        #
+        format_str_per_field = []
+        for f in fields_to_print:
+            format_str_per_field.append("%%(%s)-%ds" % (f, field_widths[f]))
+
+        row_format_str = " ".join(format_str_per_field) + "\n"
+
+        #
+        # finally print! only caveat is to handle sparse data with a blank_dict
+        # let result be a list, and append new strings to generate the final result,
+        # (for better python performance)
+        #
+        result= []
+        result.append(" ".join(format_str_per_field) % field_headers + "\n")
+        result.append("|".join(["-"*field_widths[f] for f in fields_to_print]) + "\n") # I <3 python too
+
+        blank_dict = dict([(f,"") for f in fields_to_print])
+        for row in data_list:
+            result.append(row_format_str % dict(blank_dict, **row))
+        
+        return ''.join(result)
+
+
+    def format_entry(self, data, display_format=None, field_ordering="default", debug=False):
+        """
+        Takes in parsed JSON object, generates nice single entry printout,
+        intended for 'details' display
+
+        @param data list of dictionaries, values for output
+        @param format name of format description to use for printing
+        @param field_ordering list of field to print
+        @param debug print values of compound keys
+        """
+        if not data:
+            return "None."
+        elif type(data) == dict and "error_type" in data:
+            return data.get("description", "Internal error")
+    
+        format_info = self.table_info.get(display_format, None)
+
+        # Print. Pretty please.
+        if format_info:
+            fields = format_info['fields']
+        else:
+            fields = dict([[x, {}] for x in data.keys()])
+            format_info = { 'fields' : fields }
+            if self.sdnsh.description:
+                print "format_entry: Missing format ", display_format, fields
+
+        # Find widest data field name
+        label_w = len( max(data, key=lambda x:len(x)) )
+        if format_info:
+            verbose_len = max([len(self.get_header_for_field(format_info, x)) for x in fields.keys()])
+            # This isn't exactly right, the verbose names for the fields ought to be replaced first
+            label_w = max(verbose_len, label_w)
+        label_str = "%%-%ds :" % label_w
+
+        # Use format_info for this table to order fields if possible
+        fields_to_print = None
+        if format_info:
+            if 'field-orderings' in format_info:
+                fields_to_print = format_info['field-orderings'].get(field_ordering, [])
+            else:
+                if self.sdnsh.description:
+                    print 'Error: internal: %s field ordering %s not present for %s' % \
+                          (display_format, field_ordering, format_info)
+            if fields_to_print == None or len(fields_to_print) == 0:
+                # either no field_orderings or couldn't find specific
+                fields_to_print = format_info['fields'].keys()
+        else:
+            fields_to_print = sorted(fields.keys())
+
+        result = ""
+        tmp_merged_dict = dict([(f,"") for f in fields_to_print], **data)
+        blank_dict = dict([(f,"") for f in fields_to_print])
+
+        # first print the requested fields
+        all_fields_in_correct_order = list(fields_to_print)
+        # then the remaining fields
+        all_fields_in_correct_order.extend([x for x in fields.keys()
+                                            if x not in fields_to_print])
+        # give all the formatter's a shot, save the updates
+        updated = {}
+        for e in all_fields_in_correct_order:
+            if format_info:
+                info = self.get_field_info(format_info, e)
+                if not info:
+                    continue
+                if 'entry-formatter' in info:
+                    updated[e] = info['entry-formatter'](
+                                    tmp_merged_dict.get(e, ''), tmp_merged_dict)
+                elif 'formatter' in info:
+                    updated[e] = info['formatter'](
+                                    tmp_merged_dict.get(e, ''), tmp_merged_dict)
+
+        tmp_merged_dict.update(updated)
+
+        data.update(updated)
+        all_fields_in_correct_order = filter(lambda x: x in data,
+                                             all_fields_in_correct_order)
+        for e in all_fields_in_correct_order:
+            if format_info:
+                info = self.get_field_info(format_info, e)
+                if not debug and info and 'help_text' in info and info['help_text'][0] == '#':
+                    # sdnsh._is_compound_key(), please skip display of compound key
+                    continue
+            result += (label_str % self.get_header_for_field(format_info, e)+" "+
+                       str(tmp_merged_dict[e]) + "\n");
+        
+        return result[:-1]
+
+    def get_terminal_size(self):
+        def ioctl_GWINSZ(fd):
+            try:
+                import fcntl, termios, struct, os
+                cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ,
+                                                     '1234'))
+            except:
+                return None
+            return cr
+        cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
+        if not cr:
+            try:
+                fd = os.open(os.ctermid(), os.O_RDONLY)
+                cr = ioctl_GWINSZ(fd)
+                os.close(fd)
+            except:
+                pass
+        if not cr:
+            try:
+                cr = (os.environ['LINES'], os.environ['COLUMNS'])
+            except:
+                cr = (24, 80)
+
+        if (cr[1] == 0 or cr[0] == 0):
+            return (80, 24)
+
+        return int(cr[1]), int(cr[0])
+
+    def format_time_series_graph(self, data, obj_type=None, field_ordering="default"):
+        if not data:
+            return "None."
+        elif type(data) == dict and "error_type" in data:
+            return data.get("description", "Internal error")
+
+        obj_type_info = self.table_info.get(obj_type, None)
+
+        if self.sdnsh.description:
+            print "format_time_series_graph", obj_type, obj_type_info
+        yunits = None
+        ylabel = "Value"
+        if (obj_type_info):
+            ylabel = obj_type_info['fields']['value']['verbose-name']
+            if 'units' in obj_type_info['fields']['value']:
+                yunits = obj_type_info['fields']['value']['units']
+
+
+        miny = None
+        maxy = None
+        minx = None
+        maxx = None
+
+        for (x,y) in data:
+            if miny == None or y < miny:
+                miny = y
+            if maxy == None or y > maxy:
+                maxy = y
+            if minx == None or x < minx:
+                minx = x
+            if maxx == None or x > maxx:
+                maxx = x
+
+        if (yunits == '%'):
+            maxy = 100
+
+        if isinstance(maxy, float) and maxy < 10.0:
+            axisyw = len('%.5f' % maxy)
+        else:
+            axisyw = len('%s' % maxy)
+        
+        (twidth, theight) = self.get_terminal_size()
+
+        width = twidth-axisyw
+        height = theight-5
+
+        ybucket = float(maxy)/height;
+
+        xbucket = (maxx-minx)/width;
+        if (xbucket == 0):
+            minx = maxx - 1800000
+            maxx += 1800000
+            xbucket = maxx/width;
+
+        graph = array.array('c')
+        graph.fromstring(' ' * (width * height))
+        for (x,y) in data:
+            if (ybucket == 0):
+                yc = height
+            else:
+                yc = int(round((maxy-y)/ybucket))
+            if (xbucket == 0):
+                xy = width
+            else:
+                xc = int(round((x-minx)/xbucket))
+
+            if (yc < 0):
+                yc = 0
+            if (yc >= height):
+                yc = height-1
+            if (xc < 0):
+                xc = 0
+            if (xc >= width):
+                xc = width-1
+
+            #print (xc,yc, x, y, yc*width + xc)
+            for i in range(yc,height):
+                graph[i*width + xc] = '#'
+
+        b = '%s\n' % (ylabel)
+
+        if isinstance(maxy, float) and maxy < 10.0:
+            form = '%%%d.5f|%%s\n'
+        else:
+            form = '%%%ds|%%s\n'
+
+        for i in range(0,height-1):
+            ylabel = maxy - i*ybucket
+            if not isinstance(maxy, float) or maxy >= 10.0:
+                ylabel = int(round(ylabel))
+            b += (form % axisyw) % \
+                (ylabel, 
+                 ''.join(graph[i*width:(i+1)*width-1]))
+        b += (form % axisyw) % \
+            (0, ''.join(graph[(height-1)*width:height*width-1]).replace(' ', '_'))
+
+        b += '%s' % (' ' * axisyw)
+        d = ' ' * axisyw
+
+        olddate = None
+        interval = (maxx - minx)/(width/7.0)
+        for i in range(0, width/7):
+            curtimestamp = minx + interval*i
+           
+            if i == width/7-1:
+                df = (' ' * (width % 7)) + " %m/%d^"
+                tf = (' ' * (width % 7)) + " %H:%M^"
+                curtimestamp = maxx
+            else:
+                df = "^%m/%d "
+                tf = "^%H:%M "
+
+            curtime = datetime.datetime.fromtimestamp(curtimestamp/1000.0)
+            date = curtime.strftime(df)
+            b += curtime.strftime(tf)
+            if (date != olddate):
+                olddate = date
+                d += date
+            else:
+                d += ' ' * 7
+
+        b += '\n%s\n' % d
+        b += '%s%sTime' % (' ' * axisyw, ' ' * (width/2-2))
+
+        return b