netutils.config.parser.BaseConfigParser
Found Classes
AIREOSConfigParser
from netutils.config.parser import AIREOSConfigParser
Ancestors (MRO)
The Method Resolution Order is described below.
- AIREOSConfigParser
- CiscoConfigParser
- BaseSpaceConfigParser
- BaseConfigParser
Descendant Classes
Attributes
Key |
Value |
Defined in |
banner_start |
['banner', 'vacant-message'] |
BaseSpaceConfigParser |
banner_start |
['banner', 'vacant-message'] |
BaseConfigParser |
comment_chars |
['!'] |
BaseSpaceConfigParser |
comment_chars |
['!'] |
BaseConfigParser |
regex_banner |
re.compile('^(banner\\s+\\S+|\\s*vacant-message)\\s+(?P<banner_delimiter>\\^C|.)') |
CiscoConfigParser |
Methods
def __init__(self, config):
CiscoConfigParser
CiscoConfigParser
| def __init__(self, config: str):
"""Create ConfigParser Object.
Args:
config (str): The config text to parse.
"""
self._banner_end: t.Optional[str] = None
super(CiscoConfigParser, self).__init__(config)
|
BaseSpaceConfigParser
| def __init__(self, config: str):
"""Create ConfigParser Object.
Args:
config (str): The config text to parse.
"""
self._indent_level = 0
super(BaseSpaceConfigParser, self).__init__(config)
|
BaseConfigParser
| def __init__(self, config: str):
"""Create ConfigParser Object.
Args:
config: The config text to parse.
"""
self.config = config
self._config: t.Optional[str] = None
self._current_parents: t.Tuple[str, ...] = ()
self.generator_config = (line for line in self.config_lines_only.splitlines())
self.config_lines: t.List[ConfigLine] = []
self.build_config_relationship()
|
def _build_banner(self, config_line):
AIREOSConfigParser
AIREOSConfigParser
| def _build_banner(self, config_line: str) -> None:
raise NotImplementedError()
|
CiscoConfigParser
| def _build_banner(self, config_line: str) -> t.Optional[str]:
"""Handle banner config lines.
Args:
config_line: The start of the banner config.
Returns:
The next configuration line in the configuration text or None when banner end is the end of the config text.
Raises:
ValueError: When the parser is unable to identify the End of the Banner.
"""
if self.is_banner_one_line(config_line):
self._update_config_lines(config_line)
try:
return next(self.generator_config)
except StopIteration:
return None
return super(CiscoConfigParser, self)._build_banner(config_line)
|
BaseSpaceConfigParser
| def _build_banner(self, config_line: str) -> t.Optional[str]:
"""Handle banner config lines.
Args:
config_line: The start of the banner config.
Returns:
The next configuration line in the configuration text or None
Raises:
ValueError: When the parser is unable to identify the end of the Banner.
"""
self._update_config_lines(config_line)
self._current_parents += (config_line,)
banner_config = []
for line in self.generator_config:
if not self.is_banner_end(line):
banner_config.append(line)
else:
line = normalise_delimiter_caret_c(self.banner_end, line)
banner_config.append(line)
line = "\n".join(banner_config)
if line.endswith("^C"):
banner, end, _ = line.rpartition("^C")
line = banner.rstrip() + end
self._update_config_lines(line)
self._current_parents = self._current_parents[:-1]
try:
return next(self.generator_config)
except StopIteration:
return None
raise ValueError("Unable to parse banner end.")
|
def _build_nested_config(self, line):
BaseSpaceConfigParser
| def _build_nested_config(self, line: str) -> t.Optional[str]:
"""Handle building child config sections.
Args:
line: A configuration line from the configuration text.
Returns:
The next top-level configuration line in the configuration text or None when the last line of configuration
text is a nested configuration line.
Raises:
IndexError: When the number of parents does not match the expected deindent level.
"""
self._update_config_lines(line)
for line in self.generator_config:
if not line[0].isspace():
self._current_parents = ()
self.indent_level = 0
return line
spaces = self.get_leading_space_count(line)
if spaces == self.indent_level:
pass
elif spaces > self.indent_level:
previous_config = self.config_lines[-1]
self._current_parents += (previous_config.config_line,)
else:
self._current_parents = self._remove_parents(line, spaces)
if spaces != self.indent_level:
self.indent_level = spaces
if self.is_banner_start(line):
banner_line = self._build_banner(line)
if banner_line is None or not banner_line[0].isspace():
self._current_parents = ()
self.indent_level = 0
return banner_line
line = banner_line
self._update_config_lines(line)
return None
|
def _match_type_check(line, pattern, match_type):
BaseSpaceConfigParser
| @staticmethod
def _match_type_check(line: str, pattern: str, match_type: str) -> bool:
"""Checks pattern for exact match or regex."""
if match_type == "exact" and line == pattern:
return True
if match_type == "startswith" and line.startswith(pattern):
return True
if match_type == "endswith" and line.endswith(pattern):
return True
if match_type == "regex" and re.match(pattern, line):
return True
return False
|
def _remove_parents(self, line, current_spaces):
BaseSpaceConfigParser
| def _remove_parents(self, line: str, current_spaces: int) -> t.Tuple[str, ...]:
"""Remove parents from ``self._curent_parents`` based on indent levels.
Args:
config_line: A line of text in the config.
Returns:
The config lines parent config lines.
"""
deindent_level = 1
try:
previous_parent = self._current_parents[-1]
previous_indent = self.get_leading_space_count(previous_parent)
while previous_indent > current_spaces:
deindent_level += 1
previous_parent = self._current_parents[-deindent_level]
previous_indent = self.get_leading_space_count(previous_parent)
except IndexError:
raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n")
parents = self._current_parents[:-deindent_level] or (self._current_parents[0],)
return parents
|
def _update_config_lines(self, config_line):
BaseSpaceConfigParser
| def _update_config_lines(self, config_line: str) -> None:
"""Add a ``ConfigLine`` object to ``self.config_lines``.
Args:
config_line: The current config line being evaluated.
Returns:
None
"""
entry = ConfigLine(config_line, self._current_parents)
self.config_lines.append(entry)
|
def build_config_relationship(self):
BaseSpaceConfigParser
BaseSpaceConfigParser
| def build_config_relationship(self) -> t.List[ConfigLine]:
r"""Parse text tree of config lines and their parents.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
>>> config = (
... "interface Ethernet1/1\n"
... " vlan 10\n"
... " no shutdown\n"
... "interface Ethernet1/2\n"
... " shutdown\n"
... )
>>> config_tree = BaseSpaceConfigParser(config)
>>> config_tree.build_config_relationship() == \
... [
... ConfigLine(config_line='interface Ethernet1/1', parents=()),
... ConfigLine(config_line=' vlan 10', parents=('interface Ethernet1/1',)),
... ConfigLine(config_line=' no shutdown', parents=('interface Ethernet1/1',)),
... ConfigLine(config_line='interface Ethernet1/2', parents=(),),
... ConfigLine(config_line=' shutdown', parents=('interface Ethernet1/2',))
... ]
True
"""
for line in self.generator_config:
if not line[0].isspace():
self._current_parents = ()
if self.is_banner_start(line):
line = self._build_banner(line) # type: ignore
else:
previous_config = self.config_lines[-1]
self._current_parents = (previous_config.config_line,)
self.indent_level = self.get_leading_space_count(line)
if not self.is_banner_start(line):
line = self._build_nested_config(line) # type: ignore
else:
line = self._build_banner(line) # type: ignore
if line is not None and line[0].isspace():
line = self._build_nested_config(line) # type: ignore
else:
self._current_parents = ()
if line is None:
break
elif self.is_banner_start(line):
line = self._build_banner(line) # type: ignore
self._update_config_lines(line)
return self.config_lines
|
BaseConfigParser
| def build_config_relationship(self) -> t.List[ConfigLine]:
"""Parse text tree of config lines and their parents."""
raise NotImplementedError()
|
def find_all_children(self, pattern, match_type=exact):
BaseSpaceConfigParser
| def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]:
"""Returns configuration part for a specific pattern not including parents.
Args:
pattern: pattern that describes parent.
match_type (optional): Exact or regex. Defaults to "exact".
Returns:
configuration under that parent pattern.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser
>>> config = '''
... router bgp 45000
... address-family ipv4 unicast
... neighbor 192.168.1.2 activate
... network 172.17.1.0 mask'''
>>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith")
>>> print(bgp_conf)
['router bgp 45000', ' address-family ipv4 unicast', ' neighbor 192.168.1.2 activate', ' network 172.17.1.0 mask']
"""
config = []
for cfg_line in self.build_config_relationship():
parents = cfg_line.parents[0] if cfg_line.parents else None
if (
parents
and self._match_type_check(parents, pattern, match_type)
or self._match_type_check(cfg_line.config_line, pattern, match_type)
):
config.append(cfg_line.config_line)
return config
|
def find_children_w_parents(self, parent_pattern, child_pattern, match_type=exact):
BaseSpaceConfigParser
| def find_children_w_parents(
self, parent_pattern: str, child_pattern: str, match_type: str = "exact"
) -> t.List[str]:
"""Returns configuration part for a specific pattern including parents and children.
Args:
parent_pattern: pattern that describes parent.
child_pattern: pattern that describes child.
match_type (optional): Exact or regex. Defaults to "exact".
Returns:
configuration under that parent pattern.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser
>>> config = '''
... router bgp 45000
... address-family ipv4 unicast
... neighbor 192.168.1.2 activate
... network 172.17.1.0 mask'''
>>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern=" address-family", match_type="regex")
>>> print(bgp_conf)
[' address-family ipv4 unicast', ' neighbor 192.168.1.2 activate', ' network 172.17.1.0 mask']
"""
config = []
potential_parents = [
elem.parents[0]
for elem in self.build_config_relationship()
if self._match_type_check(elem.config_line, child_pattern, match_type)
]
for cfg_line in self.build_config_relationship():
parents = cfg_line.parents[0] if cfg_line.parents else None
if parents in potential_parents and self._match_type_check(parents, parent_pattern, match_type):
config.append(cfg_line.config_line)
return config
|
def get_leading_space_count(config_line):
BaseSpaceConfigParser
| @staticmethod
def get_leading_space_count(config_line: str) -> int:
r"""Determine how many spaces the ``config_line`` is indented.
Args:
config_line: A line of text in the config.
Returns:
The number of leading spaces.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser
>>> config = '''interface GigabitEthernet1\n description link to ISP'''
>>> config_line = " description link to ISP"
>>> indent_level = BaseSpaceConfigParser(config).get_leading_space_count(config_line)
>>> indent_level
1
>>>
"""
return len(config_line) - len(config_line.lstrip())
|
def is_banner_end(self, line):
BaseSpaceConfigParser
| def is_banner_end(self, line: str) -> bool:
"""Determine if line ends the banner config.
Args:
line: The current config line in iteration.
Returns:
True if line ends banner, else False.
"""
if self.banner_end in line:
return True
return False
|
def is_banner_one_line(config_line):
CiscoConfigParser
| @staticmethod
def is_banner_one_line(config_line: str) -> bool:
"""Determine if all banner config is on one line."""
_, delimeter, banner = config_line.partition("^C")
# Based on NXOS configs, the banner delimeter is ignored until another char is used
banner_config_start = banner.lstrip(delimeter)
if delimeter not in banner_config_start:
return False
return True
|
def is_banner_start(self, line):
CiscoConfigParser
CiscoConfigParser
| def is_banner_start(self, line: str) -> bool:
"""Determine if the line starts a banner config."""
state = super(CiscoConfigParser, self).is_banner_start(line)
if state:
self.banner_end = line
return state
|
BaseSpaceConfigParser
| def is_banner_start(self, line: str) -> bool:
"""Determine if the line starts a banner config.
Args:
line: The current config line in iteration.
Returns:
True if line starts banner, else False.
"""
for banner_start in self.banner_start:
if line.lstrip().startswith(banner_start):
return True
return False
|
def is_comment(self, line):
BaseSpaceConfigParser
| def is_comment(self, line: str) -> bool:
"""Determine if line is a comment.
Args:
line: A config line from the device.
Returns:
True if line is a comment, else False.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser
>>> BaseSpaceConfigParser("interface Ethernet1/1").is_comment("interface Ethernet1/1")
False
>>> BaseSpaceConfigParser("!").is_comment("!")
True
>>>
"""
for comment_char in self.comment_chars:
if line.lstrip().startswith(comment_char):
return True
return False
|
ASAConfigParser
from netutils.config.parser import ASAConfigParser
Ancestors (MRO)
The Method Resolution Order is described below.
- ASAConfigParser
- CiscoConfigParser
- BaseSpaceConfigParser
- BaseConfigParser
Descendant Classes
Attributes
Key |
Value |
Defined in |
banner_start |
['banner', 'vacant-message'] |
BaseSpaceConfigParser |
banner_start |
['banner', 'vacant-message'] |
BaseConfigParser |
comment_chars |
['!'] |
BaseSpaceConfigParser |
comment_chars |
['!'] |
BaseConfigParser |
regex_banner |
re.compile('^(banner\\s+\\S+|\\s*vacant-message)\\s+(?P<banner_delimiter>\\^C|.)') |
CiscoConfigParser |
Methods
def __init__(self, config):
ASAConfigParser
ASAConfigParser
| def __init__(self, config: str):
"""Create ConfigParser Object.
Args:
config: The config text to parse.
"""
self.unique_config_lines: t.Set[ConfigLine] = set()
self.same_line_children: t.Set[ConfigLine] = set()
super(ASAConfigParser, self).__init__(config)
|
CiscoConfigParser
| def __init__(self, config: str):
"""Create ConfigParser Object.
Args:
config (str): The config text to parse.
"""
self._banner_end: t.Optional[str] = None
super(CiscoConfigParser, self).__init__(config)
|
BaseSpaceConfigParser
| def __init__(self, config: str):
"""Create ConfigParser Object.
Args:
config (str): The config text to parse.
"""
self._indent_level = 0
super(BaseSpaceConfigParser, self).__init__(config)
|
BaseConfigParser
| def __init__(self, config: str):
"""Create ConfigParser Object.
Args:
config: The config text to parse.
"""
self.config = config
self._config: t.Optional[str] = None
self._current_parents: t.Tuple[str, ...] = ()
self.generator_config = (line for line in self.config_lines_only.splitlines())
self.config_lines: t.List[ConfigLine] = []
self.build_config_relationship()
|
def _build_banner(self, config_line):
CiscoConfigParser
CiscoConfigParser
| def _build_banner(self, config_line: str) -> t.Optional[str]:
"""Handle banner config lines.
Args:
config_line: The start of the banner config.
Returns:
The next configuration line in the configuration text or None when banner end is the end of the config text.
Raises:
ValueError: When the parser is unable to identify the End of the Banner.
"""
if self.is_banner_one_line(config_line):
self._update_config_lines(config_line)
try:
return next(self.generator_config)
except StopIteration:
return None
return super(CiscoConfigParser, self)._build_banner(config_line)
|
BaseSpaceConfigParser
| def _build_banner(self, config_line: str) -> t.Optional[str]:
"""Handle banner config lines.
Args:
config_line: The start of the banner config.
Returns:
The next configuration line in the configuration text or None
Raises:
ValueError: When the parser is unable to identify the end of the Banner.
"""
self._update_config_lines(config_line)
self._current_parents += (config_line,)
banner_config = []
for line in self.generator_config:
if not self.is_banner_end(line):
banner_config.append(line)
else:
line = normalise_delimiter_caret_c(self.banner_end, line)
banner_config.append(line)
line = "\n".join(banner_config)
if line.endswith("^C"):
banner, end, _ = line.rpartition("^C")
line = banner.rstrip() + end
self._update_config_lines(line)
self._current_parents = self._current_parents[:-1]
try:
return next(self.generator_config)
except StopIteration:
return None
raise ValueError("Unable to parse banner end.")
|
def _build_nested_config(self, line):
BaseSpaceConfigParser
| def _build_nested_config(self, line: str) -> t.Optional[str]:
"""Handle building child config sections.
Args:
line: A configuration line from the configuration text.
Returns:
The next top-level configuration line in the configuration text or None when the last line of configuration
text is a nested configuration line.
Raises:
IndexError: When the number of parents does not match the expected deindent level.
"""
self._update_config_lines(line)
for line in self.generator_config:
if not line[0].isspace():
self._current_parents = ()
self.indent_level = 0
return line
spaces = self.get_leading_space_count(line)
if spaces == self.indent_level:
pass
elif spaces > self.indent_level:
previous_config = self.config_lines[-1]
self._current_parents += (previous_config.config_line,)
else:
self._current_parents = self._remove_parents(line, spaces)
if spaces != self.indent_level:
self.indent_level = spaces
if self.is_banner_start(line):
banner_line = self._build_banner(line)
if banner_line is None or not banner_line[0].isspace():
self._current_parents = ()
self.indent_level = 0
return banner_line
line = banner_line
self._update_config_lines(line)
return None
|
def _match_type_check(line, pattern, match_type):
BaseSpaceConfigParser
| @staticmethod
def _match_type_check(line: str, pattern: str, match_type: str) -> bool:
"""Checks pattern for exact match or regex."""
if match_type == "exact" and line == pattern:
return True
if match_type == "startswith" and line.startswith(pattern):
return True
if match_type == "endswith" and line.endswith(pattern):
return True
if match_type == "regex" and re.match(pattern, line):
return True
return False
|
def _remove_parents(self, line, current_spaces):
BaseSpaceConfigParser
| def _remove_parents(self, line: str, current_spaces: int) -> t.Tuple[str, ...]:
"""Remove parents from ``self._curent_parents`` based on indent levels.
Args:
config_line: A line of text in the config.
Returns:
The config lines parent config lines.
"""
deindent_level = 1
try:
previous_parent = self._current_parents[-1]
previous_indent = self.get_leading_space_count(previous_parent)
while previous_indent > current_spaces:
deindent_level += 1
previous_parent = self._current_parents[-deindent_level]
previous_indent = self.get_leading_space_count(previous_parent)
except IndexError:
raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n")
parents = self._current_parents[:-deindent_level] or (self._current_parents[0],)
return parents
|
def _update_config_lines(self, config_line):
ASAConfigParser
ASAConfigParser
| def _update_config_lines(self, config_line: str) -> None:
"""Add a ``ConfigLine`` object to ``self.config_lines``.
In addition to adding entries to config_lines, this also updates:
* self.same_line_children
* self.unique_config_lines
Args:
config_line (str): The current config line being evaluated.
Returns:
None
"""
super(ASAConfigParser, self)._update_config_lines(config_line)
entry = self.config_lines[-1]
if entry in self.unique_config_lines:
self.same_line_children.add(entry)
self.unique_config_lines.add(entry)
|
BaseSpaceConfigParser
| def _update_config_lines(self, config_line: str) -> None:
"""Add a ``ConfigLine`` object to ``self.config_lines``.
Args:
config_line: The current config line being evaluated.
Returns:
None
"""
entry = ConfigLine(config_line, self._current_parents)
self.config_lines.append(entry)
|
def build_config_relationship(self):
ASAConfigParser
ASAConfigParser
| def build_config_relationship(self) -> t.List[ConfigLine]:
r"""Parse text tree of config lines and their parents.
Examples:
>>> from netutils.config.parser import ASAConfigParser, ConfigLine
>>> config = '''
... interface Management0/0
... management-only
... nameif Management
... security-level 100
... ip address 10.1.1.10 255.255.255.0'''
>>> config_tree = ASAConfigParser(str(config))
>>> config_tree.build_config_relationship() == \
... [
... ConfigLine(config_line="interface Management0/0", parents=()),
... ConfigLine(config_line=" management-only", parents=("interface Management0/0",)),
... ConfigLine(config_line=" nameif Management", parents=("interface Management0/0",)),
... ConfigLine(config_line=" security-level 100", parents=("interface Management0/0",)),
... ConfigLine(config_line=" ip address 10.1.1.10 255.255.255.0", parents=("interface Management0/0",)),
... ]
True
"""
for line in self.generator_config:
if not line[0].isspace():
self._current_parents = ()
else:
previous_config = self.config_lines[-1]
self._current_parents = (previous_config.config_line,)
self.indent_level = self.get_leading_space_count(line)
if line is not None and line[0].isspace():
line = self._build_nested_config(line) # type: ignore
else:
self._current_parents = ()
if line is None:
break
self._update_config_lines(line)
return self.config_lines
|
BaseSpaceConfigParser
| def build_config_relationship(self) -> t.List[ConfigLine]:
r"""Parse text tree of config lines and their parents.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
>>> config = (
... "interface Ethernet1/1\n"
... " vlan 10\n"
... " no shutdown\n"
... "interface Ethernet1/2\n"
... " shutdown\n"
... )
>>> config_tree = BaseSpaceConfigParser(config)
>>> config_tree.build_config_relationship() == \
... [
... ConfigLine(config_line='interface Ethernet1/1', parents=()),
... ConfigLine(config_line=' vlan 10', parents=('interface Ethernet1/1',)),
... ConfigLine(config_line=' no shutdown', parents=('interface Ethernet1/1',)),
... ConfigLine(config_line='interface Ethernet1/2', parents=(),),
... ConfigLine(config_line=' shutdown', parents=('interface Ethernet1/2',))
... ]
True
"""
for line in self.generator_config:
if not line[0].isspace():
self._current_parents = ()
if self.is_banner_start(line):
line = self._build_banner(line) # type: ignore
else:
previous_config = self.config_lines[-1]
self._current_parents = (previous_config.config_line,)
self.indent_level = self.get_leading_space_count(line)
if not self.is_banner_start(line):
line = self._build_nested_config(line) # type: ignore
else:
line = self._build_banner(line) # type: ignore
if line is not None and line[0].isspace():
line = self._build_nested_config(line) # type: ignore
else:
self._current_parents = ()
if line is None:
break
elif self.is_banner_start(line):
line = self._build_banner(line) # type: ignore
self._update_config_lines(line)
return self.config_lines
|
BaseConfigParser
| def build_config_relationship(self) -> t.List[ConfigLine]:
"""Parse text tree of config lines and their parents."""
raise NotImplementedError()
|
def find_all_children(self, pattern, match_type=exact):
BaseSpaceConfigParser
| def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]:
"""Returns configuration part for a specific pattern not including parents.
Args:
pattern: pattern that describes parent.
match_type (optional): Exact or regex. Defaults to "exact".
Returns:
configuration under that parent pattern.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser
>>> config = '''
... router bgp 45000
... address-family ipv4 unicast
... neighbor 192.168.1.2 activate
... network 172.17.1.0 mask'''
>>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith")
>>> print(bgp_conf)
['router bgp 45000', ' address-family ipv4 unicast', ' neighbor 192.168.1.2 activate', ' network 172.17.1.0 mask']
"""
config = []
for cfg_line in self.build_config_relationship():
parents = cfg_line.parents[0] if cfg_line.parents else None
if (
parents
and self._match_type_check(parents, pattern, match_type)
or self._match_type_check(cfg_line.config_line, pattern, match_type)
):
config.append(cfg_line.config_line)
return config
|
def find_children_w_parents(self, parent_pattern, child_pattern, match_type=exact):
BaseSpaceConfigParser
| def find_children_w_parents(
self, parent_pattern: str, child_pattern: str, match_type: str = "exact"
) -> t.List[str]:
"""Returns configuration part for a specific pattern including parents and children.
Args:
parent_pattern: pattern that describes parent.
child_pattern: pattern that describes child.
match_type (optional): Exact or regex. Defaults to "exact".
Returns:
configuration under that parent pattern.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser
>>> config = '''
... router bgp 45000
... address-family ipv4 unicast
... neighbor 192.168.1.2 activate
... network 172.17.1.0 mask'''
>>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern=" address-family", match_type="regex")
>>> print(bgp_conf)
[' address-family ipv4 unicast', ' neighbor 192.168.1.2 activate', ' network 172.17.1.0 mask']
"""
config = []
potential_parents = [
elem.parents[0]
for elem in self.build_config_relationship()
if self._match_type_check(elem.config_line, child_pattern, match_type)
]
for cfg_line in self.build_config_relationship():
parents = cfg_line.parents[0] if cfg_line.parents else None
if parents in potential_parents and self._match_type_check(parents, parent_pattern, match_type):
config.append(cfg_line.config_line)
return config
|
def get_leading_space_count(config_line):
BaseSpaceConfigParser
| @staticmethod
def get_leading_space_count(config_line: str) -> int:
r"""Determine how many spaces the ``config_line`` is indented.
Args:
config_line: A line of text in the config.
Returns:
The number of leading spaces.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser
>>> config = '''interface GigabitEthernet1\n description link to ISP'''
>>> config_line = " description link to ISP"
>>> indent_level = BaseSpaceConfigParser(config).get_leading_space_count(config_line)
>>> indent_level
1
>>>
"""
return len(config_line) - len(config_line.lstrip())
|
def is_banner_end(self, line):
BaseSpaceConfigParser
| def is_banner_end(self, line: str) -> bool:
"""Determine if line ends the banner config.
Args:
line: The current config line in iteration.
Returns:
True if line ends banner, else False.
"""
if self.banner_end in line:
return True
return False
|
def is_banner_one_line(config_line):
CiscoConfigParser
| @staticmethod
def is_banner_one_line(config_line: str) -> bool:
"""Determine if all banner config is on one line."""
_, delimeter, banner = config_line.partition("^C")
# Based on NXOS configs, the banner delimeter is ignored until another char is used
banner_config_start = banner.lstrip(delimeter)
if delimeter not in banner_config_start:
return False
return True
|
def is_banner_start(self, line):
CiscoConfigParser
CiscoConfigParser
| def is_banner_start(self, line: str) -> bool:
"""Determine if the line starts a banner config."""
state = super(CiscoConfigParser, self).is_banner_start(line)
if state:
self.banner_end = line
return state
|
BaseSpaceConfigParser
| def is_banner_start(self, line: str) -> bool:
"""Determine if the line starts a banner config.
Args:
line: The current config line in iteration.
Returns:
True if line starts banner, else False.
"""
for banner_start in self.banner_start:
if line.lstrip().startswith(banner_start):
return True
return False
|
def is_comment(self, line):
BaseSpaceConfigParser
| def is_comment(self, line: str) -> bool:
"""Determine if line is a comment.
Args:
line: A config line from the device.
Returns:
True if line is a comment, else False.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser
>>> BaseSpaceConfigParser("interface Ethernet1/1").is_comment("interface Ethernet1/1")
False
>>> BaseSpaceConfigParser("!").is_comment("!")
True
>>>
"""
for comment_char in self.comment_chars:
if line.lstrip().startswith(comment_char):
return True
return False
|
ArubaConfigParser
from netutils.config.parser import ArubaConfigParser
Ancestors (MRO)
The Method Resolution Order is described below.
- ArubaConfigParser
- BaseSpaceConfigParser
- BaseConfigParser
Descendant Classes
Attributes
Key |
Value |
Defined in |
banner_end |
'!' |
ArubaConfigParser |
banner_start |
['banner', 'vacant-message'] |
BaseSpaceConfigParser |
banner_start |
['banner', 'vacant-message'] |
BaseConfigParser |
comment_chars |
['!'] |
ArubaConfigParser |
comment_chars |
['!'] |
BaseSpaceConfigParser |
comment_chars |
['!'] |
BaseConfigParser |
Methods
def __init__(self, config):
BaseSpaceConfigParser
BaseSpaceConfigParser
| def __init__(self, config: str):
"""Create ConfigParser Object.
Args:
config (str): The config text to parse.
"""
self._indent_level = 0
super(BaseSpaceConfigParser, self).__init__(config)
|
BaseConfigParser
| def __init__(self, config: str):
"""Create ConfigParser Object.
Args:
config: The config text to parse.
"""
self.config = config
self._config: t.Optional[str] = None
self._current_parents: t.Tuple[str, ...] = ()
self.generator_config = (line for line in self.config_lines_only.splitlines())
self.config_lines: t.List[ConfigLine] = []
self.build_config_relationship()
|
def _build_banner(self, config_line):
ArubaConfigParser
ArubaConfigParser
| def _build_banner(self, config_line: str) -> t.Optional[str]:
"""Handle banner config lines.
Args:
config_line: The start of the banner config.
Returns:
The next configuration line in the configuration text or None when banner end is the end of the config text.
Raises:
ValueError: When the parser is unable to identify the End of the Banner.
"""
self._update_config_lines(config_line)
self._current_parents += (config_line,)
banner_config = []
for line in self.generator_config:
if not self.is_banner_end(line):
banner_config.append(line)
else:
banner_config.append(line)
line = "\n".join(banner_config)
self._update_config_lines(line)
self._current_parents = self._current_parents[:-1]
try:
return next(self.generator_config)
except StopIteration:
return None
raise ValueError("Unable to parse banner end.")
|
BaseSpaceConfigParser
| def _build_banner(self, config_line: str) -> t.Optional[str]:
"""Handle banner config lines.
Args:
config_line: The start of the banner config.
Returns:
The next configuration line in the configuration text or None
Raises:
ValueError: When the parser is unable to identify the end of the Banner.
"""
self._update_config_lines(config_line)
self._current_parents += (config_line,)
banner_config = []
for line in self.generator_config:
if not self.is_banner_end(line):
banner_config.append(line)
else:
line = normalise_delimiter_caret_c(self.banner_end, line)
banner_config.append(line)
line = "\n".join(banner_config)
if line.endswith("^C"):
banner, end, _ = line.rpartition("^C")
line = banner.rstrip() + end
self._update_config_lines(line)
self._current_parents = self._current_parents[:-1]
try:
return next(self.generator_config)
except StopIteration:
return None
raise ValueError("Unable to parse banner end.")
|
def _build_nested_config(self, line):
BaseSpaceConfigParser
| def _build_nested_config(self, line: str) -> t.Optional[str]:
"""Handle building child config sections.
Args:
line: A configuration line from the configuration text.
Returns:
The next top-level configuration line in the configuration text or None when the last line of configuration
text is a nested configuration line.
Raises:
IndexError: When the number of parents does not match the expected deindent level.
"""
self._update_config_lines(line)
for line in self.generator_config:
if not line[0].isspace():
self._current_parents = ()
self.indent_level = 0
return line
spaces = self.get_leading_space_count(line)
if spaces == self.indent_level:
pass
elif spaces > self.indent_level:
previous_config = self.config_lines[-1]
self._current_parents += (previous_config.config_line,)
else:
self._current_parents = self._remove_parents(line, spaces)
if spaces != self.indent_level:
self.indent_level = spaces
if self.is_banner_start(line):
banner_line = self._build_banner(line)
if banner_line is None or not banner_line[0].isspace():
self._current_parents = ()
self.indent_level = 0
return banner_line
line = banner_line
self._update_config_lines(line)
return None
|
def _match_type_check(line, pattern, match_type):
BaseSpaceConfigParser
| @staticmethod
def _match_type_check(line: str, pattern: str, match_type: str) -> bool:
"""Checks pattern for exact match or regex."""
if match_type == "exact" and line == pattern:
return True
if match_type == "startswith" and line.startswith(pattern):
return True
if match_type == "endswith" and line.endswith(pattern):
return True
if match_type == "regex" and re.match(pattern, line):
return True
return False
|
def _parse_out_comments(self, config):
ArubaConfigParser
| def _parse_out_comments(self, config: str) -> str:
"""Remove comments while retaining the banner end.
Args:
config (str): full config as a string.
Returns:
The non-comment lines from ``config``.
"""
# Aruba AOS-CX uses "!" as both comments and the banner delimiter.
# Even if another delimiter is used while creating the banner, show run changes the delimiter to use "!".
# We need to remove comments while retaining the banner delimiter.
config_lines = []
banner_started = False
banner_ended = False
for line in config.splitlines():
if self.is_banner_start(line):
banner_started = True
banner_ended = False
if line and banner_started and not banner_ended:
config_lines.append(line.rstrip())
if line.lstrip().startswith(self.banner_end):
banner_ended = True
banner_started = False
else:
if line and not self.is_comment(line):
config_lines.append(line.rstrip())
full_config = "\n".join(config_lines)
return full_config
|
def _remove_parents(self, line, current_spaces):
BaseSpaceConfigParser
| def _remove_parents(self, line: str, current_spaces: int) -> t.Tuple[str, ...]:
"""Remove parents from ``self._curent_parents`` based on indent levels.
Args:
config_line: A line of text in the config.
Returns:
The config lines parent config lines.
"""
deindent_level = 1
try:
previous_parent = self._current_parents[-1]
previous_indent = self.get_leading_space_count(previous_parent)
while previous_indent > current_spaces:
deindent_level += 1
previous_parent = self._current_parents[-deindent_level]
previous_indent = self.get_leading_space_count(previous_parent)
except IndexError:
raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n")
parents = self._current_parents[:-deindent_level] or (self._current_parents[0],)
return parents
|
def _update_config_lines(self, config_line):
BaseSpaceConfigParser
| def _update_config_lines(self, config_line: str) -> None:
"""Add a ``ConfigLine`` object to ``self.config_lines``.
Args:
config_line: The current config line being evaluated.
Returns:
None
"""
entry = ConfigLine(config_line, self._current_parents)
self.config_lines.append(entry)
|
def build_config_relationship(self):
BaseSpaceConfigParser
BaseSpaceConfigParser
| def build_config_relationship(self) -> t.List[ConfigLine]:
r"""Parse text tree of config lines and their parents.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
>>> config = (
... "interface Ethernet1/1\n"
... " vlan 10\n"
... " no shutdown\n"
... "interface Ethernet1/2\n"
... " shutdown\n"
... )
>>> config_tree = BaseSpaceConfigParser(config)
>>> config_tree.build_config_relationship() == \
... [
... ConfigLine(config_line='interface Ethernet1/1', parents=()),
... ConfigLine(config_line=' vlan 10', parents=('interface Ethernet1/1',)),
... ConfigLine(config_line=' no shutdown', parents=('interface Ethernet1/1',)),
... ConfigLine(config_line='interface Ethernet1/2', parents=(),),
... ConfigLine(config_line=' shutdown', parents=('interface Ethernet1/2',))
... ]
True
"""
for line in self.generator_config:
if not line[0].isspace():
self._current_parents = ()
if self.is_banner_start(line):
line = self._build_banner(line) # type: ignore
else:
previous_config = self.config_lines[-1]
self._current_parents = (previous_config.config_line,)
self.indent_level = self.get_leading_space_count(line)
if not self.is_banner_start(line):
line = self._build_nested_config(line) # type: ignore
else:
line = self._build_banner(line) # type: ignore
if line is not None and line[0].isspace():
line = self._build_nested_config(line) # type: ignore
else:
self._current_parents = ()
if line is None:
break
elif self.is_banner_start(line):
line = self._build_banner(line) # type: ignore
self._update_config_lines(line)
return self.config_lines
|
BaseConfigParser
| def build_config_relationship(self) -> t.List[ConfigLine]:
"""Parse text tree of config lines and their parents."""
raise NotImplementedError()
|
def find_all_children(self, pattern, match_type=exact):
BaseSpaceConfigParser
| def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]:
"""Returns configuration part for a specific pattern not including parents.
Args:
pattern: pattern that describes parent.
match_type (optional): Exact or regex. Defaults to "exact".
Returns:
configuration under that parent pattern.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser
>>> config = '''
... router bgp 45000
... address-family ipv4 unicast
... neighbor 192.168.1.2 activate
... network 172.17.1.0 mask'''
>>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith")
>>> print(bgp_conf)
['router bgp 45000', ' address-family ipv4 unicast', ' neighbor 192.168.1.2 activate', ' network 172.17.1.0 mask']
"""
config = []
for cfg_line in self.build_config_relationship():
parents = cfg_line.parents[0] if cfg_line.parents else None
if (
parents
and self._match_type_check(parents, pattern, match_type)
or self._match_type_check(cfg_line.config_line, pattern, match_type)
):
config.append(cfg_line.config_line)
return config
|
def find_children_w_parents(self, parent_pattern, child_pattern, match_type=exact):
BaseSpaceConfigParser
| def find_children_w_parents(
self, parent_pattern: str, child_pattern: str, match_type: str = "exact"
) -> t.List[str]:
"""Returns configuration part for a specific pattern including parents and children.
Args:
parent_pattern: pattern that describes parent.
child_pattern: pattern that describes child.
match_type (optional): Exact or regex. Defaults to "exact".
Returns:
configuration under that parent pattern.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser
>>> config = '''
... router bgp 45000
... address-family ipv4 unicast
... neighbor 192.168.1.2 activate
... network 172.17.1.0 mask'''
>>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern=" address-family", match_type="regex")
>>> print(bgp_conf)
[' address-family ipv4 unicast', ' neighbor 192.168.1.2 activate', ' network 172.17.1.0 mask']
"""
config = []
potential_parents = [
elem.parents[0]
for elem in self.build_config_relationship()
if self._match_type_check(elem.config_line, child_pattern, match_type)
]
for cfg_line in self.build_config_relationship():
parents = cfg_line.parents[0] if cfg_line.parents else None
if parents in potential_parents and self._match_type_check(parents, parent_pattern, match_type):
config.append(cfg_line.config_line)
return config
|
def get_leading_space_count(config_line):
BaseSpaceConfigParser
| @staticmethod
def get_leading_space_count(config_line: str) -> int:
r"""Determine how many spaces the ``config_line`` is indented.
Args:
config_line: A line of text in the config.
Returns:
The number of leading spaces.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser
>>> config = '''interface GigabitEthernet1\n description link to ISP'''
>>> config_line = " description link to ISP"
>>> indent_level = BaseSpaceConfigParser(config).get_leading_space_count(config_line)
>>> indent_level
1
>>>
"""
return len(config_line) - len(config_line.lstrip())
|
def is_banner_end(self, line):
BaseSpaceConfigParser
| def is_banner_end(self, line: str) -> bool:
"""Determine if line ends the banner config.
Args:
line: The current config line in iteration.
Returns:
True if line ends banner, else False.
"""
if self.banner_end in line:
return True
return False
|
def is_banner_start(self, line):
BaseSpaceConfigParser
| def is_banner_start(self, line: str) -> bool:
"""Determine if the line starts a banner config.
Args:
line: The current config line in iteration.
Returns:
True if line starts banner, else False.
"""
for banner_start in self.banner_start:
if line.lstrip().startswith(banner_start):
return True
return False
|
def is_comment(self, line):
BaseSpaceConfigParser
| def is_comment(self, line: str) -> bool:
"""Determine if line is a comment.
Args:
line: A config line from the device.
Returns:
True if line is a comment, else False.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser
>>> BaseSpaceConfigParser("interface Ethernet1/1").is_comment("interface Ethernet1/1")
False
>>> BaseSpaceConfigParser("!").is_comment("!")
True
>>>
"""
for comment_char in self.comment_chars:
if line.lstrip().startswith(comment_char):
return True
return False
|
BaseBraceConfigParser
from netutils.config.parser import BaseBraceConfigParser
Ancestors (MRO)
The Method Resolution Order is described below.
- BaseBraceConfigParser
- BaseConfigParser
Descendant Classes
The below Classes rely on: BaseBraceConfigParser
.
Attributes
Key |
Value |
Defined in |
banner_start |
['banner', 'vacant-message'] |
BaseConfigParser |
comment_chars |
['!'] |
BaseConfigParser |
Methods
def __init__(self, config):
BaseConfigParser
| def __init__(self, config: str):
"""Create ConfigParser Object.
Args:
config: The config text to parse.
"""
self.config = config
self._config: t.Optional[str] = None
self._current_parents: t.Tuple[str, ...] = ()
self.generator_config = (line for line in self.config_lines_only.splitlines())
self.config_lines: t.List[ConfigLine] = []
self.build_config_relationship()
|
def _build_multiline_config(self, delimiter):
BaseBraceConfigParser
| def _build_multiline_config(self, delimiter: str) -> t.Optional[ConfigLine]:
r"""Build config sections between characters demarcating multiline strings.
Args:
delimiter: The text to look for to end multiline config.
Returns:
The multiline string text that was added to ``self.config_lines``.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
>>> config = (
... 'sys syslog {\n'
... ' include "\n'
... ' filter f_local0 {\n'
... ' facility(local0) and not match(\"DEBUG");\n'
... ' "\n'
... '}'
... )
>>> parser = BaseBraceConfigParser(config)
>>> # ' include "' started a multiline config
>>> parser.config_lines == \
... [
... ConfigLine(config_line='sys syslog {', parents=()),
... ConfigLine(config_line=' include "', parents=('sys syslog {',)),
... ConfigLine(config_line=' filter f_local0 {', parents=('sys syslog {',)),
... ConfigLine(config_line=' facility(local0) and not match("DEBUG");', parents=('sys syslog {', ' filter f_local0 {')),
... ConfigLine(config_line=' "', parents=('sys syslog {', ' filter f_local0 {')),
... ConfigLine(config_line='}', parents=('sys syslog {', ' filter f_local0 {'))
... ]
True
"""
multiline_config = []
for line in self.generator_config:
multiline_config.append(line)
if line.lstrip() == delimiter:
multiline_entry = ConfigLine("\n".join(multiline_config), self._current_parents)
self.config_lines.append(multiline_entry)
self._current_parents = self._current_parents[:-1]
return multiline_entry
return None
|
def build_config_relationship(self):
BaseBraceConfigParser
BaseBraceConfigParser
| def build_config_relationship(self) -> t.List[ConfigLine]:
r"""Parse text tree of config lines and their parents.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
>>> config = '''auth ldap system-auth {
... port ldaps
... servers { ams-lda01.ntc.com }
... }
... auth partition Common {
... description "Repository for system objects and shared objects."
... }
... auth password-policy { }'''
>>> config_tree = BaseBraceConfigParser(config)
>>> config_tree.build_config_relationship() == \
... [
... ConfigLine(config_line='auth ldap system-auth {', parents=()),
... ConfigLine(config_line=' port ldaps', parents=('auth ldap system-auth {',)),
... ConfigLine(config_line=' servers { ams-lda01.ntc.com }', parents=('auth ldap system-auth {',)),
... ConfigLine(config_line=' }', parents=('auth ldap system-auth {',)),
... ConfigLine(config_line=' auth partition Common {', parents=()),
... ConfigLine(config_line=' description "Repository for system objects and shared objects."', parents=(' auth partition Common {',)), ConfigLine(config_line=' }', parents=(' auth partition Common {',)),
... ConfigLine(config_line=' auth password-policy { }', parents=())
... ]
True
"""
for line in self.generator_config:
self.config_lines.append(ConfigLine(line, self._current_parents))
line_end = line[-1]
if line.endswith("{"):
self._current_parents += (line,)
elif line.lstrip() == "}":
self._current_parents = self._current_parents[:-1]
elif line_end in self.multiline_delimiters and line.count(line_end) == 1:
self._current_parents += (line,)
self._build_multiline_config(line_end)
return self.config_lines
|
BaseConfigParser
| def build_config_relationship(self) -> t.List[ConfigLine]:
"""Parse text tree of config lines and their parents."""
raise NotImplementedError()
|
BaseConfigParser
from netutils.config.parser import BaseConfigParser
Ancestors (MRO)
The Method Resolution Order is described below.
- BaseConfigParser
Descendant Classes
The below Classes rely on: BaseConfigParser
.
Attributes
Key |
Value |
Defined in |
banner_start |
['banner', 'vacant-message'] |
BaseConfigParser |
comment_chars |
['!'] |
BaseConfigParser |
Methods
def __init__(self, config):
BaseConfigParser
| def __init__(self, config: str):
"""Create ConfigParser Object.
Args:
config: The config text to parse.
"""
self.config = config
self._config: t.Optional[str] = None
self._current_parents: t.Tuple[str, ...] = ()
self.generator_config = (line for line in self.config_lines_only.splitlines())
self.config_lines: t.List[ConfigLine] = []
self.build_config_relationship()
|
def build_config_relationship(self):
BaseConfigParser
| def build_config_relationship(self) -> t.List[ConfigLine]:
"""Parse text tree of config lines and their parents."""
raise NotImplementedError()
|
BaseSpaceConfigParser
from netutils.config.parser import BaseSpaceConfigParser
Ancestors (MRO)
The Method Resolution Order is described below.
- BaseSpaceConfigParser
- BaseConfigParser
Descendant Classes
The below Classes rely on: BaseSpaceConfigParser
.
Attributes
Key |
Value |
Defined in |
banner_start |
['banner', 'vacant-message'] |
BaseSpaceConfigParser |
banner_start |
['banner', 'vacant-message'] |
BaseConfigParser |
comment_chars |
['!'] |
BaseSpaceConfigParser |
comment_chars |
['!'] |
BaseConfigParser |
Methods
def __init__(self, config):
BaseSpaceConfigParser
BaseSpaceConfigParser
| def __init__(self, config: str):
"""Create ConfigParser Object.
Args:
config (str): The config text to parse.
"""
self._indent_level = 0
super(BaseSpaceConfigParser, self).__init__(config)
|
BaseConfigParser
| def __init__(self, config: str):
"""Create ConfigParser Object.
Args:
config: The config text to parse.
"""
self.config = config
self._config: t.Optional[str] = None
self._current_parents: t.Tuple[str, ...] = ()
self.generator_config = (line for line in self.config_lines_only.splitlines())
self.config_lines: t.List[ConfigLine] = []
self.build_config_relationship()
|
def _build_banner(self, config_line):
BaseSpaceConfigParser
| def _build_banner(self, config_line: str) -> t.Optional[str]:
"""Handle banner config lines.
Args:
config_line: The start of the banner config.
Returns:
The next configuration line in the configuration text or None
Raises:
ValueError: When the parser is unable to identify the end of the Banner.
"""
self._update_config_lines(config_line)
self._current_parents += (config_line,)
banner_config = []
for line in self.generator_config:
if not self.is_banner_end(line):
banner_config.append(line)
else:
line = normalise_delimiter_caret_c(self.banner_end, line)
banner_config.append(line)
line = "\n".join(banner_config)
if line.endswith("^C"):
banner, end, _ = line.rpartition("^C")
line = banner.rstrip() + end
self._update_config_lines(line)
self._current_parents = self._current_parents[:-1]
try:
return next(self.generator_config)
except StopIteration:
return None
raise ValueError("Unable to parse banner end.")
|
def _build_nested_config(self, line):
BaseSpaceConfigParser
| def _build_nested_config(self, line: str) -> t.Optional[str]:
"""Handle building child config sections.
Args:
line: A configuration line from the configuration text.
Returns:
The next top-level configuration line in the configuration text or None when the last line of configuration
text is a nested configuration line.
Raises:
IndexError: When the number of parents does not match the expected deindent level.
"""
self._update_config_lines(line)
for line in self.generator_config:
if not line[0].isspace():
self._current_parents = ()
self.indent_level = 0
return line
spaces = self.get_leading_space_count(line)
if spaces == self.indent_level:
pass
elif spaces > self.indent_level:
previous_config = self.config_lines[-1]
self._current_parents += (previous_config.config_line,)
else:
self._current_parents = self._remove_parents(line, spaces)
if spaces != self.indent_level:
self.indent_level = spaces
if self.is_banner_start(line):
banner_line = self._build_banner(line)
if banner_line is None or not banner_line[0].isspace():
self._current_parents = ()
self.indent_level = 0
return banner_line
line = banner_line
self._update_config_lines(line)
return None
|
def _match_type_check(line, pattern, match_type):
BaseSpaceConfigParser
| @staticmethod
def _match_type_check(line: str, pattern: str, match_type: str) -> bool:
"""Checks pattern for exact match or regex."""
if match_type == "exact" and line == pattern:
return True
if match_type == "startswith" and line.startswith(pattern):
return True
if match_type == "endswith" and line.endswith(pattern):
return True
if match_type == "regex" and re.match(pattern, line):
return True
return False
|
def _remove_parents(self, line, current_spaces):
BaseSpaceConfigParser
| def _remove_parents(self, line: str, current_spaces: int) -> t.Tuple[str, ...]:
"""Remove parents from ``self._curent_parents`` based on indent levels.
Args:
config_line: A line of text in the config.
Returns:
The config lines parent config lines.
"""
deindent_level = 1
try:
previous_parent = self._current_parents[-1]
previous_indent = self.get_leading_space_count(previous_parent)
while previous_indent > current_spaces:
deindent_level += 1
previous_parent = self._current_parents[-deindent_level]
previous_indent = self.get_leading_space_count(previous_parent)
except IndexError:
raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n")
parents = self._current_parents[:-deindent_level] or (self._current_parents[0],)
return parents
|
def _update_config_lines(self, config_line):
BaseSpaceConfigParser
| def _update_config_lines(self, config_line: str) -> None:
"""Add a ``ConfigLine`` object to ``self.config_lines``.
Args:
config_line: The current config line being evaluated.
Returns:
None
"""
entry = ConfigLine(config_line, self._current_parents)
self.config_lines.append(entry)
|
def build_config_relationship(self):
BaseSpaceConfigParser
BaseSpaceConfigParser
| def build_config_relationship(self) -> t.List[ConfigLine]:
r"""Parse text tree of config lines and their parents.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
>>> config = (
... "interface Ethernet1/1\n"
... " vlan 10\n"
... " no shutdown\n"
... "interface Ethernet1/2\n"
... " shutdown\n"
... )
>>> config_tree = BaseSpaceConfigParser(config)
>>> config_tree.build_config_relationship() == \
... [
... ConfigLine(config_line='interface Ethernet1/1', parents=()),
... ConfigLine(config_line=' vlan 10', parents=('interface Ethernet1/1',)),
... ConfigLine(config_line=' no shutdown', parents=('interface Ethernet1/1',)),
... ConfigLine(config_line='interface Ethernet1/2', parents=(),),
... ConfigLine(config_line=' shutdown', parents=('interface Ethernet1/2',))
... ]
True
"""
for line in self.generator_config:
if not line[0].isspace():
self._current_parents = ()
if self.is_banner_start(line):
line = self._build_banner(line) # type: ignore
else:
previous_config = self.config_lines[-1]
self._current_parents = (previous_config.config_line,)
self.indent_level = self.get_leading_space_count(line)
if not self.is_banner_start(line):
line = self._build_nested_config(line) # type: ignore
else:
line = self._build_banner(line) # type: ignore
if line is not None and line[0].isspace():
line = self._build_nested_config(line) # type: ignore
else:
self._current_parents = ()
if line is None:
break
elif self.is_banner_start(line):
line = self._build_banner(line) # type: ignore
self._update_config_lines(line)
return self.config_lines
|
BaseConfigParser
| def build_config_relationship(self) -> t.List[ConfigLine]:
"""Parse text tree of config lines and their parents."""
raise NotImplementedError()
|
def find_all_children(self, pattern, match_type=exact):
BaseSpaceConfigParser
| def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]:
"""Returns configuration part for a specific pattern not including parents.
Args:
pattern: pattern that describes parent.
match_type (optional): Exact or regex. Defaults to "exact".
Returns:
configuration under that parent pattern.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser
>>> config = '''
... router bgp 45000
... address-family ipv4 unicast
... neighbor 192.168.1.2 activate
... network 172.17.1.0 mask'''
>>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith")
>>> print(bgp_conf)
['router bgp 45000', ' address-family ipv4 unicast', ' neighbor 192.168.1.2 activate', ' network 172.17.1.0 mask']
"""
config = []
for cfg_line in self.build_config_relationship():
parents = cfg_line.parents[0] if cfg_line.parents else None
if (
parents
and self._match_type_check(parents, pattern, match_type)
or self._match_type_check(cfg_line.config_line, pattern, match_type)
):
config.append(cfg_line.config_line)
return config
|
def find_children_w_parents(self, parent_pattern, child_pattern, match_type=exact):
BaseSpaceConfigParser
| def find_children_w_parents(
self, parent_pattern: str, child_pattern: str, match_type: str = "exact"
) -> t.List[str]:
"""Returns configuration part for a specific pattern including parents and children.
Args:
parent_pattern: pattern that describes parent.
child_pattern: pattern that describes child.
match_type (optional): Exact or regex. Defaults to "exact".
Returns:
configuration under that parent pattern.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser
>>> config = '''
... router bgp 45000
... address-family ipv4 unicast
... neighbor 192.168.1.2 activate
... network 172.17.1.0 mask'''
>>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern=" address-family", match_type="regex")
>>> print(bgp_conf)
[' address-family ipv4 unicast', ' neighbor 192.168.1.2 activate', ' network 172.17.1.0 mask']
"""
config = []
potential_parents = [
elem.parents[0]
for elem in self.build_config_relationship()
if self._match_type_check(elem.config_line, child_pattern, match_type)
]
for cfg_line in self.build_config_relationship():
parents = cfg_line.parents[0] if cfg_line.parents else None
if parents in potential_parents and self._match_type_check(parents, parent_pattern, match_type):
config.append(cfg_line.config_line)
return config
|
def get_leading_space_count(config_line):
BaseSpaceConfigParser
| @staticmethod
def get_leading_space_count(config_line: str) -> int:
r"""Determine how many spaces the ``config_line`` is indented.
Args:
config_line: A line of text in the config.
Returns:
The number of leading spaces.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser
>>> config = '''interface GigabitEthernet1\n description link to ISP'''
>>> config_line = " description link to ISP"
>>> indent_level = BaseSpaceConfigParser(config).get_leading_space_count(config_line)
>>> indent_level
1
>>>
"""
return len(config_line) - len(config_line.lstrip())
|
def is_banner_end(self, line):
BaseSpaceConfigParser
| def is_banner_end(self, line: str) -> bool:
"""Determine if line ends the banner config.
Args:
line: The current config line in iteration.
Returns:
True if line ends banner, else False.
"""
if self.banner_end in line:
return True
return False
|
def is_banner_start(self, line):
BaseSpaceConfigParser
| def is_banner_start(self, line: str) -> bool:
"""Determine if the line starts a banner config.
Args:
line: The current config line in iteration.
Returns:
True if line starts banner, else False.
"""
for banner_start in self.banner_start:
if line.lstrip().startswith(banner_start):
return True
return False
|
def is_comment(self, line):
BaseSpaceConfigParser
| def is_comment(self, line: str) -> bool:
"""Determine if line is a comment.
Args:
line: A config line from the device.
Returns:
True if line is a comment, else False.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser
>>> BaseSpaceConfigParser("interface Ethernet1/1").is_comment("interface Ethernet1/1")
False
>>> BaseSpaceConfigParser("!").is_comment("!")
True
>>>
"""
for comment_char in self.comment_chars:
if line.lstrip().startswith(comment_char):
return True
return False
|
CiscoConfigParser
from netutils.config.parser import CiscoConfigParser
Ancestors (MRO)
The Method Resolution Order is described below.
- CiscoConfigParser
- BaseSpaceConfigParser
- BaseConfigParser
Descendant Classes
The below Classes rely on: CiscoConfigParser
.
Attributes
Key |
Value |
Defined in |
banner_start |
['banner', 'vacant-message'] |
BaseSpaceConfigParser |
banner_start |
['banner', 'vacant-message'] |
BaseConfigParser |
comment_chars |
['!'] |
BaseSpaceConfigParser |
comment_chars |
['!'] |
BaseConfigParser |
regex_banner |
re.compile('^(banner\\s+\\S+|\\s*vacant-message)\\s+(?P<banner_delimiter>\\^C|.)') |
CiscoConfigParser |
Methods
def __init__(self, config):
CiscoConfigParser
CiscoConfigParser
| def __init__(self, config: str):
"""Create ConfigParser Object.
Args:
config (str): The config text to parse.
"""
self._banner_end: t.Optional[str] = None
super(CiscoConfigParser, self).__init__(config)
|
BaseSpaceConfigParser
| def __init__(self, config: str):
"""Create ConfigParser Object.
Args:
config (str): The config text to parse.
"""
self._indent_level = 0
super(BaseSpaceConfigParser, self).__init__(config)
|
BaseConfigParser
| def __init__(self, config: str):
"""Create ConfigParser Object.
Args:
config: The config text to parse.
"""
self.config = config
self._config: t.Optional[str] = None
self._current_parents: t.Tuple[str, ...] = ()
self.generator_config = (line for line in self.config_lines_only.splitlines())
self.config_lines: t.List[ConfigLine] = []
self.build_config_relationship()
|
def _build_banner(self, config_line):
CiscoConfigParser
CiscoConfigParser
| def _build_banner(self, config_line: str) -> t.Optional[str]:
"""Handle banner config lines.
Args:
config_line: The start of the banner config.
Returns:
The next configuration line in the configuration text or None when banner end is the end of the config text.
Raises:
ValueError: When the parser is unable to identify the End of the Banner.
"""
if self.is_banner_one_line(config_line):
self._update_config_lines(config_line)
try:
return next(self.generator_config)
except StopIteration:
return None
return super(CiscoConfigParser, self)._build_banner(config_line)
|
BaseSpaceConfigParser
| def _build_banner(self, config_line: str) -> t.Optional[str]:
"""Handle banner config lines.
Args:
config_line: The start of the banner config.
Returns:
The next configuration line in the configuration text or None
Raises:
ValueError: When the parser is unable to identify the end of the Banner.
"""
self._update_config_lines(config_line)
self._current_parents += (config_line,)
banner_config = []
for line in self.generator_config:
if not self.is_banner_end(line):
banner_config.append(line)
else:
line = normalise_delimiter_caret_c(self.banner_end, line)
banner_config.append(line)
line = "\n".join(banner_config)
if line.endswith("^C"):
banner, end, _ = line.rpartition("^C")
line = banner.rstrip() + end
self._update_config_lines(line)
self._current_parents = self._current_parents[:-1]
try:
return next(self.generator_config)
except StopIteration:
return None
raise ValueError("Unable to parse banner end.")
|
def _build_nested_config(self, line):
BaseSpaceConfigParser
| def _build_nested_config(self, line: str) -> t.Optional[str]:
"""Handle building child config sections.
Args:
line: A configuration line from the configuration text.
Returns:
The next top-level configuration line in the configuration text or None when the last line of configuration
text is a nested configuration line.
Raises:
IndexError: When the number of parents does not match the expected deindent level.
"""
self._update_config_lines(line)
for line in self.generator_config:
if not line[0].isspace():
self._current_parents = ()
self.indent_level = 0
return line
spaces = self.get_leading_space_count(line)
if spaces == self.indent_level:
pass
elif spaces > self.indent_level:
previous_config = self.config_lines[-1]
self._current_parents += (previous_config.config_line,)
else:
self._current_parents = self._remove_parents(line, spaces)
if spaces != self.indent_level:
self.indent_level = spaces
if self.is_banner_start(line):
banner_line = self._build_banner(line)
if banner_line is None or not banner_line[0].isspace():
self._current_parents = ()
self.indent_level = 0
return banner_line
line = banner_line
self._update_config_lines(line)
return None
|
def _match_type_check(line, pattern, match_type):
BaseSpaceConfigParser
| @staticmethod
def _match_type_check(line: str, pattern: str, match_type: str) -> bool:
"""Checks pattern for exact match or regex."""
if match_type == "exact" and line == pattern:
return True
if match_type == "startswith" and line.startswith(pattern):
return True
if match_type == "endswith" and line.endswith(pattern):
return True
if match_type == "regex" and re.match(pattern, line):
return True
return False
|
def _remove_parents(self, line, current_spaces):
BaseSpaceConfigParser
| def _remove_parents(self, line: str, current_spaces: int) -> t.Tuple[str, ...]:
"""Remove parents from ``self._curent_parents`` based on indent levels.
Args:
config_line: A line of text in the config.
Returns:
The config lines parent config lines.
"""
deindent_level = 1
try:
previous_parent = self._current_parents[-1]
previous_indent = self.get_leading_space_count(previous_parent)
while previous_indent > current_spaces:
deindent_level += 1
previous_parent = self._current_parents[-deindent_level]
previous_indent = self.get_leading_space_count(previous_parent)
except IndexError:
raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n")
parents = self._current_parents[:-deindent_level] or (self._current_parents[0],)
return parents
|
def _update_config_lines(self, config_line):
BaseSpaceConfigParser
| def _update_config_lines(self, config_line: str) -> None:
"""Add a ``ConfigLine`` object to ``self.config_lines``.
Args:
config_line: The current config line being evaluated.
Returns:
None
"""
entry = ConfigLine(config_line, self._current_parents)
self.config_lines.append(entry)
|
def build_config_relationship(self):
BaseSpaceConfigParser
BaseSpaceConfigParser
| def build_config_relationship(self) -> t.List[ConfigLine]:
r"""Parse text tree of config lines and their parents.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
>>> config = (
... "interface Ethernet1/1\n"
... " vlan 10\n"
... " no shutdown\n"
... "interface Ethernet1/2\n"
... " shutdown\n"
... )
>>> config_tree = BaseSpaceConfigParser(config)
>>> config_tree.build_config_relationship() == \
... [
... ConfigLine(config_line='interface Ethernet1/1', parents=()),
... ConfigLine(config_line=' vlan 10', parents=('interface Ethernet1/1',)),
... ConfigLine(config_line=' no shutdown', parents=('interface Ethernet1/1',)),
... ConfigLine(config_line='interface Ethernet1/2', parents=(),),
... ConfigLine(config_line=' shutdown', parents=('interface Ethernet1/2',))
... ]
True
"""
for line in self.generator_config:
if not line[0].isspace():
self._current_parents = ()
if self.is_banner_start(line):
line = self._build_banner(line) # type: ignore
else:
previous_config = self.config_lines[-1]
self._current_parents = (previous_config.config_line,)
self.indent_level = self.get_leading_space_count(line)
if not self.is_banner_start(line):
line = self._build_nested_config(line) # type: ignore
else:
line = self._build_banner(line) # type: ignore
if line is not None and line[0].isspace():
line = self._build_nested_config(line) # type: ignore
else:
self._current_parents = ()
if line is None:
break
elif self.is_banner_start(line):
line = self._build_banner(line) # type: ignore
self._update_config_lines(line)
return self.config_lines
|
BaseConfigParser
| def build_config_relationship(self) -> t.List[ConfigLine]:
"""Parse text tree of config lines and their parents."""
raise NotImplementedError()
|
def find_all_children(self, pattern, match_type=exact):
BaseSpaceConfigParser
| def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]:
"""Returns configuration part for a specific pattern not including parents.
Args:
pattern: pattern that describes parent.
match_type (optional): Exact or regex. Defaults to "exact".
Returns:
configuration under that parent pattern.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser
>>> config = '''
... router bgp 45000
... address-family ipv4 unicast
... neighbor 192.168.1.2 activate
... network 172.17.1.0 mask'''
>>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith")
>>> print(bgp_conf)
['router bgp 45000', ' address-family ipv4 unicast', ' neighbor 192.168.1.2 activate', ' network 172.17.1.0 mask']
"""
config = []
for cfg_line in self.build_config_relationship():
parents = cfg_line.parents[0] if cfg_line.parents else None
if (
parents
and self._match_type_check(parents, pattern, match_type)
or self._match_type_check(cfg_line.config_line, pattern, match_type)
):
config.append(cfg_line.config_line)
return config
|
def find_children_w_parents(self, parent_pattern, child_pattern, match_type=exact):
BaseSpaceConfigParser
| def find_children_w_parents(
self, parent_pattern: str, child_pattern: str, match_type: str = "exact"
) -> t.List[str]:
"""Returns configuration part for a specific pattern including parents and children.
Args:
parent_pattern: pattern that describes parent.
child_pattern: pattern that describes child.
match_type (optional): Exact or regex. Defaults to "exact".
Returns:
configuration under that parent pattern.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser
>>> config = '''
... router bgp 45000
... address-family ipv4 unicast
... neighbor 192.168.1.2 activate
... network 172.17.1.0 mask'''
>>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern=" address-family", match_type="regex")
>>> print(bgp_conf)
[' address-family ipv4 unicast', ' neighbor 192.168.1.2 activate', ' network 172.17.1.0 mask']
"""
config = []
potential_parents = [
elem.parents[0]
for elem in self.build_config_relationship()
if self._match_type_check(elem.config_line, child_pattern, match_type)
]
for cfg_line in self.build_config_relationship():
parents = cfg_line.parents[0] if cfg_line.parents else None
if parents in potential_parents and self._match_type_check(parents, parent_pattern, match_type):
config.append(cfg_line.config_line)
return config
|
def get_leading_space_count(config_line):
BaseSpaceConfigParser
| @staticmethod
def get_leading_space_count(config_line: str) -> int:
r"""Determine how many spaces the ``config_line`` is indented.
Args:
config_line: A line of text in the config.
Returns:
The number of leading spaces.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser
>>> config = '''interface GigabitEthernet1\n description link to ISP'''
>>> config_line = " description link to ISP"
>>> indent_level = BaseSpaceConfigParser(config).get_leading_space_count(config_line)
>>> indent_level
1
>>>
"""
return len(config_line) - len(config_line.lstrip())
|
def is_banner_end(self, line):
BaseSpaceConfigParser
| def is_banner_end(self, line: str) -> bool:
"""Determine if line ends the banner config.
Args:
line: The current config line in iteration.
Returns:
True if line ends banner, else False.
"""
if self.banner_end in line:
return True
return False
|
def is_banner_one_line(config_line):
CiscoConfigParser
| @staticmethod
def is_banner_one_line(config_line: str) -> bool:
"""Determine if all banner config is on one line."""
_, delimeter, banner = config_line.partition("^C")
# Based on NXOS configs, the banner delimeter is ignored until another char is used
banner_config_start = banner.lstrip(delimeter)
if delimeter not in banner_config_start:
return False
return True
|
def is_banner_start(self, line):
CiscoConfigParser
CiscoConfigParser
| def is_banner_start(self, line: str) -> bool:
"""Determine if the line starts a banner config."""
state = super(CiscoConfigParser, self).is_banner_start(line)
if state:
self.banner_end = line
return state
|
BaseSpaceConfigParser
| def is_banner_start(self, line: str) -> bool:
"""Determine if the line starts a banner config.
Args:
line: The current config line in iteration.
Returns:
True if line starts banner, else False.
"""
for banner_start in self.banner_start:
if line.lstrip().startswith(banner_start):
return True
return False
|
def is_comment(self, line):
BaseSpaceConfigParser
| def is_comment(self, line: str) -> bool:
"""Determine if line is a comment.
Args:
line: A config line from the device.
Returns:
True if line is a comment, else False.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser
>>> BaseSpaceConfigParser("interface Ethernet1/1").is_comment("interface Ethernet1/1")
False
>>> BaseSpaceConfigParser("!").is_comment("!")
True
>>>
"""
for comment_char in self.comment_chars:
if line.lstrip().startswith(comment_char):
return True
return False
|
EOSConfigParser
from netutils.config.parser import EOSConfigParser
Ancestors (MRO)
The Method Resolution Order is described below.
- EOSConfigParser
- BaseSpaceConfigParser
- BaseConfigParser
Descendant Classes
Attributes
Key |
Value |
Defined in |
banner_end |
'EOF' |
EOSConfigParser |
banner_start |
['banner', 'vacant-message'] |
BaseSpaceConfigParser |
banner_start |
['banner', 'vacant-message'] |
BaseConfigParser |
comment_chars |
['!'] |
BaseSpaceConfigParser |
comment_chars |
['!'] |
BaseConfigParser |
Methods
def __init__(self, config):
BaseSpaceConfigParser
BaseSpaceConfigParser
| def __init__(self, config: str):
"""Create ConfigParser Object.
Args:
config (str): The config text to parse.
"""
self._indent_level = 0
super(BaseSpaceConfigParser, self).__init__(config)
|
BaseConfigParser
| def __init__(self, config: str):
"""Create ConfigParser Object.
Args:
config: The config text to parse.
"""
self.config = config
self._config: t.Optional[str] = None
self._current_parents: t.Tuple[str, ...] = ()
self.generator_config = (line for line in self.config_lines_only.splitlines())
self.config_lines: t.List[ConfigLine] = []
self.build_config_relationship()
|
def _build_banner(self, config_line):
EOSConfigParser
EOSConfigParser
| def _build_banner(self, config_line: str) -> t.Optional[str]:
"""Handle banner config lines.
Args:
config_line: The start of the banner config.
Returns:
The next configuration line in the configuration text or None when banner end is the end of the config text.
Raises:
ValueError: When the parser is unable to identify the End of the Banner.
"""
self._update_config_lines(config_line)
self._current_parents += (config_line,)
banner_config = []
for line in self.generator_config:
if not self.is_banner_end(line):
banner_config.append(line)
else:
banner_config.append(line)
line = "\n".join(banner_config)
self._update_config_lines(line)
self._current_parents = self._current_parents[:-1]
try:
return next(self.generator_config)
except StopIteration:
return None
raise ValueError("Unable to parse banner end.")
|
BaseSpaceConfigParser
| def _build_banner(self, config_line: str) -> t.Optional[str]:
"""Handle banner config lines.
Args:
config_line: The start of the banner config.
Returns:
The next configuration line in the configuration text or None
Raises:
ValueError: When the parser is unable to identify the end of the Banner.
"""
self._update_config_lines(config_line)
self._current_parents += (config_line,)
banner_config = []
for line in self.generator_config:
if not self.is_banner_end(line):
banner_config.append(line)
else:
line = normalise_delimiter_caret_c(self.banner_end, line)
banner_config.append(line)
line = "\n".join(banner_config)
if line.endswith("^C"):
banner, end, _ = line.rpartition("^C")
line = banner.rstrip() + end
self._update_config_lines(line)
self._current_parents = self._current_parents[:-1]
try:
return next(self.generator_config)
except StopIteration:
return None
raise ValueError("Unable to parse banner end.")
|
def _build_nested_config(self, line):
BaseSpaceConfigParser
| def _build_nested_config(self, line: str) -> t.Optional[str]:
"""Handle building child config sections.
Args:
line: A configuration line from the configuration text.
Returns:
The next top-level configuration line in the configuration text or None when the last line of configuration
text is a nested configuration line.
Raises:
IndexError: When the number of parents does not match the expected deindent level.
"""
self._update_config_lines(line)
for line in self.generator_config:
if not line[0].isspace():
self._current_parents = ()
self.indent_level = 0
return line
spaces = self.get_leading_space_count(line)
if spaces == self.indent_level:
pass
elif spaces > self.indent_level:
previous_config = self.config_lines[-1]
self._current_parents += (previous_config.config_line,)
else:
self._current_parents = self._remove_parents(line, spaces)
if spaces != self.indent_level:
self.indent_level = spaces
if self.is_banner_start(line):
banner_line = self._build_banner(line)
if banner_line is None or not banner_line[0].isspace():
self._current_parents = ()
self.indent_level = 0
return banner_line
line = banner_line
self._update_config_lines(line)
return None
|
def _match_type_check(line, pattern, match_type):
BaseSpaceConfigParser
| @staticmethod
def _match_type_check(line: str, pattern: str, match_type: str) -> bool:
"""Checks pattern for exact match or regex."""
if match_type == "exact" and line == pattern:
return True
if match_type == "startswith" and line.startswith(pattern):
return True
if match_type == "endswith" and line.endswith(pattern):
return True
if match_type == "regex" and re.match(pattern, line):
return True
return False
|
def _remove_parents(self, line, current_spaces):
BaseSpaceConfigParser
| def _remove_parents(self, line: str, current_spaces: int) -> t.Tuple[str, ...]:
"""Remove parents from ``self._curent_parents`` based on indent levels.
Args:
config_line: A line of text in the config.
Returns:
The config lines parent config lines.
"""
deindent_level = 1
try:
previous_parent = self._current_parents[-1]
previous_indent = self.get_leading_space_count(previous_parent)
while previous_indent > current_spaces:
deindent_level += 1
previous_parent = self._current_parents[-deindent_level]
previous_indent = self.get_leading_space_count(previous_parent)
except IndexError:
raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n")
parents = self._current_parents[:-deindent_level] or (self._current_parents[0],)
return parents
|
def _update_config_lines(self, config_line):
BaseSpaceConfigParser
| def _update_config_lines(self, config_line: str) -> None:
"""Add a ``ConfigLine`` object to ``self.config_lines``.
Args:
config_line: The current config line being evaluated.
Returns:
None
"""
entry = ConfigLine(config_line, self._current_parents)
self.config_lines.append(entry)
|
def build_config_relationship(self):
BaseSpaceConfigParser
BaseSpaceConfigParser
| def build_config_relationship(self) -> t.List[ConfigLine]:
r"""Parse text tree of config lines and their parents.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
>>> config = (
... "interface Ethernet1/1\n"
... " vlan 10\n"
... " no shutdown\n"
... "interface Ethernet1/2\n"
... " shutdown\n"
... )
>>> config_tree = BaseSpaceConfigParser(config)
>>> config_tree.build_config_relationship() == \
... [
... ConfigLine(config_line='interface Ethernet1/1', parents=()),
... ConfigLine(config_line=' vlan 10', parents=('interface Ethernet1/1',)),
... ConfigLine(config_line=' no shutdown', parents=('interface Ethernet1/1',)),
... ConfigLine(config_line='interface Ethernet1/2', parents=(),),
... ConfigLine(config_line=' shutdown', parents=('interface Ethernet1/2',))
... ]
True
"""
for line in self.generator_config:
if not line[0].isspace():
self._current_parents = ()
if self.is_banner_start(line):
line = self._build_banner(line) # type: ignore
else:
previous_config = self.config_lines[-1]
self._current_parents = (previous_config.config_line,)
self.indent_level = self.get_leading_space_count(line)
if not self.is_banner_start(line):
line = self._build_nested_config(line) # type: ignore
else:
line = self._build_banner(line) # type: ignore
if line is not None and line[0].isspace():
line = self._build_nested_config(line) # type: ignore
else:
self._current_parents = ()
if line is None:
break
elif self.is_banner_start(line):
line = self._build_banner(line) # type: ignore
self._update_config_lines(line)
return self.config_lines
|
BaseConfigParser
| def build_config_relationship(self) -> t.List[ConfigLine]:
"""Parse text tree of config lines and their parents."""
raise NotImplementedError()
|
def find_all_children(self, pattern, match_type=exact):
BaseSpaceConfigParser
| def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]:
"""Returns configuration part for a specific pattern not including parents.
Args:
pattern: pattern that describes parent.
match_type (optional): Exact or regex. Defaults to "exact".
Returns:
configuration under that parent pattern.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser
>>> config = '''
... router bgp 45000
... address-family ipv4 unicast
... neighbor 192.168.1.2 activate
... network 172.17.1.0 mask'''
>>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith")
>>> print(bgp_conf)
['router bgp 45000', ' address-family ipv4 unicast', ' neighbor 192.168.1.2 activate', ' network 172.17.1.0 mask']
"""
config = []
for cfg_line in self.build_config_relationship():
parents = cfg_line.parents[0] if cfg_line.parents else None
if (
parents
and self._match_type_check(parents, pattern, match_type)
or self._match_type_check(cfg_line.config_line, pattern, match_type)
):
config.append(cfg_line.config_line)
return config
|
def find_children_w_parents(self, parent_pattern, child_pattern, match_type=exact):
BaseSpaceConfigParser
| def find_children_w_parents(
self, parent_pattern: str, child_pattern: str, match_type: str = "exact"
) -> t.List[str]:
"""Returns configuration part for a specific pattern including parents and children.
Args:
parent_pattern: pattern that describes parent.
child_pattern: pattern that describes child.
match_type (optional): Exact or regex. Defaults to "exact".
Returns:
configuration under that parent pattern.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser
>>> config = '''
... router bgp 45000
... address-family ipv4 unicast
... neighbor 192.168.1.2 activate
... network 172.17.1.0 mask'''
>>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern=" address-family", match_type="regex")
>>> print(bgp_conf)
[' address-family ipv4 unicast', ' neighbor 192.168.1.2 activate', ' network 172.17.1.0 mask']
"""
config = []
potential_parents = [
elem.parents[0]
for elem in self.build_config_relationship()
if self._match_type_check(elem.config_line, child_pattern, match_type)
]
for cfg_line in self.build_config_relationship():
parents = cfg_line.parents[0] if cfg_line.parents else None
if parents in potential_parents and self._match_type_check(parents, parent_pattern, match_type):
config.append(cfg_line.config_line)
return config
|
def get_leading_space_count(config_line):
BaseSpaceConfigParser
| @staticmethod
def get_leading_space_count(config_line: str) -> int:
r"""Determine how many spaces the ``config_line`` is indented.
Args:
config_line: A line of text in the config.
Returns:
The number of leading spaces.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser
>>> config = '''interface GigabitEthernet1\n description link to ISP'''
>>> config_line = " description link to ISP"
>>> indent_level = BaseSpaceConfigParser(config).get_leading_space_count(config_line)
>>> indent_level
1
>>>
"""
return len(config_line) - len(config_line.lstrip())
|
def is_banner_end(self, line):
BaseSpaceConfigParser
| def is_banner_end(self, line: str) -> bool:
"""Determine if line ends the banner config.
Args:
line: The current config line in iteration.
Returns:
True if line ends banner, else False.
"""
if self.banner_end in line:
return True
return False
|
def is_banner_start(self, line):
BaseSpaceConfigParser
| def is_banner_start(self, line: str) -> bool:
"""Determine if the line starts a banner config.
Args:
line: The current config line in iteration.
Returns:
True if line starts banner, else False.
"""
for banner_start in self.banner_start:
if line.lstrip().startswith(banner_start):
return True
return False
|
def is_comment(self, line):
BaseSpaceConfigParser
| def is_comment(self, line: str) -> bool:
"""Determine if line is a comment.
Args:
line: A config line from the device.
Returns:
True if line is a comment, else False.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser
>>> BaseSpaceConfigParser("interface Ethernet1/1").is_comment("interface Ethernet1/1")
False
>>> BaseSpaceConfigParser("!").is_comment("!")
True
>>>
"""
for comment_char in self.comment_chars:
if line.lstrip().startswith(comment_char):
return True
return False
|
F5ConfigParser
from netutils.config.parser import F5ConfigParser
Ancestors (MRO)
The Method Resolution Order is described below.
- F5ConfigParser
- BaseBraceConfigParser
- BaseConfigParser
Descendant Classes
Attributes
Key |
Value |
Defined in |
banner_start |
['banner', 'vacant-message'] |
BaseConfigParser |
comment_chars |
['!'] |
BaseConfigParser |
Methods
def __init__(self, config):
F5ConfigParser
F5ConfigParser
| def __init__(self, config: str):
"""Create ConfigParser Object.
Args:
config: The config text to parse.
"""
super().__init__(self._clean_config_f5(config))
|
BaseConfigParser
| def __init__(self, config: str):
"""Create ConfigParser Object.
Args:
config: The config text to parse.
"""
self.config = config
self._config: t.Optional[str] = None
self._current_parents: t.Tuple[str, ...] = ()
self.generator_config = (line for line in self.config_lines_only.splitlines())
self.config_lines: t.List[ConfigLine] = []
self.build_config_relationship()
|
def _build_multiline_config(self, delimiter):
BaseBraceConfigParser
| def _build_multiline_config(self, delimiter: str) -> t.Optional[ConfigLine]:
r"""Build config sections between characters demarcating multiline strings.
Args:
delimiter: The text to look for to end multiline config.
Returns:
The multiline string text that was added to ``self.config_lines``.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
>>> config = (
... 'sys syslog {\n'
... ' include "\n'
... ' filter f_local0 {\n'
... ' facility(local0) and not match(\"DEBUG");\n'
... ' "\n'
... '}'
... )
>>> parser = BaseBraceConfigParser(config)
>>> # ' include "' started a multiline config
>>> parser.config_lines == \
... [
... ConfigLine(config_line='sys syslog {', parents=()),
... ConfigLine(config_line=' include "', parents=('sys syslog {',)),
... ConfigLine(config_line=' filter f_local0 {', parents=('sys syslog {',)),
... ConfigLine(config_line=' facility(local0) and not match("DEBUG");', parents=('sys syslog {', ' filter f_local0 {')),
... ConfigLine(config_line=' "', parents=('sys syslog {', ' filter f_local0 {')),
... ConfigLine(config_line='}', parents=('sys syslog {', ' filter f_local0 {'))
... ]
True
"""
multiline_config = []
for line in self.generator_config:
multiline_config.append(line)
if line.lstrip() == delimiter:
multiline_entry = ConfigLine("\n".join(multiline_config), self._current_parents)
self.config_lines.append(multiline_entry)
self._current_parents = self._current_parents[:-1]
return multiline_entry
return None
|
def _build_multiline_single_configuration_line(self, delimiter, prev_line):
F5ConfigParser
| def _build_multiline_single_configuration_line(self, delimiter: str, prev_line: str) -> t.Optional[ConfigLine]:
r"""Concatenate Multiline strings between delimiter when newlines causes string to traverse multiple lines.
Args:
delimiter: The text to look for to end multiline config.
prev_line: The text from the previously analyzed line.
Returns:
The multiline string text that was added to ``self.config_lines``.
Examples:
>>> from netutils.config.parser import F5ConfigParser, ConfigLine
>>> config = '''apm resource webtop-link aShare {
... application-uri http://funshare.example.com
... customization-group a_customization_group
... }
... apm sso form-based portal_ext_sso_form_based {
... form-action /Citrix/Example/ExplicitAuth/LoginAttempt
... form-field "LoginBtn Log+On
... StateContext "
... form-password password
... form-username username
... passthru true
... start-uri /Citrix/Example/ExplicitAuth/Login*
... success-match-type cookie
... success-match-value CtxsAuthId
... }
... '''
>>>
>>>
>>> config_tree = F5ConfigParser(str(config))
>>> print(config_tree.build_config_relationship())
[ConfigLine(config_line='apm resource webtop-link aShare {', parents=()), ConfigLine(config_line=' application-uri http://funshare.example.com', parents=('apm resource webtop-link aShare {',)), ConfigLine(config_line=' customization-group a_customization_group', parents=('apm resource webtop-link aShare {',)), ConfigLine(config_line='}', parents=('apm resource webtop-link aShare {',)), ConfigLine(config_line='apm sso form-based portal_ext_sso_form_based {', parents=()), ConfigLine(config_line=' form-action /Citrix/Example/ExplicitAuth/LoginAttempt', parents=('apm sso form-based portal_ext_sso_form_based {',)), ConfigLine(config_line=' form-field "LoginBtn Log+On\nStateContext "', parents=('apm sso form-based portal_ext_sso_form_based {',)), ConfigLine(config_line=' form-password password', parents=()), ConfigLine(config_line=' form-username username', parents=()), ConfigLine(config_line=' passthru true', parents=()), ConfigLine(config_line=' start-uri /Citrix/Example/ExplicitAuth/Login*', parents=()), ConfigLine(config_line=' success-match-type cookie', parents=()), ConfigLine(config_line=' success-match-value CtxsAuthId', parents=()), ConfigLine(config_line='}', parents=())]
"""
multiline_config = [prev_line]
for line in self.generator_config:
multiline_config.append(line)
if line.endswith(delimiter):
multiline_entry = ConfigLine("\n".join(multiline_config), self._current_parents)
self.config_lines[-1] = multiline_entry
self._current_parents = self._current_parents[:-1]
return multiline_entry
return None
|
def _clean_config_f5(self, config_text):
F5ConfigParser
| def _clean_config_f5(self, config_text: str) -> str:
"""Removes all configuration items with 'ltm rule'.
iRules are essentially impossible to parse with the lack of uniformity,
therefore, this method ensures they are not included in ``self.config``.
Args:
config_text: The entire config as a string.
Returns:
The sanitized config with all iRules (ltm rule) stanzas removed.
"""
config_split = config_text.split("ltm rule")
if len(config_split) > 1:
start_config = config_split[0]
end_config = config_split[-1]
_, ltm, clean_config = end_config.partition("ltm")
final_config = start_config + ltm + clean_config
else:
final_config = config_text
return final_config
|
def build_config_relationship(self):
F5ConfigParser
F5ConfigParser
| def build_config_relationship(self) -> t.List[ConfigLine]:
r"""Parse text tree of config lines and their parents.
Examples:
>>> from netutils.config.parser import F5ConfigParser, ConfigLine
>>> config = '''apm resource webtop-link aShare {
... application-uri http://funshare.example.com
... customization-group a_customization_group
... }
... apm sso form-based portal_ext_sso_form_based {
... form-action /Citrix/Example/ExplicitAuth/LoginAttempt
... form-field "LoginBtn Log+On
... StateContext "
... form-password password
... form-username username
... passthru true
... start-uri /Citrix/Example/ExplicitAuth/Login*
... success-match-type cookie
... success-match-value CtxsAuthId
... }
... '''
>>>
>>> config_tree = F5ConfigParser(config)
>>> print(config_tree.build_config_relationship())
[ConfigLine(config_line='apm resource webtop-link aShare {', parents=()), ConfigLine(config_line=' application-uri http://funshare.example.com', parents=('apm resource webtop-link aShare {',)), ConfigLine(config_line=' customization-group a_customization_group', parents=('apm resource webtop-link aShare {',)), ConfigLine(config_line='}', parents=('apm resource webtop-link aShare {',)), ConfigLine(config_line='apm sso form-based portal_ext_sso_form_based {', parents=()), ConfigLine(config_line=' form-action /Citrix/Example/ExplicitAuth/LoginAttempt', parents=('apm sso form-based portal_ext_sso_form_based {',)), ConfigLine(config_line=' form-field "LoginBtn Log+On\nStateContext "', parents=('apm sso form-based portal_ext_sso_form_based {',)), ConfigLine(config_line=' form-password password', parents=()), ConfigLine(config_line=' form-username username', parents=()), ConfigLine(config_line=' passthru true', parents=()), ConfigLine(config_line=' start-uri /Citrix/Example/ExplicitAuth/Login*', parents=()), ConfigLine(config_line=' success-match-type cookie', parents=()), ConfigLine(config_line=' success-match-value CtxsAuthId', parents=()), ConfigLine(config_line='}', parents=())]
"""
for line in self.generator_config:
self.config_lines.append(ConfigLine(line, self._current_parents))
line_end = line[-1]
if line.endswith("{"):
self._current_parents += (line,)
elif line.lstrip() == "}":
self._current_parents = self._current_parents[:-1]
elif any(
delimiters in self.multiline_delimiters and line.count(delimiters) == 1
for delimiters in self.multiline_delimiters
):
for delimiter in self.multiline_delimiters:
if line.count(delimiter) == 1:
self._build_multiline_single_configuration_line(delimiter, line)
elif line_end in self.multiline_delimiters and line.count(line_end) == 1:
self._current_parents += (line,)
self._build_multiline_config(line_end)
return self.config_lines
|
BaseBraceConfigParser
| def build_config_relationship(self) -> t.List[ConfigLine]:
r"""Parse text tree of config lines and their parents.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
>>> config = '''auth ldap system-auth {
... port ldaps
... servers { ams-lda01.ntc.com }
... }
... auth partition Common {
... description "Repository for system objects and shared objects."
... }
... auth password-policy { }'''
>>> config_tree = BaseBraceConfigParser(config)
>>> config_tree.build_config_relationship() == \
... [
... ConfigLine(config_line='auth ldap system-auth {', parents=()),
... ConfigLine(config_line=' port ldaps', parents=('auth ldap system-auth {',)),
... ConfigLine(config_line=' servers { ams-lda01.ntc.com }', parents=('auth ldap system-auth {',)),
... ConfigLine(config_line=' }', parents=('auth ldap system-auth {',)),
... ConfigLine(config_line=' auth partition Common {', parents=()),
... ConfigLine(config_line=' description "Repository for system objects and shared objects."', parents=(' auth partition Common {',)), ConfigLine(config_line=' }', parents=(' auth partition Common {',)),
... ConfigLine(config_line=' auth password-policy { }', parents=())
... ]
True
"""
for line in self.generator_config:
self.config_lines.append(ConfigLine(line, self._current_parents))
line_end = line[-1]
if line.endswith("{"):
self._current_parents += (line,)
elif line.lstrip() == "}":
self._current_parents = self._current_parents[:-1]
elif line_end in self.multiline_delimiters and line.count(line_end) == 1:
self._current_parents += (line,)
self._build_multiline_config(line_end)
return self.config_lines
|
BaseConfigParser
| def build_config_relationship(self) -> t.List[ConfigLine]:
"""Parse text tree of config lines and their parents."""
raise NotImplementedError()
|
FastironConfigParser
from netutils.config.parser import FastironConfigParser
Ancestors (MRO)
The Method Resolution Order is described below.
- FastironConfigParser
- CiscoConfigParser
- BaseSpaceConfigParser
- BaseConfigParser
Descendant Classes
Attributes
Key |
Value |
Defined in |
banner_start |
['banner', 'vacant-message'] |
BaseSpaceConfigParser |
banner_start |
['banner', 'vacant-message'] |
BaseConfigParser |
comment_chars |
['!'] |
BaseSpaceConfigParser |
comment_chars |
['!'] |
BaseConfigParser |
regex_banner |
re.compile('^banner(\\smotd)?\\s+(?P<banner_delimiter>\\S)') |
FastironConfigParser |
regex_banner |
re.compile('^(banner\\s+\\S+|\\s*vacant-message)\\s+(?P<banner_delimiter>\\^C|.)') |
CiscoConfigParser |
Methods
def __init__(self, config):
FastironConfigParser
FastironConfigParser
| def __init__(self, config: str):
"""Create ConfigParser Object.
Args:
config (str): The config text to parse.
"""
super(FastironConfigParser, self).__init__(config)
|
CiscoConfigParser
| def __init__(self, config: str):
"""Create ConfigParser Object.
Args:
config (str): The config text to parse.
"""
self._banner_end: t.Optional[str] = None
super(CiscoConfigParser, self).__init__(config)
|
BaseSpaceConfigParser
| def __init__(self, config: str):
"""Create ConfigParser Object.
Args:
config (str): The config text to parse.
"""
self._indent_level = 0
super(BaseSpaceConfigParser, self).__init__(config)
|
BaseConfigParser
| def __init__(self, config: str):
"""Create ConfigParser Object.
Args:
config: The config text to parse.
"""
self.config = config
self._config: t.Optional[str] = None
self._current_parents: t.Tuple[str, ...] = ()
self.generator_config = (line for line in self.config_lines_only.splitlines())
self.config_lines: t.List[ConfigLine] = []
self.build_config_relationship()
|
def _build_banner(self, config_line):
FastironConfigParser
FastironConfigParser
| def _build_banner(self, config_line: str) -> t.Optional[str]:
"""Handle banner config lines.
Args:
config_line: The start of the banner config.
Returns:
The next configuration line in the configuration text or None
Raises:
ValueError: When the parser is unable to identify the end of the Banner.
"""
self._update_config_lines(config_line)
self._current_parents += (config_line,)
banner_config = []
for line in self.generator_config:
if not self.is_banner_end(line):
banner_config.append(line)
else:
banner_config.append(line)
line = "\n".join(banner_config)
if line.endswith(self.banner_end):
banner, end, _ = line.rpartition(self.banner_end)
line = banner.rstrip() + end
self._update_config_lines(line)
self._current_parents = self._current_parents[:-1]
try:
return next(self.generator_config)
except StopIteration:
return None
raise ValueError("Unable to parse banner end.")
|
CiscoConfigParser
| def _build_banner(self, config_line: str) -> t.Optional[str]:
"""Handle banner config lines.
Args:
config_line: The start of the banner config.
Returns:
The next configuration line in the configuration text or None when banner end is the end of the config text.
Raises:
ValueError: When the parser is unable to identify the End of the Banner.
"""
if self.is_banner_one_line(config_line):
self._update_config_lines(config_line)
try:
return next(self.generator_config)
except StopIteration:
return None
return super(CiscoConfigParser, self)._build_banner(config_line)
|
BaseSpaceConfigParser
| def _build_banner(self, config_line: str) -> t.Optional[str]:
"""Handle banner config lines.
Args:
config_line: The start of the banner config.
Returns:
The next configuration line in the configuration text or None
Raises:
ValueError: When the parser is unable to identify the end of the Banner.
"""
self._update_config_lines(config_line)
self._current_parents += (config_line,)
banner_config = []
for line in self.generator_config:
if not self.is_banner_end(line):
banner_config.append(line)
else:
line = normalise_delimiter_caret_c(self.banner_end, line)
banner_config.append(line)
line = "\n".join(banner_config)
if line.endswith("^C"):
banner, end, _ = line.rpartition("^C")
line = banner.rstrip() + end
self._update_config_lines(line)
self._current_parents = self._current_parents[:-1]
try:
return next(self.generator_config)
except StopIteration:
return None
raise ValueError("Unable to parse banner end.")
|
def _build_nested_config(self, line):
BaseSpaceConfigParser
| def _build_nested_config(self, line: str) -> t.Optional[str]:
"""Handle building child config sections.
Args:
line: A configuration line from the configuration text.
Returns:
The next top-level configuration line in the configuration text or None when the last line of configuration
text is a nested configuration line.
Raises:
IndexError: When the number of parents does not match the expected deindent level.
"""
self._update_config_lines(line)
for line in self.generator_config:
if not line[0].isspace():
self._current_parents = ()
self.indent_level = 0
return line
spaces = self.get_leading_space_count(line)
if spaces == self.indent_level:
pass
elif spaces > self.indent_level:
previous_config = self.config_lines[-1]
self._current_parents += (previous_config.config_line,)
else:
self._current_parents = self._remove_parents(line, spaces)
if spaces != self.indent_level:
self.indent_level = spaces
if self.is_banner_start(line):
banner_line = self._build_banner(line)
if banner_line is None or not banner_line[0].isspace():
self._current_parents = ()
self.indent_level = 0
return banner_line
line = banner_line
self._update_config_lines(line)
return None
|
def _match_type_check(line, pattern, match_type):
BaseSpaceConfigParser
| @staticmethod
def _match_type_check(line: str, pattern: str, match_type: str) -> bool:
"""Checks pattern for exact match or regex."""
if match_type == "exact" and line == pattern:
return True
if match_type == "startswith" and line.startswith(pattern):
return True
if match_type == "endswith" and line.endswith(pattern):
return True
if match_type == "regex" and re.match(pattern, line):
return True
return False
|
def _remove_parents(self, line, current_spaces):
BaseSpaceConfigParser
| def _remove_parents(self, line: str, current_spaces: int) -> t.Tuple[str, ...]:
"""Remove parents from ``self._curent_parents`` based on indent levels.
Args:
config_line: A line of text in the config.
Returns:
The config lines parent config lines.
"""
deindent_level = 1
try:
previous_parent = self._current_parents[-1]
previous_indent = self.get_leading_space_count(previous_parent)
while previous_indent > current_spaces:
deindent_level += 1
previous_parent = self._current_parents[-deindent_level]
previous_indent = self.get_leading_space_count(previous_parent)
except IndexError:
raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n")
parents = self._current_parents[:-deindent_level] or (self._current_parents[0],)
return parents
|
def _update_config_lines(self, config_line):
BaseSpaceConfigParser
| def _update_config_lines(self, config_line: str) -> None:
"""Add a ``ConfigLine`` object to ``self.config_lines``.
Args:
config_line: The current config line being evaluated.
Returns:
None
"""
entry = ConfigLine(config_line, self._current_parents)
self.config_lines.append(entry)
|
def build_config_relationship(self):
BaseSpaceConfigParser
BaseSpaceConfigParser
| def build_config_relationship(self) -> t.List[ConfigLine]:
r"""Parse text tree of config lines and their parents.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
>>> config = (
... "interface Ethernet1/1\n"
... " vlan 10\n"
... " no shutdown\n"
... "interface Ethernet1/2\n"
... " shutdown\n"
... )
>>> config_tree = BaseSpaceConfigParser(config)
>>> config_tree.build_config_relationship() == \
... [
... ConfigLine(config_line='interface Ethernet1/1', parents=()),
... ConfigLine(config_line=' vlan 10', parents=('interface Ethernet1/1',)),
... ConfigLine(config_line=' no shutdown', parents=('interface Ethernet1/1',)),
... ConfigLine(config_line='interface Ethernet1/2', parents=(),),
... ConfigLine(config_line=' shutdown', parents=('interface Ethernet1/2',))
... ]
True
"""
for line in self.generator_config:
if not line[0].isspace():
self._current_parents = ()
if self.is_banner_start(line):
line = self._build_banner(line) # type: ignore
else:
previous_config = self.config_lines[-1]
self._current_parents = (previous_config.config_line,)
self.indent_level = self.get_leading_space_count(line)
if not self.is_banner_start(line):
line = self._build_nested_config(line) # type: ignore
else:
line = self._build_banner(line) # type: ignore
if line is not None and line[0].isspace():
line = self._build_nested_config(line) # type: ignore
else:
self._current_parents = ()
if line is None:
break
elif self.is_banner_start(line):
line = self._build_banner(line) # type: ignore
self._update_config_lines(line)
return self.config_lines
|
BaseConfigParser
| def build_config_relationship(self) -> t.List[ConfigLine]:
"""Parse text tree of config lines and their parents."""
raise NotImplementedError()
|
def find_all_children(self, pattern, match_type=exact):
BaseSpaceConfigParser
| def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]:
"""Returns configuration part for a specific pattern not including parents.
Args:
pattern: pattern that describes parent.
match_type (optional): Exact or regex. Defaults to "exact".
Returns:
configuration under that parent pattern.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser
>>> config = '''
... router bgp 45000
... address-family ipv4 unicast
... neighbor 192.168.1.2 activate
... network 172.17.1.0 mask'''
>>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith")
>>> print(bgp_conf)
['router bgp 45000', ' address-family ipv4 unicast', ' neighbor 192.168.1.2 activate', ' network 172.17.1.0 mask']
"""
config = []
for cfg_line in self.build_config_relationship():
parents = cfg_line.parents[0] if cfg_line.parents else None
if (
parents
and self._match_type_check(parents, pattern, match_type)
or self._match_type_check(cfg_line.config_line, pattern, match_type)
):
config.append(cfg_line.config_line)
return config
|
def find_children_w_parents(self, parent_pattern, child_pattern, match_type=exact):
BaseSpaceConfigParser
| def find_children_w_parents(
self, parent_pattern: str, child_pattern: str, match_type: str = "exact"
) -> t.List[str]:
"""Returns configuration part for a specific pattern including parents and children.
Args:
parent_pattern: pattern that describes parent.
child_pattern: pattern that describes child.
match_type (optional): Exact or regex. Defaults to "exact".
Returns:
configuration under that parent pattern.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser
>>> config = '''
... router bgp 45000
... address-family ipv4 unicast
... neighbor 192.168.1.2 activate
... network 172.17.1.0 mask'''
>>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern=" address-family", match_type="regex")
>>> print(bgp_conf)
[' address-family ipv4 unicast', ' neighbor 192.168.1.2 activate', ' network 172.17.1.0 mask']
"""
config = []
potential_parents = [
elem.parents[0]
for elem in self.build_config_relationship()
if self._match_type_check(elem.config_line, child_pattern, match_type)
]
for cfg_line in self.build_config_relationship():
parents = cfg_line.parents[0] if cfg_line.parents else None
if parents in potential_parents and self._match_type_check(parents, parent_pattern, match_type):
config.append(cfg_line.config_line)
return config
|
def get_leading_space_count(config_line):
BaseSpaceConfigParser
| @staticmethod
def get_leading_space_count(config_line: str) -> int:
r"""Determine how many spaces the ``config_line`` is indented.
Args:
config_line: A line of text in the config.
Returns:
The number of leading spaces.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser
>>> config = '''interface GigabitEthernet1\n description link to ISP'''
>>> config_line = " description link to ISP"
>>> indent_level = BaseSpaceConfigParser(config).get_leading_space_count(config_line)
>>> indent_level
1
>>>
"""
return len(config_line) - len(config_line.lstrip())
|
def is_banner_end(self, line):
BaseSpaceConfigParser
| def is_banner_end(self, line: str) -> bool:
"""Determine if line ends the banner config.
Args:
line: The current config line in iteration.
Returns:
True if line ends banner, else False.
"""
if self.banner_end in line:
return True
return False
|
def is_banner_one_line(config_line):
CiscoConfigParser
| @staticmethod
def is_banner_one_line(config_line: str) -> bool:
"""Determine if all banner config is on one line."""
_, delimeter, banner = config_line.partition("^C")
# Based on NXOS configs, the banner delimeter is ignored until another char is used
banner_config_start = banner.lstrip(delimeter)
if delimeter not in banner_config_start:
return False
return True
|
def is_banner_start(self, line):
CiscoConfigParser
CiscoConfigParser
| def is_banner_start(self, line: str) -> bool:
"""Determine if the line starts a banner config."""
state = super(CiscoConfigParser, self).is_banner_start(line)
if state:
self.banner_end = line
return state
|
BaseSpaceConfigParser
| def is_banner_start(self, line: str) -> bool:
"""Determine if the line starts a banner config.
Args:
line: The current config line in iteration.
Returns:
True if line starts banner, else False.
"""
for banner_start in self.banner_start:
if line.lstrip().startswith(banner_start):
return True
return False
|
def is_comment(self, line):
BaseSpaceConfigParser
| def is_comment(self, line: str) -> bool:
"""Determine if line is a comment.
Args:
line: A config line from the device.
Returns:
True if line is a comment, else False.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser
>>> BaseSpaceConfigParser("interface Ethernet1/1").is_comment("interface Ethernet1/1")
False
>>> BaseSpaceConfigParser("!").is_comment("!")
True
>>>
"""
for comment_char in self.comment_chars:
if line.lstrip().startswith(comment_char):
return True
return False
|
FortinetConfigParser
from netutils.config.parser import FortinetConfigParser
Ancestors (MRO)
The Method Resolution Order is described below.
- FortinetConfigParser
- BaseSpaceConfigParser
- BaseConfigParser
Descendant Classes
Attributes
Key |
Value |
Defined in |
banner_start |
['banner', 'vacant-message'] |
BaseSpaceConfigParser |
banner_start |
['banner', 'vacant-message'] |
BaseConfigParser |
comment_chars |
['!'] |
BaseSpaceConfigParser |
comment_chars |
['!'] |
BaseConfigParser |
Methods
def __init__(self, config):
FortinetConfigParser
FortinetConfigParser
| def __init__(self, config: str):
"""Create ConfigParser Object.
Args:
config (str): The config text to parse.
"""
self.uncommon_data = self._get_uncommon_lines(config)
super(FortinetConfigParser, self).__init__(config)
|
BaseSpaceConfigParser
| def __init__(self, config: str):
"""Create ConfigParser Object.
Args:
config (str): The config text to parse.
"""
self._indent_level = 0
super(BaseSpaceConfigParser, self).__init__(config)
|
BaseConfigParser
| def __init__(self, config: str):
"""Create ConfigParser Object.
Args:
config: The config text to parse.
"""
self.config = config
self._config: t.Optional[str] = None
self._current_parents: t.Tuple[str, ...] = ()
self.generator_config = (line for line in self.config_lines_only.splitlines())
self.config_lines: t.List[ConfigLine] = []
self.build_config_relationship()
|
def _build_banner(self, config_line):
BaseSpaceConfigParser
| def _build_banner(self, config_line: str) -> t.Optional[str]:
"""Handle banner config lines.
Args:
config_line: The start of the banner config.
Returns:
The next configuration line in the configuration text or None
Raises:
ValueError: When the parser is unable to identify the end of the Banner.
"""
self._update_config_lines(config_line)
self._current_parents += (config_line,)
banner_config = []
for line in self.generator_config:
if not self.is_banner_end(line):
banner_config.append(line)
else:
line = normalise_delimiter_caret_c(self.banner_end, line)
banner_config.append(line)
line = "\n".join(banner_config)
if line.endswith("^C"):
banner, end, _ = line.rpartition("^C")
line = banner.rstrip() + end
self._update_config_lines(line)
self._current_parents = self._current_parents[:-1]
try:
return next(self.generator_config)
except StopIteration:
return None
raise ValueError("Unable to parse banner end.")
|
def _build_nested_config(self, line):
FortinetConfigParser
FortinetConfigParser
| def _build_nested_config(self, line: str) -> t.Optional[str]:
"""Handle building child config sections.
Args:
line: A configuration line from the configuration text.
Returns:
The next top-level configuration line in the configuration text or None when the last line of configuration
text is a nested configuration line.
Raises:
IndexError: When the number of parents does not match the expected deindent level.
"""
if "[" in line:
updated_line = self.uncommon_data.get(line.split('"')[1], None)
if not updated_line:
raise ValueError("Input line is malformed.")
line = updated_line
self._update_config_lines(line)
for line in self.generator_config:
if not line[0].isspace():
self._current_parents = ()
self.indent_level = 0
return line
spaces = self.get_leading_space_count(line)
if spaces == self.indent_level:
pass
elif spaces > self.indent_level:
previous_config = self.config_lines[-1]
self._current_parents += (previous_config.config_line,)
else:
self._current_parents = self._remove_parents(line, spaces)
if spaces != self.indent_level:
self.indent_level = spaces
self._update_config_lines(line)
return None
|
BaseSpaceConfigParser
| def _build_nested_config(self, line: str) -> t.Optional[str]:
"""Handle building child config sections.
Args:
line: A configuration line from the configuration text.
Returns:
The next top-level configuration line in the configuration text or None when the last line of configuration
text is a nested configuration line.
Raises:
IndexError: When the number of parents does not match the expected deindent level.
"""
self._update_config_lines(line)
for line in self.generator_config:
if not line[0].isspace():
self._current_parents = ()
self.indent_level = 0
return line
spaces = self.get_leading_space_count(line)
if spaces == self.indent_level:
pass
elif spaces > self.indent_level:
previous_config = self.config_lines[-1]
self._current_parents += (previous_config.config_line,)
else:
self._current_parents = self._remove_parents(line, spaces)
if spaces != self.indent_level:
self.indent_level = spaces
if self.is_banner_start(line):
banner_line = self._build_banner(line)
if banner_line is None or not banner_line[0].isspace():
self._current_parents = ()
self.indent_level = 0
return banner_line
line = banner_line
self._update_config_lines(line)
return None
|
def _get_uncommon_lines(self, config):
FortinetConfigParser
| def _get_uncommon_lines(self, config: str) -> t.Dict[str, str]:
"""Regex to find replacemsg lines which can contain html/css data.
Args:
config: Original config before parsing.
Returns:
dict: dictionary with replace message name as key, html/css data as value.
"""
pattern = r"(config system replacemsg.*\n)(\s{4}set\sbuffer\s\"[\S\s]*?\"\n)"
regex_result = re.findall(pattern, config)
result = {}
for group_match in regex_result:
result.update({group_match[0].split('"')[1]: group_match[1]})
return result
|
def _match_type_check(line, pattern, match_type):
BaseSpaceConfigParser
| @staticmethod
def _match_type_check(line: str, pattern: str, match_type: str) -> bool:
"""Checks pattern for exact match or regex."""
if match_type == "exact" and line == pattern:
return True
if match_type == "startswith" and line.startswith(pattern):
return True
if match_type == "endswith" and line.endswith(pattern):
return True
if match_type == "regex" and re.match(pattern, line):
return True
return False
|
def _parse_out_offending(self, config):
FortinetConfigParser
| def _parse_out_offending(self, config: str) -> str:
"""Preprocess out strings that offend the normal spaced configuration syntax.
Args:
config (str): full config as a string.
"""
# This will grab everything between quotes after the 'set buffer' sub-command.
# Its explicitly looking for "\n to end the captured data. This is to support html
# data that is supported in Fortinet config with double quotes within the html.
pattern = r"(config system replacemsg.*(\".*\")\n)(\s{4}set\sbuffer\s\"[\S\s]*?\"\n)"
return re.sub(pattern, r"\1 [\2]\n", config)
|
def _remove_parents(self, line, current_spaces):
BaseSpaceConfigParser
| def _remove_parents(self, line: str, current_spaces: int) -> t.Tuple[str, ...]:
"""Remove parents from ``self._curent_parents`` based on indent levels.
Args:
config_line: A line of text in the config.
Returns:
The config lines parent config lines.
"""
deindent_level = 1
try:
previous_parent = self._current_parents[-1]
previous_indent = self.get_leading_space_count(previous_parent)
while previous_indent > current_spaces:
deindent_level += 1
previous_parent = self._current_parents[-deindent_level]
previous_indent = self.get_leading_space_count(previous_parent)
except IndexError:
raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n")
parents = self._current_parents[:-deindent_level] or (self._current_parents[0],)
return parents
|
def _update_config_lines(self, config_line):
BaseSpaceConfigParser
| def _update_config_lines(self, config_line: str) -> None:
"""Add a ``ConfigLine`` object to ``self.config_lines``.
Args:
config_line: The current config line being evaluated.
Returns:
None
"""
entry = ConfigLine(config_line, self._current_parents)
self.config_lines.append(entry)
|
def build_config_relationship(self):
BaseSpaceConfigParser
BaseSpaceConfigParser
| def build_config_relationship(self) -> t.List[ConfigLine]:
r"""Parse text tree of config lines and their parents.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
>>> config = (
... "interface Ethernet1/1\n"
... " vlan 10\n"
... " no shutdown\n"
... "interface Ethernet1/2\n"
... " shutdown\n"
... )
>>> config_tree = BaseSpaceConfigParser(config)
>>> config_tree.build_config_relationship() == \
... [
... ConfigLine(config_line='interface Ethernet1/1', parents=()),
... ConfigLine(config_line=' vlan 10', parents=('interface Ethernet1/1',)),
... ConfigLine(config_line=' no shutdown', parents=('interface Ethernet1/1',)),
... ConfigLine(config_line='interface Ethernet1/2', parents=(),),
... ConfigLine(config_line=' shutdown', parents=('interface Ethernet1/2',))
... ]
True
"""
for line in self.generator_config:
if not line[0].isspace():
self._current_parents = ()
if self.is_banner_start(line):
line = self._build_banner(line) # type: ignore
else:
previous_config = self.config_lines[-1]
self._current_parents = (previous_config.config_line,)
self.indent_level = self.get_leading_space_count(line)
if not self.is_banner_start(line):
line = self._build_nested_config(line) # type: ignore
else:
line = self._build_banner(line) # type: ignore
if line is not None and line[0].isspace():
line = self._build_nested_config(line) # type: ignore
else:
self._current_parents = ()
if line is None:
break
elif self.is_banner_start(line):
line = self._build_banner(line) # type: ignore
self._update_config_lines(line)
return self.config_lines
|
BaseConfigParser
| def build_config_relationship(self) -> t.List[ConfigLine]:
"""Parse text tree of config lines and their parents."""
raise NotImplementedError()
|
def find_all_children(self, pattern, match_type=exact):
BaseSpaceConfigParser
| def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]:
"""Returns configuration part for a specific pattern not including parents.
Args:
pattern: pattern that describes parent.
match_type (optional): Exact or regex. Defaults to "exact".
Returns:
configuration under that parent pattern.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser
>>> config = '''
... router bgp 45000
... address-family ipv4 unicast
... neighbor 192.168.1.2 activate
... network 172.17.1.0 mask'''
>>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith")
>>> print(bgp_conf)
['router bgp 45000', ' address-family ipv4 unicast', ' neighbor 192.168.1.2 activate', ' network 172.17.1.0 mask']
"""
config = []
for cfg_line in self.build_config_relationship():
parents = cfg_line.parents[0] if cfg_line.parents else None
if (
parents
and self._match_type_check(parents, pattern, match_type)
or self._match_type_check(cfg_line.config_line, pattern, match_type)
):
config.append(cfg_line.config_line)
return config
|
def find_children_w_parents(self, parent_pattern, child_pattern, match_type=exact):
BaseSpaceConfigParser
| def find_children_w_parents(
self, parent_pattern: str, child_pattern: str, match_type: str = "exact"
) -> t.List[str]:
"""Returns configuration part for a specific pattern including parents and children.
Args:
parent_pattern: pattern that describes parent.
child_pattern: pattern that describes child.
match_type (optional): Exact or regex. Defaults to "exact".
Returns:
configuration under that parent pattern.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser
>>> config = '''
... router bgp 45000
... address-family ipv4 unicast
... neighbor 192.168.1.2 activate
... network 172.17.1.0 mask'''
>>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern=" address-family", match_type="regex")
>>> print(bgp_conf)
[' address-family ipv4 unicast', ' neighbor 192.168.1.2 activate', ' network 172.17.1.0 mask']
"""
config = []
potential_parents = [
elem.parents[0]
for elem in self.build_config_relationship()
if self._match_type_check(elem.config_line, child_pattern, match_type)
]
for cfg_line in self.build_config_relationship():
parents = cfg_line.parents[0] if cfg_line.parents else None
if parents in potential_parents and self._match_type_check(parents, parent_pattern, match_type):
config.append(cfg_line.config_line)
return config
|
def get_leading_space_count(config_line):
BaseSpaceConfigParser
| @staticmethod
def get_leading_space_count(config_line: str) -> int:
r"""Determine how many spaces the ``config_line`` is indented.
Args:
config_line: A line of text in the config.
Returns:
The number of leading spaces.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser
>>> config = '''interface GigabitEthernet1\n description link to ISP'''
>>> config_line = " description link to ISP"
>>> indent_level = BaseSpaceConfigParser(config).get_leading_space_count(config_line)
>>> indent_level
1
>>>
"""
return len(config_line) - len(config_line.lstrip())
|
def is_banner_end(self, line):
BaseSpaceConfigParser
| def is_banner_end(self, line: str) -> bool:
"""Determine if line ends the banner config.
Args:
line: The current config line in iteration.
Returns:
True if line ends banner, else False.
"""
if self.banner_end in line:
return True
return False
|
def is_banner_start(self, line):
BaseSpaceConfigParser
| def is_banner_start(self, line: str) -> bool:
"""Determine if the line starts a banner config.
Args:
line: The current config line in iteration.
Returns:
True if line starts banner, else False.
"""
for banner_start in self.banner_start:
if line.lstrip().startswith(banner_start):
return True
return False
|
def is_comment(self, line):
BaseSpaceConfigParser
| def is_comment(self, line: str) -> bool:
"""Determine if line is a comment.
Args:
line: A config line from the device.
Returns:
True if line is a comment, else False.
Examples:
>>> from netutils.config.parser import BaseSpaceConfigParser
>>> BaseSpaceConfigParser("interface Ethernet1/1").is_comment("interface Ethernet1/1")
False
>>> BaseSpaceConfigParser("!").is_comment("!")
True
>>>
"""
for comment_char in self.comment_chars:
if line.lstrip().startswith(comment_char):
return True
return False
|
def is_end_next(self, line):
FortinetConfigParser
| def is_end_next(self, line: str) -> bool:
"""Determine if line has 'end' or 'next' in it.
Args:
line: A config line from the device.
Returns:
True if line has 'end' or 'next', else False.
Examples:
>>> from netutils.config.parser import FortinetConfigParser
>>> FortinetConfigParser("config system virtual-switch").is_end_next("config system virtual-switch")
False
>>> FortinetConfigParser("end").is_end_next("end")
True
>>>
"""
for end_next in ["end", "next"]:
if line.lstrip() == end_next:
return True
return False
|
IOSConfigParser
from netutils.config.parser import IOSConfigParser
Ancestors (MRO)
The Method Resolution Order is described below.
- IOSConfigParser
- CiscoConfigParser
- BaseSpaceConfigParser
- BaseConfigParser
Descendant Classes
Attributes
Key |
Value |
Defined in |
banner_start |
['banner', 'vacant-message'] |
BaseSpaceConfigParser |
banner_start |
['banner', 'vacant-message'] |
BaseConfigParser |
comment_chars |
['!'] |
BaseSpaceConfigParser |
comment_chars |
['!'] |
BaseConfigParser |
regex_banner |
re.compile('^(banner\\s+\\S+|\\s*vacant-message)\\s+(?P<banner_delimiter>\\^C|.)') |
CiscoConfigParser |
Methods
def __init__(self, config):
IOSConfigParser
IOSConfigParser
| def __init__(self, config: str):
"""Create ConfigParser Object.
Args:
config (str): The config text to parse.
"""
self.unique_config_lines: t.Set[ConfigLine] = set()
self.same_line_children: t.Set[ConfigLine] = set()
super(IOSConfigParser, self).__init__(config)
|
CiscoConfigParser
| def __init__(self, config: str):
"""Create ConfigParser Object.
Args:
config (str): The config text to parse.
"""
self._banner_end: t.Optional[str] = None
super(CiscoConfigParser, self).__init__(config)
|
BaseSpaceConfigParser
| def __init__(self, config: str):
"""Create ConfigParser Object.
Args:
config (str): The config text to parse.
"""
self._indent_level = 0
super(BaseSpaceConfigParser, self).__init__(config)
|
BaseConfigParser
| def __init__(self, config: str):
"""Create ConfigParser Object.
Args:
config: The config text to parse.
"""
self.config = config
self._config: t.Optional[str] = None
self._current_parents: t.Tuple[str, ...] = ()
self.generator_config = (line for line in self.config_lines_only.splitlines())
self.config_lines: t.List[ConfigLine] = []
self.build_config_relationship()
|
def _build_banner(self, config_line):
IOSConfigParser
IOSConfigParser
| def _build_banner(self, config_line: str) -> t.Optional[str]:
"""Handle banner config lines.
Args:
config_line: The start of the banner config.
Returns:
The next configuration line in the configuration text or None when banner end is the end of the config text.
Raises:
ValueError: When the parser is unable to identify the End of the Banner.
"""
config_line = normalise_delimiter_caret_c(self.banner_end, config_line)
return super(IOSConfigParser, self)._build_banner(config_line)
|
CiscoConfigParser
| def _build_banner(self, config_line: str) -> t.Optional[str]:
"""Handle banner config lines.
Args:
config_line: The start of the banner config.
Returns:
The next configuration line in the configuration text or None when banner end is the end of the config text.
Raises:
ValueError: When the parser is unable to identify the End of the Banner.
"""
if self.is_banner_one_line(config_line):
self._update_config_lines(config_line)
try:
return next(self.generator_config)
except StopIteration:
return None
return super(CiscoConfigParser, self)._build_banner(config_line)
|
BaseSpaceConfigParser
| def _build_banner(self, config_line: str) -> t.Optional[str]:
"""Handle banner config lines.
Args:
config_line: The start of the banner config.
Returns:
The next configuration line in the configuration text or None
Raises:
ValueError: When the parser is unable to identify the end of the Banner.
"""
self._update_config_lines(config_line)
self._current_parents += (config_line,)
banner_config = []
for line in self.generator_config:
if not self.is_banner_end(line):
banner_config.append(line)
else:
line = normalise_delimiter_caret_c(self.banner_end, line)
banner_config.append(line)
line = "\n".join(banner_config)
if line.endswith("^C"):
banner, end, _ = line.rpartition("^C")
line = banner.rstrip() + end
self._update_config_lines(line)
self._current_parents = self._current_parents[:-1]
try:
return next(self.generator_config)
except StopIteration:
return None
raise ValueError("Unable to parse banner end.")
|
def _build_nested_config(self, line):
BaseSpaceConfigParser
| def _build_nested_config(self, line: str) -> t.Optional[str]:
"""Handle building child config sections.
Args:
line: A configuration line from the configuration text.
Returns:
The next top-level configuration line in the configuration text or None when the last line of configuration
text is a nested configuration line.
Raises:
IndexError: When the number of parents does not match the expected deindent level.
"""
self._update_config_lines(line)
for line in self.generator_config:
if not line[0].isspace():
self._current_parents = ()
self.indent_level = 0
return line
spaces = self.get_leading_space_count(line)
if spaces == self.indent_level:
pass
elif spaces > self.indent_level:
previous_config = self.config_lines[-1]
self._current_parents += (previous_config.config_line,)
else:
self._current_parents = self._remove_parents(line, spaces)
if spaces != self.indent_level:
self.indent_level = spaces
if self.is_banner_start(line):
banner_line = self._build_banner(
|