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