diff --git a/ovs_dbg/ofparse/console.py b/ovs_dbg/ofparse/console.py index 4e40b69..06de0b9 100644 --- a/ovs_dbg/ofparse/console.py +++ b/ovs_dbg/ofparse/console.py @@ -85,35 +85,16 @@ class ConsoleFormatter(FlowFormatter): rich.console.Console() """ - default_style = Style(color="white") - - default_style_obj = FlowStyle( - { - "key": Style(color="#B0C4DE"), - "value": Style(color="#B0C4DE"), - "delim": Style(color="#B0C4DE"), - "value.type.IPAddress": Style(color="#008700"), - "value.type.IPMask": Style(color="#008700"), - "value.type.EthMask": Style(color="#008700"), - "value.ct": Style(color="bright_black"), - "value.ufid": Style(color="#870000"), - "value.clone": Style(color="bright_black"), - "value.controller": Style(color="bright_black"), - "flag": Style(color="#875fff"), - "key.drop": Style(color="red"), - "key.resubmit": Style(color="#00d700"), - "key.output": Style(color="#00d700"), - "key.highlighted": Style(color="#f20905", underline=True), - "value.highlighted": Style(color="#f20905", underline=True), - "delim.highlighted": Style(color="#f20905", underline=True), - } - ) - - def __init__(self, console=None, style_obj=None, default_style=None, **kwargs): + def __init__(self, opts=None, console=None, **kwargs): super(ConsoleFormatter, self).__init__() - self.console = console or Console(**kwargs) + style = self.style_from_opts(opts) + self.console = console or Console(no_color=(style is None), **kwargs) + self.style = style or FlowStyle() - def print_flow(self, flow, style=None): + def style_from_opts(self, opts): + return self._style_from_opts(opts, "console", Style) + + def print_flow(self, flow): """ Prints a flow to the console @@ -123,10 +104,10 @@ def print_flow(self, flow, style=None): """ buf = ConsoleBuffer(Text()) - self.format_flow(buf, flow, style) + self.format_flow(buf, flow) self.console.print(buf.text) - def format_flow(self, buf, flow, style=None): + def format_flow(self, buf, flow): """ Formats the flow into the provided buffer as a rich.Text @@ -135,10 +116,7 @@ def format_flow(self, buf, flow, style=None): flow (ovs_dbg.OFPFlow): the flow to format style (FlowStyle): Optional; style object to use """ - style_obj = style or self.default_style_obj - return super(ConsoleFormatter, self).format_flow( - buf, flow, style_obj, self.default_style - ) + return super(ConsoleFormatter, self).format_flow(buf, flow, self.style) def hash_pallete(hue, saturation, value): @@ -162,7 +140,7 @@ def get_style(string): return get_style -def print_context(console, paged=False, styles=True): +def print_context(console, opts): """ Returns a printing context @@ -171,7 +149,7 @@ def print_context(console, paged=False, styles=True): paged (bool): Wheter to page the output style (bool): Whether to force the use of styled pager """ - if paged: + if opts.get("paged"): # Internally pydoc's pager library is used which returns a # plain pager if both stdin and stdout are not tty devices # @@ -180,6 +158,8 @@ def print_context(console, paged=False, styles=True): if not sys.stdin.isatty() and sys.stdout.isatty(): setattr(sys.stdin, "isatty", lambda: True) - return console.pager(styles=styles) + with_style = opts.get("style") is not None + + return console.pager(styles=with_style) return contextlib.nullcontext() diff --git a/ovs_dbg/ofparse/dp.py b/ovs_dbg/ofparse/dp.py index 99a672f..402374a 100644 --- a/ovs_dbg/ofparse/dp.py +++ b/ovs_dbg/ofparse/dp.py @@ -18,6 +18,7 @@ print_context, hash_pallete, ) +from ovs_dbg.ofparse.format import FlowStyle from ovs_dbg.ofparse.html import HTMLBuffer, HTMLFormatter from ovs_dbg.odp import ODPFlow from ovs_dbg.filter import OFFilter @@ -62,15 +63,15 @@ def callback(flow): ) tree = Tree("Datapath Flows (logical)") - console = Console(color_system=None if opts["no_color"] else "256") - ofconsole = ConsoleFormatter(console) + ofconsole = ConsoleFormatter(opts) + console = ofconsole.console # HSV_tuples = [(x / size, 0.7, 0.8) for x in range(size)] recirc_style_gen = hash_pallete( hue=[x / 50 for x in range(0, 50)], saturation=[0.7], value=[0.8] ) - style = ConsoleFormatter.default_style_obj + style = ofconsole.style style.set_default_value_style(Style(color="bright_black")) style.set_key_style("output", Style(color="green")) style.set_value_style("output", Style(color="green")) @@ -79,13 +80,13 @@ def callback(flow): def append_to_tree(parent, flow): buf = ConsoleBuffer(Text()) - ofconsole.format_flow(buf=buf, flow=flow, style=style) + ofconsole.format_flow(buf, flow) tree_elem = parent.add(buf.text) return tree_elem process_flow_tree(flow_list, tree, 0, append_to_tree) - with print_context(console, opts["paged"], not opts["no_color"]): + with print_context(console, opts): console.print(tree) @@ -224,9 +225,9 @@ def callback(flow): class HTMLFlowTree: - def __init__(self, flow=None, style=None): + def __init__(self, flow=None, opts=None): self._flow = flow - self._style = style + self._formatter = HTMLFormatter(opts) self._subflows = list() def append(self, flow): @@ -245,7 +246,7 @@ def render(self, item=0): id=self._flow.id ) buf = HTMLBuffer() - HTMLFormatter().format_flow(buf, self._flow, self._style) + self._formatter.format_flow(buf, self._flow, self._style) html_obj += buf.text html_obj += "" if self._subflows: diff --git a/ovs_dbg/ofparse/format.py b/ovs_dbg/ofparse/format.py index 170230e..af53da6 100644 --- a/ovs_dbg/ofparse/format.py +++ b/ovs_dbg/ofparse/format.py @@ -13,6 +13,7 @@ class FlowStyle: - key.highlighted (if key is found in hightlighted) - key.{key} - key + - default In order to determine the style for a "value", the following items in the dictionary are fetched: @@ -23,11 +24,11 @@ class FlowStyle: - value.{key} - value.type.{value.__class__.__name__} - value + - default Additionally, the following style items can be defined: - delim: for delimiters - delim.highlighted: for delimiters of highlighted key-values - - extra: for extra characters """ def __init__(self, initial=None): @@ -71,14 +72,14 @@ def get(self, key): def get_delim_style(self, highlighted=False): delim_style_lookup = ["delim.highlighted"] if highlighted else [] - delim_style_lookup.extend(["delim"]) + delim_style_lookup.extend(["delim", "default"]) return next( (self._styles.get(s) for s in delim_style_lookup if self._styles.get(s)), None, ) def get_flag_style(self): - return self._styles.get("flag") + return self._styles.get("flag") or self._styles.get("default") def get_key_style(self, kv, highlighted=False): key = kv.meta.kstring @@ -86,7 +87,7 @@ def get_key_style(self, kv, highlighted=False): key_style_lookup = ( ["key.highlighted.%s" % key, "key.highlighted"] if highlighted else [] ) - key_style_lookup.extend(["key.%s" % key, "key"]) + key_style_lookup.extend(["key.%s" % key, "key", "default"]) style = next( (self._styles.get(s) for s in key_style_lookup if self._styles.get(s)), @@ -113,6 +114,7 @@ def get_value_style(self, kv, highlighted=False): "value.%s" % key, "value.type.%s" % value_type, "value", + "default", ] ) @@ -138,7 +140,39 @@ def highlight(self, keys): """ self._highlighted = keys - def format_flow(self, buf, flow, style_obj=None, default_style=None): + def _style_from_opts(self, opts, opts_key, style_constructor): + """Create style object from options + + Args: + opts (dict): Options dictionary + opts_key (str): The options style key to extract (e.g: console or html) + style_constructor(callable): A callable that creates a derived style object + """ + if not opts or not opts.get("style"): + return None + + section_name = ".".join(["styles", opts.get("style")]) + if section_name not in opts.get("config").sections(): + raise Exception("Style not present in config file") + + config = opts.get("config")[section_name] + style = {} + for key in config: + (_, console, style_full_key) = key.partition(opts_key + ".") + if not console: + continue + + (style_key, _, prop) = style_full_key.rpartition(".") + if not prop or not style_key: + raise Exception("malformed style config: {}".format(key)) + + if not style.get(style_key): + style[style_key] = {} + style[style_key][prop] = config[key] + + return FlowStyle({k: style_constructor(**v) for k, v in style.items()}) + + def format_flow(self, buf, flow, style_obj=None): """ Formats the flow into the provided buffer @@ -146,21 +180,19 @@ def format_flow(self, buf, flow, style_obj=None, default_style=None): buf (FlowBuffer): the flow buffer to append to flow (ovs_dbg.OFPFlow): the flow to format style_obj (FlowStyle): Optional; style to use - default_style (Any): Optional; default style to use """ last_printed_pos = 0 + style_obj = style_obj or FlowStyle() for section in sorted(flow.sections, key=lambda x: x.pos): buf.append_extra( flow.orig[last_printed_pos : section.pos], - style=style_obj.get("extra") or default_style, - ) - self.format_kv_list( - buf, section.data, section.string, style_obj, default_style + style=style_obj.get("default"), ) + self.format_kv_list(buf, section.data, section.string, style_obj) last_printed_pos = section.pos + len(section.string) - def format_kv_list(self, buf, kv_list, full_str, style_obj, default_style=None): + def format_kv_list(self, buf, kv_list, full_str, style_obj): """ Format a KeyValue List @@ -169,23 +201,19 @@ def format_kv_list(self, buf, kv_list, full_str, style_obj, default_style=None): kv_list (list[KeyValue]: the KeyValue list to format full_str (str): the full string containing all k-v style_obj (FlowStyle): a FlowStyle object to use - default_style (Any): the default style to pass to the buffer if - the provided style_obj did not return a valid style """ for i in range(len(kv_list)): kv = kv_list[i] - written = self.format_kv( - buf, kv, style_obj=style_obj, default_style=default_style - ) + written = self.format_kv(buf, kv, style_obj=style_obj) end = kv_list[i + 1].meta.kpos if i < (len(kv_list) - 1) else len(full_str) buf.append_extra( full_str[(kv.meta.kpos + written) : end].rstrip("\n\r"), - style=style_obj.get("extra") or default_style, + style=style_obj.get("default"), ) - def format_kv(self, buf, kv, style_obj, default_style=None): + def format_kv(self, buf, kv, style_obj): """Format a KeyValue A formatted keyvalue has the following parts: @@ -195,8 +223,6 @@ def format_kv(self, buf, kv, style_obj, default_style=None): buf (FlowBuffer): buffer to append the KeyValue to kv (KeyValue): The KeyValue to print style_obj (FlowStyle): The style object to use - default_style (Any): The default object to use in case the style_obj - fails to find a valid style Returns the number of printed characters """ @@ -204,7 +230,7 @@ def format_kv(self, buf, kv, style_obj, default_style=None): key = kv.meta.kstring highlighted = key in self._highlighted - key_style = style_obj.get_key_style(kv, highlighted) or default_style + key_style = style_obj.get_key_style(kv, highlighted) buf.append_key(kv, key_style) # format value ret += len(key) @@ -212,20 +238,16 @@ def format_kv(self, buf, kv, style_obj, default_style=None): return ret if kv.meta.delim not in ("\n", "\t", "\r", ""): - buf.append_delim( - kv, style_obj.get_delim_style(highlighted) or default_style - ) + buf.append_delim(kv, style_obj.get_delim_style(highlighted)) ret += len(kv.meta.delim) - value_style = style_obj.get_value_style(kv, highlighted) or default_style + value_style = style_obj.get_value_style(kv, highlighted) buf.append_value(kv, value_style) # format value ret += len(kv.meta.vstring) if kv.meta.end_delim: - buf.append_end_delim( - kv, style_obj.get_delim_style(highlighted) or default_style - ) + buf.append_end_delim(kv, style_obj.get_delim_style(highlighted)) ret += len(kv.meta.end_delim) return ret diff --git a/ovs_dbg/ofparse/html.py b/ovs_dbg/ofparse/html.py index 5a198b1..aac4591 100644 --- a/ovs_dbg/ofparse/html.py +++ b/ovs_dbg/ofparse/html.py @@ -46,8 +46,8 @@ def append_key(self, kv, style): kv (KeyValue): the KeyValue instance to append style (HTMLStyle): the style to use """ - href = style.anchor_gen(kv) if style.anchor_gen else "" - return self._append(kv.meta.kstring, style.color, href) + href = style.anchor_gen(kv) if (style and style.anchor_gen) else "" + return self._append(kv.meta.kstring, style.color if style else "", href) def append_delim(self, kv, style): """Append a delimiter @@ -55,8 +55,8 @@ def append_delim(self, kv, style): kv (KeyValue): the KeyValue instance to append style (HTMLStyle): the style to use """ - href = style.anchor_gen(kv) if style.anchor_gen else "" - return self._append(kv.meta.delim, style.color, href) + href = style.anchor_gen(kv) if (style and style.anchor_gen) else "" + return self._append(kv.meta.delim, style.color if style else "", href) def append_end_delim(self, kv, style): """Append an end delimiter @@ -64,8 +64,8 @@ def append_end_delim(self, kv, style): kv (KeyValue): the KeyValue instance to append style (HTMLStyle): the style to use """ - href = style.anchor_gen(kv) if style.anchor_gen else "" - return self._append(kv.meta.end_delim, style.color, href) + href = style.anchor_gen(kv) if (style and style.anchor_gen) else "" + return self._append(kv.meta.end_delim, style.color if style else "", href) def append_value(self, kv, style): """Append a value @@ -73,8 +73,8 @@ def append_value(self, kv, style): kv (KeyValue): the KeyValue instance to append style (HTMLStyle): the style to use """ - href = style.anchor_gen(kv) if style.anchor_gen else "" - return self._append(kv.meta.vstring, style.color, href) + href = style.anchor_gen(kv) if (style and style.anchor_gen) else "" + return self._append(kv.meta.vstring, style.color if style else "", href) def append_extra(self, extra, style): """Append extra string @@ -82,7 +82,7 @@ def append_extra(self, extra, style): kv (KeyValue): the KeyValue instance to append style (HTMLStyle): the style to use """ - return self._append(extra, style.color, "") + return self._append(extra, style.color if style else "", "") class HTMLFormatter(FlowFormatter): @@ -90,20 +90,25 @@ class HTMLFormatter(FlowFormatter): Formts a flow in HTML Format """ - default_style = HTMLStyle() default_style_obj = FlowStyle( { "value.resubmit": HTMLStyle( anchor_gen=lambda x: "#table_{}".format(x.value["table"]) - ) + ), + "default": HTMLStyle(), } ) - def __init__(self): + def __init__(self, opts=None): super(HTMLFormatter, self).__init__() + self.style = self._style_from_opts(opts, "html", HTMLStyle) or FlowStyle() + self.style.set_value_style( + "resubmit", + HTMLStyle( + self.style.get("value.resubmit"), + anchor_gen=lambda x: "#table_{}".format(x.value["table"]), + ), + ) def format_flow(self, buf, flow, style=None): - style_obj = style or self.default_style_obj - return super(HTMLFormatter, self).format_flow( - buf, flow, style_obj, self.default_style - ) + return super(HTMLFormatter, self).format_flow(buf, flow, self.style) diff --git a/ovs_dbg/ofparse/main.py b/ovs_dbg/ofparse/main.py index 833e123..2b71a7a 100644 --- a/ovs_dbg/ofparse/main.py +++ b/ovs_dbg/ofparse/main.py @@ -1,7 +1,12 @@ import click import sys +import configparser from ovs_dbg.filter import OFFilter +from pkg_resources import resource_filename + +_default_config_file = "ofparse.conf" +_default_config_path = resource_filename(__name__, _default_config_file) class Options(dict): @@ -13,6 +18,18 @@ class Options(dict): @click.group( subcommand_metavar="TYPE", context_settings=dict(help_option_names=["-h", "--help"]) ) +@click.option( + "-c", + "--config", + help="Use non-default config file", + type=click.Path(), +) +@click.option( + "--style", + help="Select style (defined in config file)", + default=None, + show_default=True, +) @click.option( "-i", "-input", @@ -30,13 +47,6 @@ class Options(dict): default=False, show_default=True, ) -@click.option( - "--no-color", - help="Do not use colors. Alternatively, set the environment variable NO_COLOR", - is_flag=True, - default=False, - show_default=True, -) @click.option( "-f", "--filter", @@ -46,7 +56,7 @@ class Options(dict): show_default=False, ) @click.pass_context -def maincli(ctx, filename, paged, no_color, filter): +def maincli(ctx, config, style, filename, paged, filter): """ OpenFlow Parse utility. @@ -57,13 +67,19 @@ def maincli(ctx, filename, paged, no_color, filter): ctx.obj = Options() ctx.obj["filename"] = filename or "" ctx.obj["paged"] = paged - ctx.obj["no_color"] = no_color if filter: try: ctx.obj["filter"] = OFFilter(filter) except Exception as e: raise click.BadParameter("Wrong filter syntax: {}".format(e)) + config_file = config or _default_config_path + parser = configparser.ConfigParser() + parser.read(config_file) + + ctx.obj["config"] = parser + ctx.obj["style"] = style + @maincli.command(hidden=True) @click.pass_context diff --git a/ovs_dbg/ofparse/ofp.py b/ovs_dbg/ofparse/ofp.py index 0fad931..0cc1dff 100644 --- a/ovs_dbg/ofparse/ofp.py +++ b/ovs_dbg/ofparse/ofp.py @@ -123,7 +123,7 @@ def callback(flow): ) tree = Tree("Ofproto Flows (logical)") - console = Console(color_system=None if opts["no_color"] else "256") + console = Console(color_system=None if opts["style"] is None else "256") for table_num in sorted(tables.keys()): table = tables[table_num] @@ -136,28 +136,26 @@ def callback(flow): ): flows = table[lflow] - text = Text() + buf = ConsoleBuffer(Text()) - text.append( + buf.append_extra( "cookie={} ".format(hex(lflow.cookie)).ljust(18), style=cookie_style_gen(str(lflow.cookie)), ) - text.append("priority={} ".format(lflow.priority), style="steel_blue") - text.append(",".join(lflow.match_keys), style="steel_blue") - text.append(" ---> ", style="bold magenta") - text.append(",".join(lflow.action_keys), style="steel_blue") - text.append(" ( x {} )".format(len(flows)), style="dark_olive_green3") - lflow_tree = table_tree.add(text) + buf.append_extra("priority={} ".format(lflow.priority), style="steel_blue") + buf.append_extra(",".join(lflow.match_keys), style="steel_blue") + buf.append_extra(" ---> ", style="bold magenta") + buf.append_extra(",".join(lflow.action_keys), style="steel_blue") + buf.append_extra(" ( x {} )".format(len(flows)), style="dark_olive_green3") + lflow_tree = table_tree.add(buf.text) if show_flows: for flow in flows: buf = ConsoleBuffer(Text()) - ConsoleFormatter(console).format_flow( - buf, flow, ConsoleFormatter.default_style_obj - ) + ConsoleFormatter(console, opts).format_flow(buf, flow) lflow_tree.add(buf.text) - with print_context(console, opts["paged"], not opts["no_color"]): + with print_context(console, opts): console.print(tree) @@ -188,7 +186,7 @@ def callback(flow): for flow in flows: html_obj += "