Commit 37d05524 authored by Dmitry Lukhtionov's avatar Dmitry Lukhtionov Committed by Dmitry Volodin
Browse files

Add line_wrapper and row_wrapper arguments to parse_table function

parent 7aa5b0c7
......@@ -21,6 +21,10 @@ rx_header_start = re.compile(r"^\s*[-=]+[\s\+]+[-=]+")
rx_col = re.compile(r"^([\s\+]*)([\-]+|[=]+)")
def default_line_wrapper(p_line):
return p_line.expandtabs()
def parse_table(
s,
allow_wrap=False,
......@@ -29,7 +33,8 @@ def parse_table(
max_width=0,
footer=None,
n_row_delim="",
expand_tabs=True,
line_wrapper=default_line_wrapper,
row_wrapper=None,
):
"""
Parse string containing table an return a list of table rows.
......@@ -57,17 +62,23 @@ def parse_table(
:type footer: string
:param n_row_delim: Append delimiter to next cell line
:type n_row_delim: string
:param expand_tabs: Apply expandtabs() to each line
:type expand_tabs: bool
:param line_wrapper: Call line_wrapper with line argument
:type line_wrapper: callable
:param row_wrapper: Call row_wrapper with row argument
:type row_wrapper: callable
"""
r = []
columns = []
if footer is not None:
rx_footer = re.compile(footer)
if line_wrapper and not callable(line_wrapper):
line_wrapper = None
if row_wrapper and not callable(line_wrapper):
row_wrapper = None
for line in s.splitlines():
if expand_tabs:
if line_wrapper:
# Replace tabs with spaces with step 8
line = line.expandtabs()
line = line_wrapper(line)
if not line.strip() and footer is None:
columns = []
continue
......@@ -115,13 +126,18 @@ def parse_table(
and not r[-1][i].endswith(n_row_delim)
and not x.startswith(n_row_delim)
):
r[-1][i] += "%s%s" % (n_row_delim, x)
r[-1][i] += "%s%s" % (n_row_delim, row_wrapper(x) if row_wrapper else x)
else:
r[-1][i] += x
r[-1][i] += row_wrapper(x) if row_wrapper else x
else:
r += [row]
else:
r += [[line[f:t].strip() for f, t in columns]]
r += [
[
row_wrapper(line[f:t]).strip() if row_wrapper else line[f:t].strip()
for f, t in columns
]
]
if allow_wrap:
return [[x.strip() for x in rr] for rr in r]
else:
......
......@@ -40,6 +40,7 @@ from noc.core.lldp import (
lldp_caps_to_bits,
)
from noc.core.comp import smart_text
from noc.core.text import parse_table
class Script(BaseScript):
......@@ -59,124 +60,6 @@ class Script(BaseScript):
rx_header_start = re.compile(r"^\s*[-=]+[\s\+]+[-=]+")
rx_col = re.compile(r"^([\s\+]*)([\-]+|[=]+)")
def parse_table(
self,
s,
allow_wrap=False,
allow_extend=False,
expand_columns=False,
max_width=0,
footer=None,
n_row_delim="",
expand_tabs=True,
strip_rows=False,
):
"""
Parse string containing table an return a list of table rows.
Each row is a list of cells.
Columns are determined by a sequences of ---- or ==== which are
determines rows bounds.
Examples:
First Second Third
----- ------ -----
a b c
ddd eee fff
Will be parsed down to the [["a","b","c"],["ddd","eee","fff"]]
:param s: Table for parsing
:type s: str
:param allow_wrap: Union if cell contins multiple line
:type allow_wrap: bool
:param allow_extend: Check if column on row longest then column width, enlarge it and shift rest of columns
:type allow_extend: bool
:param expand_columns: Expand columns covering all available width
:type expand_columns: bool
:param max_width: Max table width, if table width < max_width extend length, else - nothing
:type max_width: int
:param footer: stop iteration if match expression footer
:type footer: string
:param n_row_delim: Append delimiter to next cell line
:type n_row_delim: string
:param expand_tabs: Apply expandtabs() to each line
:type expand_tabs: bool
:param strip_rows: Apply strip() to rest rows in column, if allow_wrap set to True
:type strip_rows: bool
"""
r = []
columns = []
if footer is not None:
rx_footer = re.compile(footer)
for line in s.splitlines():
if expand_tabs:
# Replace tabs with spaces with step 8
line = line.expandtabs()
if not line.strip() and footer is None:
columns = []
continue
if footer is not None and rx_footer.search(line):
break # Footer reached, stop
if not columns and self.rx_header_start.match(line):
# Column delimiters found. try to determine column's width
columns = []
x = 0
while line:
match = self.rx_col.match(line)
if not match:
break
spaces = len(match.group(1))
dashes = len(match.group(2))
columns += [(x + spaces, x + spaces + dashes)]
x += match.end()
line = line[match.end() :]
if max_width and columns[-1][-1] < max_width:
columns[-1] = (columns[-1][0], max_width)
if expand_columns:
columns = [(cc[0], nc[0] - 1) for cc, nc in zip(columns, columns[1:])] + [
columns[-1]
]
elif columns: # Fetch cells
if allow_extend:
# Find which spaces between column not empty
ll = len(line)
for i, (f, t) in enumerate(columns):
if t < ll and line[t].strip():
# If spaces not empty - shift column width equal size row
shift = len(line[f:].split()[0]) - (t - f)
# Enlarge column
columns[i] = (f, t + shift)
# Shift rest
columns[i + 1 :] = [
(v[0] + shift, v[1] + shift) for v in columns[i + 1 :]
]
break
if allow_wrap:
row = [line[f:t] for f, t in columns]
if r and not row[0].strip():
# first column is empty
for i, x in enumerate(row):
if (
x.strip()
and not r[-1][i].endswith(n_row_delim)
and not x.startswith(n_row_delim)
):
if strip_rows:
r[-1][i] += "%s%s" % (n_row_delim, x.strip())
else:
r[-1][i] += "%s%s" % (n_row_delim, x)
else:
if strip_rows:
r[-1][i] += x.strip()
else:
r[-1][i] += x
else:
r += [row]
else:
r += [[line[f:t].strip() for f, t in columns]]
if allow_wrap:
return [[x.strip() for x in rr] for rr in r]
else:
return r
def get_local_iface(self):
r = {}
names = {x: y for y, x in six.iteritems(self.scripts.get_ifindexes())}
......@@ -263,7 +146,9 @@ class Script(BaseScript):
r = []
# Fallback to CLI
lldp = self.cli("show lldp neighbors")
for link in self.parse_table(lldp, allow_wrap=True, expand_tabs=False, strip_rows=True):
for link in parse_table(
lldp, allow_wrap=True, line_wrapper=None, row_wrapper=lambda x: x.strip()
):
local_interface = link[0]
remote_chassis_id = link[1]
remote_port = link[2]
......@@ -293,7 +178,6 @@ class Script(BaseScript):
remote_chassis_id_subtype = LLDP_CHASSIS_SUBTYPE_MAC
else:
remote_chassis_id_subtype = LLDP_CHASSIS_SUBTYPE_LOCAL
# Get remote port subtype
remote_port_subtype = LLDP_PORT_SUBTYPE_ALIAS
if is_ipv4(remote_port):
......
......@@ -2,7 +2,7 @@
# ----------------------------------------------------------------------
# noc.lib.text tests
# ----------------------------------------------------------------------
# Copyright (C) 2007-2019 The NOC Project
# Copyright (C) 2007-2018 The NOC Project
# See LICENSE for details
# ----------------------------------------------------------------------
......@@ -256,9 +256,28 @@ g2 00:11:22:33:44:55 GigabitEthernet SS555_XXXX_Skeeee B, R 109
te1/0/3 (1RY\t# GigabitEthernet1/ MBH_75_00020_1 B, R 106
3/0
""",
{"allow_extend": True, "allow_wrap": True, "expand_tabs": False},
{"allow_extend": True, "allow_wrap": True, "line_wrapper": None},
[["te1/0/3", "(1RY\t#", "GigabitEthernet1/3/0", "MBH_75_00020_1", "B, R", "106"]],
),
(
"""
Port Device ID Port ID System Name Capabilities TTL
--------- ----------------- ------------- ----------------- ------------ -----
gi1/0/1 00:11:22:33:44:55 gigaethernet1 Internacional'nyy B, r 100
/1/28 -13_2pod
""",
{"allow_wrap": True, "row_wrapper": lambda x: x.strip()},
[
[
"gi1/0/1",
"00:11:22:33:44:55",
"gigaethernet1/1/28",
"Internacional'nyy-13_2pod",
"B, r",
"100",
]
],
),
],
)
def test_parse_table(value, kwargs, expected):
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment