Skip to content

Commit

Permalink
Merge pull request #19 from amorenoz/highlights
Browse files Browse the repository at this point in the history
Add highlighting filters
  • Loading branch information
amorenoz authored Sep 7, 2021
2 parents cde38f4 + 2a13d52 commit 5116e73
Show file tree
Hide file tree
Showing 9 changed files with 179 additions and 53 deletions.
76 changes: 60 additions & 16 deletions ovs_dbg/filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,46 @@
"""
import pyparsing as pp
import netaddr
from functools import reduce
from operator import and_, or_

from ovs_dbg.decoders import decode_default, decode_int, Decoder, IPMask, EthMask

from ovs_dbg.fields import field_decoders


class EvaluationResult:
"""An EvaluationResult is the result of an evaluation. It contains the
boolean result and the list of key-values that were evaluated
Note that since boolean operations (and, not, or) are based only on __bool__
we use bitwise alternatives (&, ||, ~)
"""

def __init__(self, result, *kv):
self.result = result
self.kv = kv if kv else list()

def __and__(self, other):
"""Logical and operation"""
return EvaluationResult(self.result and other.result, *self.kv, *other.kv)

def __or__(self, other):
"""Logical or operation"""
return EvaluationResult(self.result or other.result, *self.kv, *other.kv)

def __invert__(self):
"""Logical not operation"""
return EvaluationResult(not self.result, *self.kv)

def __bool__(self):
"""Boolean operation"""
return self.result

def __repr__(self):
return "{} [{}]".format(self.result, self.kv)


class ClauseExpression:
operators = {}
type_decoders = {
Expand All @@ -32,7 +66,7 @@ def __repr__(self):
)

def _find_data_in_kv(self, kv_list):
"""Find a value for evaluation in a list of KeyValue
"""Find a KeyValue for evaluation in a list of KeyValue
Args:
kv_list (list[KeyValue]): list of KeyValue to look into
Expand All @@ -46,8 +80,8 @@ def _find_data_in_kv(self, kv_list):
for kv in kvs:
if kv.key == self.field:
# exact match
return kv.value
elif len(key_parts) > 1:
return kv
if len(key_parts) > 1:
data = kv.value
for subkey in key_parts[1:]:
try:
Expand All @@ -58,9 +92,10 @@ def _find_data_in_kv(self, kv_list):
if not data:
break
if data:
return data
return kv
return None

def _find_data(self, flow):
def _find_keyval_to_evaluate(self, flow):
"""Finds the key-value to use for evaluation"""
for section in flow.sections:
data = self._find_data_in_kv(section.data)
Expand All @@ -69,13 +104,22 @@ def _find_data(self, flow):
return None

def evaluate(self, flow):
data = self._find_data(flow)
if not data:
return False
"""
Return whether the clause is satisfied by the flow
Args:
flow (Flow): the flow to evaluate
"""
keyval = self._find_keyval_to_evaluate(flow)

if not keyval:
return EvaluationResult(False)

data = keyval.value

if not self.value and not self.operator:
# just asserting the existance of the key
return True
return EvaluationResult(True, keyval)

# Decode the value based on the type of data
if isinstance(data, Decoder):
Expand All @@ -86,13 +130,13 @@ def evaluate(self, flow):
decoded_value = decoder(self.value)

if self.operator == "=":
return decoded_value == data
return EvaluationResult(decoded_value == data, keyval)
elif self.operator == "<":
return data < decoded_value
return EvaluationResult(data < decoded_value, keyval)
elif self.operator == ">":
return data > decoded_value
return EvaluationResult(data > decoded_value, keyval)
elif self.operator == "~=":
return decoded_value in data
return EvaluationResult(decoded_value in data, keyval)


class BoolNot:
Expand All @@ -103,7 +147,7 @@ def __repr__(self):
return "NOT({})".format(self.args)

def evaluate(self, flow):
return not self.args.evaluate(flow)
return ~self.args.evaluate(flow)


class BoolAnd:
Expand All @@ -114,15 +158,15 @@ def __repr__(self):
return "AND({})".format(self.args)

def evaluate(self, flow):
return all([arg.evaluate(flow) for arg in self.args])
return reduce(and_, [arg.evaluate(flow) for arg in self.args])


class BoolOr:
def __init__(self, pattern):
self.args = pattern[0][0::2]

def evaluate(self, flow):
return any([arg.evaluate(flow) for arg in self.args])
return reduce(or_, [arg.evaluate(flow) for arg in self.args])

def __repr__(self):
return "OR({})".format(self.args)
Expand Down
12 changes: 8 additions & 4 deletions ovs_dbg/ofparse/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,29 +94,33 @@ def __init__(self, opts=None, console=None, **kwargs):
def style_from_opts(self, opts):
return self._style_from_opts(opts, "console", Style)

def print_flow(self, flow):
def print_flow(self, flow, highlighted=None):
"""
Prints a flow to the console
Args:
flow (ovs_dbg.OFPFlow): the flow to print
style (dict): Optional; style dictionary to use
highlighted (list): Optional; list of KeyValues to highlight
"""

buf = ConsoleBuffer(Text())
self.format_flow(buf, flow)
self.format_flow(buf, flow, highlighted)
self.console.print(buf.text)

def format_flow(self, buf, flow):
def format_flow(self, buf, flow, highlighted=None):
"""
Formats the flow into the provided buffer as a rich.Text
Args:
buf (FlowBuffer): the flow buffer to append to
flow (ovs_dbg.OFPFlow): the flow to format
style (FlowStyle): Optional; style object to use
highlighted (list): Optional; list of KeyValues to highlight
"""
return super(ConsoleFormatter, self).format_flow(buf, flow, self.style)
return super(ConsoleFormatter, self).format_flow(
buf, flow, self.style, highlighted
)


def hash_pallete(hue, saturation, value):
Expand Down
25 changes: 18 additions & 7 deletions ovs_dbg/ofparse/dp.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,12 @@ def callback(flow):

def append_to_tree(parent, flow):
buf = ConsoleBuffer(Text())
ofconsole.format_flow(buf, flow)
highlighted = None
if opts.get("highlight"):
result = opts.get("highlight").evaluate(flow)
if result:
highlighted = result.kv
ofconsole.format_flow(buf, flow, highlighted)
tree_elem = parent.add(buf.text)
return tree_elem

Expand All @@ -107,7 +112,7 @@ def callback(flow):
filter=opts.get("filter"),
)

html_obj = get_html_obj(flow_list)
html_obj = get_html_obj(flow_list, opts)

print(html_obj)

Expand Down Expand Up @@ -220,7 +225,7 @@ def callback(flow):
html_obj += svg.decode("utf-8")
html_obj += "</div>"

html_obj += get_html_obj(list(itertools.chain(*recirc_flows.values())))
html_obj += get_html_obj(list(itertools.chain(*recirc_flows.values())), opts)
print(html_obj)


Expand All @@ -229,6 +234,7 @@ def __init__(self, flow=None, opts=None):
self._flow = flow
self._formatter = HTMLFormatter(opts)
self._subflows = list()
self._opts = opts

def append(self, flow):
self._subflows.append(flow)
Expand All @@ -246,7 +252,12 @@ def render(self, item=0):
id=self._flow.id
)
buf = HTMLBuffer()
self._formatter.format_flow(buf, self._flow, self._style)
highlighted = None
if self._opts.get("highlight"):
result = self._opts.get("highlight").evaluate(self._flow)
if result:
highlighted = result.kv
self._formatter.format_flow(buf, self._flow, highlighted)
html_obj += buf.text
html_obj += "</div>"
if self._subflows:
Expand All @@ -265,13 +276,13 @@ def render(self, item=0):
return html_obj, item


def get_html_obj(flow_list, flow_attrs=None):
def get_html_obj(flow_list, opts=None):
def append_to_html(parent, flow):
html_flow = HTMLFlowTree(flow)
html_flow = HTMLFlowTree(flow, opts)
parent.append(html_flow)
return html_flow

root = HTMLFlowTree()
root = HTMLFlowTree(flow=None, opts=opts)
process_flow_tree(flow_list, root, 0, append_to_html)

html_obj = """
Expand Down
34 changes: 17 additions & 17 deletions ovs_dbg/ofparse/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,6 @@ class FlowFormatter:
def __init__(self):
self._highlighted = list()

def highlight(self, keys):
"""Set the highlighted keys
Args:
keys (list[str]): list of keys to highlight
"""
self._highlighted = keys

def _style_from_opts(self, opts, opts_key, style_constructor):
"""Create style object from options
Expand Down Expand Up @@ -172,14 +165,15 @@ def _style_from_opts(self, opts, opts_key, style_constructor):

return FlowStyle({k: style_constructor(**v) for k, v in style.items()})

def format_flow(self, buf, flow, style_obj=None):
def format_flow(self, buf, flow, style_obj=None, highlighted=None):
"""
Formats the flow into the provided buffer
Args:
buf (FlowBuffer): the flow buffer to append to
flow (ovs_dbg.OFPFlow): the flow to format
style_obj (FlowStyle): Optional; style to use
highlighted (list): Optional; list of KeyValues to highlight
"""
last_printed_pos = 0
style_obj = style_obj or FlowStyle()
Expand All @@ -189,10 +183,12 @@ def format_flow(self, buf, flow, style_obj=None):
flow.orig[last_printed_pos : section.pos],
style=style_obj.get("default"),
)
self.format_kv_list(buf, section.data, section.string, style_obj)
self.format_kv_list(
buf, section.data, section.string, style_obj, highlighted
)
last_printed_pos = section.pos + len(section.string)

def format_kv_list(self, buf, kv_list, full_str, style_obj):
def format_kv_list(self, buf, kv_list, full_str, style_obj, highlighted):
"""
Format a KeyValue List
Expand All @@ -201,10 +197,13 @@ def format_kv_list(self, buf, kv_list, full_str, style_obj):
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
highlighted (list): Optional; list of KeyValues to highlight
"""
for i in range(len(kv_list)):
kv = kv_list[i]
written = self.format_kv(buf, kv, style_obj=style_obj)
written = self.format_kv(
buf, kv, style_obj=style_obj, highlighted=highlighted
)

end = kv_list[i + 1].meta.kpos if i < (len(kv_list) - 1) else len(full_str)

Expand All @@ -213,7 +212,7 @@ def format_kv_list(self, buf, kv_list, full_str, style_obj):
style=style_obj.get("default"),
)

def format_kv(self, buf, kv, style_obj):
def format_kv(self, buf, kv, style_obj, highlighted=None):
"""Format a KeyValue
A formatted keyvalue has the following parts:
Expand All @@ -223,31 +222,32 @@ def format_kv(self, buf, kv, style_obj):
buf (FlowBuffer): buffer to append the KeyValue to
kv (KeyValue): The KeyValue to print
style_obj (FlowStyle): The style object to use
highlighted (list): Optional; list of KeyValues to highlight
Returns the number of printed characters
"""
ret = 0
key = kv.meta.kstring
highlighted = key in self._highlighted
is_highlighted = key in [k.key for k in highlighted] if highlighted else False

key_style = style_obj.get_key_style(kv, highlighted)
key_style = style_obj.get_key_style(kv, is_highlighted)
buf.append_key(kv, key_style) # format value
ret += len(key)

if not kv.meta.vstring:
return ret

if kv.meta.delim not in ("\n", "\t", "\r", ""):
buf.append_delim(kv, style_obj.get_delim_style(highlighted))
buf.append_delim(kv, style_obj.get_delim_style(is_highlighted))
ret += len(kv.meta.delim)

value_style = style_obj.get_value_style(kv, highlighted)
value_style = style_obj.get_value_style(kv, is_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))
buf.append_end_delim(kv, style_obj.get_delim_style(is_highlighted))
ret += len(kv.meta.end_delim)

return ret
Expand Down
14 changes: 12 additions & 2 deletions ovs_dbg/ofparse/html.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,5 +110,15 @@ def __init__(self, opts=None):
),
)

def format_flow(self, buf, flow, style=None):
return super(HTMLFormatter, self).format_flow(buf, flow, self.style)
def format_flow(self, buf, flow, highlighted=None):
"""
Formats the flow into the provided buffer as a html object
Args:
buf (FlowBuffer): the flow buffer to append to
flow (ovs_dbg.OFPFlow): the flow to format
highlighted (list): Optional; list of KeyValues to highlight
"""
return super(HTMLFormatter, self).format_flow(
buf, flow, self.style, highlighted
)
Loading

0 comments on commit 5116e73

Please sign in to comment.