def__init__(self,config:str):"""Create ConfigParser Object. Args: config (str): The config text to parse. """self._indent_level=0super(BaseSpaceConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config: The config text to parse. """self.config=configself._config:t.Optional[str]=Noneself._current_parents:t.Tuple[str,...]=()self.generator_config=(lineforlineinself.config_lines_only.splitlines())self.config_lines:t.List[ConfigLine]=[]self.build_config_relationship()
def_build_banner(self,config_line:str)->t.Optional[str]:"""Build banner specific to ADVA AOS devices. Args: config_line: The start of the banner config. Returns: The next configuration line in the configuration text or None. Raises: ValueError when parser is unable to identify the banner end. """ifconfig_line.endswith(self.banner_end):self._update_config_lines(config_line)try:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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=[]forlineinself.generator_config:ifnotself.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)ifline.endswith("^C"):banner,end,_=line.rpartition("^C")line=banner+endself._update_config_lines(line)self._current_parents=self._current_parents[:-1]try:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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)forlineinself.generator_config:ifnotline[0].isspace():self._current_parents=()self.indent_level=0returnlinespaces=self.get_leading_space_count(line)ifspaces==self.indent_level:passelifspaces>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)ifspaces!=self.indent_level:self.indent_level=spacesifself.is_banner_start(line):banner_line=self._build_banner(line)ifbanner_lineisNoneornotbanner_line[0].isspace():self._current_parents=()self.indent_level=0returnbanner_lineline=banner_lineself._update_config_lines(line)returnNone
@staticmethoddef_match_type_check(line:str,pattern:str,match_type:str)->bool:"""Checks pattern for exact match or regex."""ifmatch_type=="exact"andline==pattern:returnTrueifmatch_type=="startswith"andline.startswith(pattern):returnTrueifmatch_type=="endswith"andline.endswith(pattern):returnTrueifmatch_type=="regex"andre.match(pattern,line):returnTruereturnFalse
def_remove_parents(self,line:str,current_spaces:int)->t.Tuple[str,...]:"""Remove parents from ``self._curent_parents`` based on indent levels. Args: line: A line of text in the config. current_spaces: The number of spaces the current line is indented. Returns: The config lines parent config lines. """deindent_level=1try:previous_parent=self._current_parents[-1]previous_indent=self.get_leading_space_count(previous_parent)whileprevious_indent>current_spaces:deindent_level+=1previous_parent=self._current_parents[-deindent_level]previous_indent=self.get_leading_space_count(previous_parent)exceptIndexError:raiseIndexError(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],)returnparents
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)
@staticmethoddefget_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 >>> """returnlen(config_line)-len(config_line.lstrip())
defis_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. """ifself.banner_endinline:returnTruereturnFalse
defis_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. """forbanner_startinself.banner_start:ifnotline:returnFalseifline.lstrip().startswith(banner_start):returnTruereturnFalse
defis_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 >>> """forcomment_charinself.comment_chars:ifline.lstrip().startswith(comment_char):returnTruereturnFalse
def__init__(self,config:str):"""Create ConfigParser Object. Args: config (str): The config text to parse. """self._indent_level=0super(BaseSpaceConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config: The config text to parse. """self.config=configself._config:t.Optional[str]=Noneself._current_parents:t.Tuple[str,...]=()self.generator_config=(lineforlineinself.config_lines_only.splitlines())self.config_lines:t.List[ConfigLine]=[]self.build_config_relationship()
def_build_banner(self,config_line:str)->t.Optional[str]:"""Build banner specific to ADVA AOS devices. Args: config_line: The start of the banner config. Returns: The next configuration line in the configuration text or None. Raises: ValueError when parser is unable to identify the banner end. """ifconfig_line.endswith(self.banner_end):self._update_config_lines(config_line)try:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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=[]forlineinself.generator_config:ifnotself.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)ifline.endswith("^C"):banner,end,_=line.rpartition("^C")line=banner+endself._update_config_lines(line)self._current_parents=self._current_parents[:-1]try:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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)forlineinself.generator_config:ifnotline[0].isspace():self._current_parents=()self.indent_level=0returnlinespaces=self.get_leading_space_count(line)ifspaces==self.indent_level:passelifspaces>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)ifspaces!=self.indent_level:self.indent_level=spacesifself.is_banner_start(line):banner_line=self._build_banner(line)ifbanner_lineisNoneornotbanner_line[0].isspace():self._current_parents=()self.indent_level=0returnbanner_lineline=banner_lineself._update_config_lines(line)returnNone
@staticmethoddef_match_type_check(line:str,pattern:str,match_type:str)->bool:"""Checks pattern for exact match or regex."""ifmatch_type=="exact"andline==pattern:returnTrueifmatch_type=="startswith"andline.startswith(pattern):returnTrueifmatch_type=="endswith"andline.endswith(pattern):returnTrueifmatch_type=="regex"andre.match(pattern,line):returnTruereturnFalse
def_remove_parents(self,line:str,current_spaces:int)->t.Tuple[str,...]:"""Remove parents from ``self._curent_parents`` based on indent levels. Args: line: A line of text in the config. current_spaces: The number of spaces the current line is indented. Returns: The config lines parent config lines. """deindent_level=1try:previous_parent=self._current_parents[-1]previous_indent=self.get_leading_space_count(previous_parent)whileprevious_indent>current_spaces:deindent_level+=1previous_parent=self._current_parents[-deindent_level]previous_indent=self.get_leading_space_count(previous_parent)exceptIndexError:raiseIndexError(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],)returnparents
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)
@staticmethoddefget_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 >>> """returnlen(config_line)-len(config_line.lstrip())
defis_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. """ifself.banner_endinline:returnTruereturnFalse
defis_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. """forbanner_startinself.banner_start:ifnotline:returnFalseifline.lstrip().startswith(banner_start):returnTruereturnFalse
defis_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 >>> """forcomment_charinself.comment_chars:ifline.lstrip().startswith(comment_char):returnTruereturnFalse
def__init__(self,config:str):"""Create ConfigParser Object. Args: config (str): The config text to parse. """self._banner_end:t.Optional[str]=Nonesuper(CiscoConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config (str): The config text to parse. """self._indent_level=0super(BaseSpaceConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config: The config text to parse. """self.config=configself._config:t.Optional[str]=Noneself._current_parents:t.Tuple[str,...]=()self.generator_config=(lineforlineinself.config_lines_only.splitlines())self.config_lines:t.List[ConfigLine]=[]self.build_config_relationship()
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. """ifself.is_banner_one_line(config_line):self._update_config_lines(config_line)try:returnnext(self.generator_config)exceptStopIteration:returnNonereturnsuper(CiscoConfigParser,self)._build_banner(config_line)
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=[]forlineinself.generator_config:ifnotself.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)ifline.endswith("^C"):banner,end,_=line.rpartition("^C")line=banner+endself._update_config_lines(line)self._current_parents=self._current_parents[:-1]try:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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)forlineinself.generator_config:ifnotline[0].isspace():self._current_parents=()self.indent_level=0returnlinespaces=self.get_leading_space_count(line)ifspaces==self.indent_level:passelifspaces>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)ifspaces!=self.indent_level:self.indent_level=spacesifself.is_banner_start(line):banner_line=self._build_banner(line)ifbanner_lineisNoneornotbanner_line[0].isspace():self._current_parents=()self.indent_level=0returnbanner_lineline=banner_lineself._update_config_lines(line)returnNone
@staticmethoddef_match_type_check(line:str,pattern:str,match_type:str)->bool:"""Checks pattern for exact match or regex."""ifmatch_type=="exact"andline==pattern:returnTrueifmatch_type=="startswith"andline.startswith(pattern):returnTrueifmatch_type=="endswith"andline.endswith(pattern):returnTrueifmatch_type=="regex"andre.match(pattern,line):returnTruereturnFalse
def_remove_parents(self,line:str,current_spaces:int)->t.Tuple[str,...]:"""Remove parents from ``self._curent_parents`` based on indent levels. Args: line: A line of text in the config. current_spaces: The number of spaces the current line is indented. Returns: The config lines parent config lines. """deindent_level=1try:previous_parent=self._current_parents[-1]previous_indent=self.get_leading_space_count(previous_parent)whileprevious_indent>current_spaces:deindent_level+=1previous_parent=self._current_parents[-deindent_level]previous_indent=self.get_leading_space_count(previous_parent)exceptIndexError:raiseIndexError(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],)returnparents
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)
@staticmethoddefget_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 >>> """returnlen(config_line)-len(config_line.lstrip())
defis_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. """ifself.banner_endinline:returnTruereturnFalse
@staticmethoddefis_banner_one_line(config_line:str)->bool:"""Determine if all banner config is on one line."""_,delimeter,banner=config_line.partition("^C")# if the banner is the delimeter is a single line empty banner. e.g banner motd ^C^C which ios allows.ifbanner=="^C":returnTrue# Based on NXOS configs, the banner delimeter is ignored until another char is usedbanner_config_start=banner.lstrip(delimeter)ifdelimeternotinbanner_config_start:returnFalsereturnTrue
defis_banner_start(self,line:str)->bool:"""Determine if the line starts a banner config."""state=super(CiscoConfigParser,self).is_banner_start(line)ifstate:self.banner_end=linereturnstate
defis_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. """forbanner_startinself.banner_start:ifnotline:returnFalseifline.lstrip().startswith(banner_start):returnTruereturnFalse
defis_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 >>> """forcomment_charinself.comment_chars:ifline.lstrip().startswith(comment_char):returnTruereturnFalse
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)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config (str): The config text to parse. """self._banner_end:t.Optional[str]=Nonesuper(CiscoConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config (str): The config text to parse. """self._indent_level=0super(BaseSpaceConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config: The config text to parse. """self.config=configself._config:t.Optional[str]=Noneself._current_parents:t.Tuple[str,...]=()self.generator_config=(lineforlineinself.config_lines_only.splitlines())self.config_lines:t.List[ConfigLine]=[]self.build_config_relationship()
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. """ifself.is_banner_one_line(config_line):self._update_config_lines(config_line)try:returnnext(self.generator_config)exceptStopIteration:returnNonereturnsuper(CiscoConfigParser,self)._build_banner(config_line)
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=[]forlineinself.generator_config:ifnotself.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)ifline.endswith("^C"):banner,end,_=line.rpartition("^C")line=banner+endself._update_config_lines(line)self._current_parents=self._current_parents[:-1]try:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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)forlineinself.generator_config:ifnotline[0].isspace():self._current_parents=()self.indent_level=0returnlinespaces=self.get_leading_space_count(line)ifspaces==self.indent_level:passelifspaces>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)ifspaces!=self.indent_level:self.indent_level=spacesifself.is_banner_start(line):banner_line=self._build_banner(line)ifbanner_lineisNoneornotbanner_line[0].isspace():self._current_parents=()self.indent_level=0returnbanner_lineline=banner_lineself._update_config_lines(line)returnNone
@staticmethoddef_match_type_check(line:str,pattern:str,match_type:str)->bool:"""Checks pattern for exact match or regex."""ifmatch_type=="exact"andline==pattern:returnTrueifmatch_type=="startswith"andline.startswith(pattern):returnTrueifmatch_type=="endswith"andline.endswith(pattern):returnTrueifmatch_type=="regex"andre.match(pattern,line):returnTruereturnFalse
def_remove_parents(self,line:str,current_spaces:int)->t.Tuple[str,...]:"""Remove parents from ``self._curent_parents`` based on indent levels. Args: line: A line of text in the config. current_spaces: The number of spaces the current line is indented. Returns: The config lines parent config lines. """deindent_level=1try:previous_parent=self._current_parents[-1]previous_indent=self.get_leading_space_count(previous_parent)whileprevious_indent>current_spaces:deindent_level+=1previous_parent=self._current_parents[-deindent_level]previous_indent=self.get_leading_space_count(previous_parent)exceptIndexError:raiseIndexError(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],)returnparents
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]ifentryinself.unique_config_lines:self.same_line_children.add(entry)self.unique_config_lines.add(entry)
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)
@staticmethoddefget_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 >>> """returnlen(config_line)-len(config_line.lstrip())
defis_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. """ifself.banner_endinline:returnTruereturnFalse
@staticmethoddefis_banner_one_line(config_line:str)->bool:"""Determine if all banner config is on one line."""_,delimeter,banner=config_line.partition("^C")# if the banner is the delimeter is a single line empty banner. e.g banner motd ^C^C which ios allows.ifbanner=="^C":returnTrue# Based on NXOS configs, the banner delimeter is ignored until another char is usedbanner_config_start=banner.lstrip(delimeter)ifdelimeternotinbanner_config_start:returnFalsereturnTrue
defis_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. """forbanner_startinself.banner_start:ifnotline:returnFalseifline.startswith(banner_start):returnTruereturnFalse
defis_banner_start(self,line:str)->bool:"""Determine if the line starts a banner config."""state=super(CiscoConfigParser,self).is_banner_start(line)ifstate:self.banner_end=linereturnstate
defis_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. """forbanner_startinself.banner_start:ifnotline:returnFalseifline.lstrip().startswith(banner_start):returnTruereturnFalse
defis_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 >>> """forcomment_charinself.comment_chars:ifline.lstrip().startswith(comment_char):returnTruereturnFalse
def__init__(self,config:str):"""Create ConfigParser Object. Args: config (str): The config text to parse. """self._indent_level=0super(BaseSpaceConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config: The config text to parse. """self.config=configself._config:t.Optional[str]=Noneself._current_parents:t.Tuple[str,...]=()self.generator_config=(lineforlineinself.config_lines_only.splitlines())self.config_lines:t.List[ConfigLine]=[]self.build_config_relationship()
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=[]forlineinself.generator_config:ifnotself.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:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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=[]forlineinself.generator_config:ifnotself.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)ifline.endswith("^C"):banner,end,_=line.rpartition("^C")line=banner+endself._update_config_lines(line)self._current_parents=self._current_parents[:-1]try:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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)forlineinself.generator_config:ifnotline[0].isspace():self._current_parents=()self.indent_level=0returnlinespaces=self.get_leading_space_count(line)ifspaces==self.indent_level:passelifspaces>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)ifspaces!=self.indent_level:self.indent_level=spacesifself.is_banner_start(line):banner_line=self._build_banner(line)ifbanner_lineisNoneornotbanner_line[0].isspace():self._current_parents=()self.indent_level=0returnbanner_lineline=banner_lineself._update_config_lines(line)returnNone
@staticmethoddef_match_type_check(line:str,pattern:str,match_type:str)->bool:"""Checks pattern for exact match or regex."""ifmatch_type=="exact"andline==pattern:returnTrueifmatch_type=="startswith"andline.startswith(pattern):returnTrueifmatch_type=="endswith"andline.endswith(pattern):returnTrueifmatch_type=="regex"andre.match(pattern,line):returnTruereturnFalse
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=Falsebanner_ended=Falseforlineinconfig.splitlines():ifself.is_banner_start(line):banner_started=Truebanner_ended=Falseiflineandbanner_startedandnotbanner_ended:config_lines.append(line.rstrip())ifline.lstrip().startswith(self.banner_end):banner_ended=Truebanner_started=Falseelse:iflineandnotself.is_comment(line):config_lines.append(line.rstrip())full_config="\n".join(config_lines)returnfull_config
def_remove_parents(self,line:str,current_spaces:int)->t.Tuple[str,...]:"""Remove parents from ``self._curent_parents`` based on indent levels. Args: line: A line of text in the config. current_spaces: The number of spaces the current line is indented. Returns: The config lines parent config lines. """deindent_level=1try:previous_parent=self._current_parents[-1]previous_indent=self.get_leading_space_count(previous_parent)whileprevious_indent>current_spaces:deindent_level+=1previous_parent=self._current_parents[-deindent_level]previous_indent=self.get_leading_space_count(previous_parent)exceptIndexError:raiseIndexError(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],)returnparents
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)
@staticmethoddefget_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 >>> """returnlen(config_line)-len(config_line.lstrip())
defis_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. """ifself.banner_endinline:returnTruereturnFalse
defis_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. """forbanner_startinself.banner_start:ifnotline:returnFalseifline.lstrip().startswith(banner_start):returnTruereturnFalse
defis_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 >>> """forcomment_charinself.comment_chars:ifline.lstrip().startswith(comment_char):returnTruereturnFalse
def__init__(self,config:str):"""Create ConfigParser Object. Args: config (str): The config text to parse. """self._indent_level=0super(BaseSpaceConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config: The config text to parse. """self.config=configself._config:t.Optional[str]=Noneself._current_parents:t.Tuple[str,...]=()self.generator_config=(lineforlineinself.config_lines_only.splitlines())self.config_lines:t.List[ConfigLine]=[]self.build_config_relationship()
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=[]forlineinself.generator_config:ifnotself.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:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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=[]forlineinself.generator_config:ifnotself.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)ifline.endswith("^C"):banner,end,_=line.rpartition("^C")line=banner+endself._update_config_lines(line)self._current_parents=self._current_parents[:-1]try:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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)forlineinself.generator_config:ifnotline[0].isspace():self._current_parents=()self.indent_level=0returnlinespaces=self.get_leading_space_count(line)ifspaces==self.indent_level:passelifspaces>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)ifspaces!=self.indent_level:self.indent_level=spacesifself.is_banner_start(line):banner_line=self._build_banner(line)ifbanner_lineisNoneornotbanner_line[0].isspace():self._current_parents=()self.indent_level=0returnbanner_lineline=banner_lineself._update_config_lines(line)returnNone
@staticmethoddef_match_type_check(line:str,pattern:str,match_type:str)->bool:"""Checks pattern for exact match or regex."""ifmatch_type=="exact"andline==pattern:returnTrueifmatch_type=="startswith"andline.startswith(pattern):returnTrueifmatch_type=="endswith"andline.endswith(pattern):returnTrueifmatch_type=="regex"andre.match(pattern,line):returnTruereturnFalse
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=Falsebanner_ended=Falseforlineinconfig.splitlines():ifself.is_banner_start(line):banner_started=Truebanner_ended=Falseiflineandbanner_startedandnotbanner_ended:config_lines.append(line.rstrip())ifline.lstrip().startswith(self.banner_end):banner_ended=Truebanner_started=Falseelse:iflineandnotself.is_comment(line):config_lines.append(line.rstrip())full_config="\n".join(config_lines)returnfull_config
def_remove_parents(self,line:str,current_spaces:int)->t.Tuple[str,...]:"""Remove parents from ``self._curent_parents`` based on indent levels. Args: line: A line of text in the config. current_spaces: The number of spaces the current line is indented. Returns: The config lines parent config lines. """deindent_level=1try:previous_parent=self._current_parents[-1]previous_indent=self.get_leading_space_count(previous_parent)whileprevious_indent>current_spaces:deindent_level+=1previous_parent=self._current_parents[-deindent_level]previous_indent=self.get_leading_space_count(previous_parent)exceptIndexError:raiseIndexError(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],)returnparents
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)
@staticmethoddefget_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 >>> """returnlen(config_line)-len(config_line.lstrip())
defis_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. """ifself.banner_endinline:returnTruereturnFalse
defis_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. """forbanner_startinself.banner_start:ifnotline:returnFalseifline.lstrip().startswith(banner_start):returnTruereturnFalse
defis_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 >>> """forcomment_charinself.comment_chars:ifline.lstrip().startswith(comment_char):returnTruereturnFalse
def__init__(self,config:str):"""Create ConfigParser Object. Args: config (str): The config text to parse. """self._indent_level=0super(BaseSpaceConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config: The config text to parse. """self.config=configself._config:t.Optional[str]=Noneself._current_parents:t.Tuple[str,...]=()self.generator_config=(lineforlineinself.config_lines_only.splitlines())self.config_lines:t.List[ConfigLine]=[]self.build_config_relationship()
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=[]forlineinself.generator_config:ifnotself.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:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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=[]forlineinself.generator_config:ifnotself.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)ifline.endswith("^C"):banner,end,_=line.rpartition("^C")line=banner+endself._update_config_lines(line)self._current_parents=self._current_parents[:-1]try:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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)forlineinself.generator_config:ifnotline[0].isspace():self._current_parents=()self.indent_level=0returnlinespaces=self.get_leading_space_count(line)ifspaces==self.indent_level:passelifspaces>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)ifspaces!=self.indent_level:self.indent_level=spacesifself.is_banner_start(line):banner_line=self._build_banner(line)ifbanner_lineisNoneornotbanner_line[0].isspace():self._current_parents=()self.indent_level=0returnbanner_lineline=banner_lineself._update_config_lines(line)returnNone
@staticmethoddef_match_type_check(line:str,pattern:str,match_type:str)->bool:"""Checks pattern for exact match or regex."""ifmatch_type=="exact"andline==pattern:returnTrueifmatch_type=="startswith"andline.startswith(pattern):returnTrueifmatch_type=="endswith"andline.endswith(pattern):returnTrueifmatch_type=="regex"andre.match(pattern,line):returnTruereturnFalse
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=Falsebanner_ended=Falseforlineinconfig.splitlines():ifself.is_banner_start(line):banner_started=Truebanner_ended=Falseiflineandbanner_startedandnotbanner_ended:config_lines.append(line.rstrip())ifline.lstrip().startswith(self.banner_end):banner_ended=Truebanner_started=Falseelse:iflineandnotself.is_comment(line):config_lines.append(line.rstrip())full_config="\n".join(config_lines)returnfull_config
def_remove_parents(self,line:str,current_spaces:int)->t.Tuple[str,...]:"""Remove parents from ``self._curent_parents`` based on indent levels. Args: line: A line of text in the config. current_spaces: The number of spaces the current line is indented. Returns: The config lines parent config lines. """deindent_level=1try:previous_parent=self._current_parents[-1]previous_indent=self.get_leading_space_count(previous_parent)whileprevious_indent>current_spaces:deindent_level+=1previous_parent=self._current_parents[-deindent_level]previous_indent=self.get_leading_space_count(previous_parent)exceptIndexError:raiseIndexError(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],)returnparents
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)
@staticmethoddefget_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 >>> """returnlen(config_line)-len(config_line.lstrip())
defis_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. """ifself.banner_endinline:returnTruereturnFalse
defis_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. """forbanner_startinself.banner_start:ifnotline:returnFalseifline.lstrip().startswith(banner_start):returnTruereturnFalse
defis_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 >>> """forcomment_charinself.comment_chars:ifline.lstrip().startswith(comment_char):returnTruereturnFalse
def__init__(self,config:str):"""Create ConfigParser Object. Args: config: The config text to parse. """self.config=configself._config:t.Optional[str]=Noneself._current_parents:t.Tuple[str,...]=()self.generator_config=(lineforlineinself.config_lines_only.splitlines())self.config_lines:t.List[ConfigLine]=[]self.build_config_relationship()
def__init__(self,config:str):"""Create ConfigParser Object. Args: config: The config text to parse. """self.config=configself._config:t.Optional[str]=Noneself._current_parents:t.Tuple[str,...]=()self.generator_config=(lineforlineinself.config_lines_only.splitlines())self.config_lines:t.List[ConfigLine]=[]self.build_config_relationship()
def__init__(self,config:str):"""Create ConfigParser Object. Args: config (str): The config text to parse. """self._indent_level=0super(BaseSpaceConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config: The config text to parse. """self.config=configself._config:t.Optional[str]=Noneself._current_parents:t.Tuple[str,...]=()self.generator_config=(lineforlineinself.config_lines_only.splitlines())self.config_lines:t.List[ConfigLine]=[]self.build_config_relationship()
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=[]forlineinself.generator_config:ifnotself.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)ifline.endswith("^C"):banner,end,_=line.rpartition("^C")line=banner+endself._update_config_lines(line)self._current_parents=self._current_parents[:-1]try:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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)forlineinself.generator_config:ifnotline[0].isspace():self._current_parents=()self.indent_level=0returnlinespaces=self.get_leading_space_count(line)ifspaces==self.indent_level:passelifspaces>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)ifspaces!=self.indent_level:self.indent_level=spacesifself.is_banner_start(line):banner_line=self._build_banner(line)ifbanner_lineisNoneornotbanner_line[0].isspace():self._current_parents=()self.indent_level=0returnbanner_lineline=banner_lineself._update_config_lines(line)returnNone
@staticmethoddef_match_type_check(line:str,pattern:str,match_type:str)->bool:"""Checks pattern for exact match or regex."""ifmatch_type=="exact"andline==pattern:returnTrueifmatch_type=="startswith"andline.startswith(pattern):returnTrueifmatch_type=="endswith"andline.endswith(pattern):returnTrueifmatch_type=="regex"andre.match(pattern,line):returnTruereturnFalse
def_remove_parents(self,line:str,current_spaces:int)->t.Tuple[str,...]:"""Remove parents from ``self._curent_parents`` based on indent levels. Args: line: A line of text in the config. current_spaces: The number of spaces the current line is indented. Returns: The config lines parent config lines. """deindent_level=1try:previous_parent=self._current_parents[-1]previous_indent=self.get_leading_space_count(previous_parent)whileprevious_indent>current_spaces:deindent_level+=1previous_parent=self._current_parents[-deindent_level]previous_indent=self.get_leading_space_count(previous_parent)exceptIndexError:raiseIndexError(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],)returnparents
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)
@staticmethoddefget_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 >>> """returnlen(config_line)-len(config_line.lstrip())
defis_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. """ifself.banner_endinline:returnTruereturnFalse
defis_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. """forbanner_startinself.banner_start:ifnotline:returnFalseifline.lstrip().startswith(banner_start):returnTruereturnFalse
defis_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 >>> """forcomment_charinself.comment_chars:ifline.lstrip().startswith(comment_char):returnTruereturnFalse
def__init__(self,config:str):"""Create ConfigParser Object. Args: config (str): The config text to parse. """self._indent_level=0super(BaseSpaceConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config: The config text to parse. """self.config=configself._config:t.Optional[str]=Noneself._current_parents:t.Tuple[str,...]=()self.generator_config=(lineforlineinself.config_lines_only.splitlines())self.config_lines:t.List[ConfigLine]=[]self.build_config_relationship()
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=[]forlineinself.generator_config:ifnotself.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)ifline.endswith("^C"):banner,end,_=line.rpartition("^C")line=banner+endself._update_config_lines(line)self._current_parents=self._current_parents[:-1]try:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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)forlineinself.generator_config:ifnotline[0].isspace():self._current_parents=()self.indent_level=0returnlinespaces=self.get_leading_space_count(line)ifspaces==self.indent_level:passelifspaces>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)ifspaces!=self.indent_level:self.indent_level=spacesifself.is_banner_start(line):banner_line=self._build_banner(line)ifbanner_lineisNoneornotbanner_line[0].isspace():self._current_parents=()self.indent_level=0returnbanner_lineline=banner_lineself._update_config_lines(line)returnNone
@staticmethoddef_match_type_check(line:str,pattern:str,match_type:str)->bool:"""Checks pattern for exact match or regex."""ifmatch_type=="exact"andline==pattern:returnTrueifmatch_type=="startswith"andline.startswith(pattern):returnTrueifmatch_type=="endswith"andline.endswith(pattern):returnTrueifmatch_type=="regex"andre.match(pattern,line):returnTruereturnFalse
def_remove_parents(self,line:str,current_spaces:int)->t.Tuple[str,...]:"""Remove parents from ``self._curent_parents`` based on indent levels. Args: line: A line of text in the config. current_spaces: The number of spaces the current line is indented. Returns: The config lines parent config lines. """deindent_level=1try:previous_parent=self._current_parents[-1]previous_indent=self.get_leading_space_count(previous_parent)whileprevious_indent>current_spaces:deindent_level+=1previous_parent=self._current_parents[-deindent_level]previous_indent=self.get_leading_space_count(previous_parent)exceptIndexError:raiseIndexError(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],)returnparents
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)
@staticmethoddefget_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 >>> """returnlen(config_line)-len(config_line.lstrip())
defis_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. """ifself.banner_endinline:returnTruereturnFalse
defis_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. """forbanner_startinself.banner_start:ifnotline:returnFalseifline.lstrip().startswith(banner_start):returnTruereturnFalse
defis_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 >>> """forcomment_charinself.comment_chars:ifline.lstrip().startswith(comment_char):returnTruereturnFalse
def__init__(self,config:str):"""Create ConfigParser Object. Args: config (str): The config text to parse. """self._banner_end:t.Optional[str]=Nonesuper(CiscoConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config (str): The config text to parse. """self._indent_level=0super(BaseSpaceConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config: The config text to parse. """self.config=configself._config:t.Optional[str]=Noneself._current_parents:t.Tuple[str,...]=()self.generator_config=(lineforlineinself.config_lines_only.splitlines())self.config_lines:t.List[ConfigLine]=[]self.build_config_relationship()
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. """ifself.is_banner_one_line(config_line):self._update_config_lines(config_line)try:returnnext(self.generator_config)exceptStopIteration:returnNonereturnsuper(CiscoConfigParser,self)._build_banner(config_line)
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=[]forlineinself.generator_config:ifnotself.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)ifline.endswith("^C"):banner,end,_=line.rpartition("^C")line=banner+endself._update_config_lines(line)self._current_parents=self._current_parents[:-1]try:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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)forlineinself.generator_config:ifnotline[0].isspace():self._current_parents=()self.indent_level=0returnlinespaces=self.get_leading_space_count(line)ifspaces==self.indent_level:passelifspaces>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)ifspaces!=self.indent_level:self.indent_level=spacesifself.is_banner_start(line):banner_line=self._build_banner(line)ifbanner_lineisNoneornotbanner_line[0].isspace():self._current_parents=()self.indent_level=0returnbanner_lineline=banner_lineself._update_config_lines(line)returnNone
@staticmethoddef_match_type_check(line:str,pattern:str,match_type:str)->bool:"""Checks pattern for exact match or regex."""ifmatch_type=="exact"andline==pattern:returnTrueifmatch_type=="startswith"andline.startswith(pattern):returnTrueifmatch_type=="endswith"andline.endswith(pattern):returnTrueifmatch_type=="regex"andre.match(pattern,line):returnTruereturnFalse
def_remove_parents(self,line:str,current_spaces:int)->t.Tuple[str,...]:"""Remove parents from ``self._curent_parents`` based on indent levels. Args: line: A line of text in the config. current_spaces: The number of spaces the current line is indented. Returns: The config lines parent config lines. """deindent_level=1try:previous_parent=self._current_parents[-1]previous_indent=self.get_leading_space_count(previous_parent)whileprevious_indent>current_spaces:deindent_level+=1previous_parent=self._current_parents[-deindent_level]previous_indent=self.get_leading_space_count(previous_parent)exceptIndexError:raiseIndexError(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],)returnparents
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)
@staticmethoddefget_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 >>> """returnlen(config_line)-len(config_line.lstrip())
defis_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. """ifself.banner_endinline:returnTruereturnFalse
@staticmethoddefis_banner_one_line(config_line:str)->bool:"""Determine if all banner config is on one line."""_,delimeter,banner=config_line.partition("^C")# if the banner is the delimeter is a single line empty banner. e.g banner motd ^C^C which ios allows.ifbanner=="^C":returnTrue# Based on NXOS configs, the banner delimeter is ignored until another char is usedbanner_config_start=banner.lstrip(delimeter)ifdelimeternotinbanner_config_start:returnFalsereturnTrue
defis_banner_start(self,line:str)->bool:"""Determine if the line starts a banner config."""state=super(CiscoConfigParser,self).is_banner_start(line)ifstate:self.banner_end=linereturnstate
defis_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. """forbanner_startinself.banner_start:ifnotline:returnFalseifline.lstrip().startswith(banner_start):returnTruereturnFalse
defis_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 >>> """forcomment_charinself.comment_chars:ifline.lstrip().startswith(comment_char):returnTruereturnFalse
def__init__(self,config:str):"""Create ConfigParser Object. Args: config (str): The config text to parse. """self._indent_level=0super(BaseSpaceConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config: The config text to parse. """self.config=configself._config:t.Optional[str]=Noneself._current_parents:t.Tuple[str,...]=()self.generator_config=(lineforlineinself.config_lines_only.splitlines())self.config_lines:t.List[ConfigLine]=[]self.build_config_relationship()
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=[]forlineinself.generator_config:ifnotself.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:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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=[]forlineinself.generator_config:ifnotself.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)ifline.endswith("^C"):banner,end,_=line.rpartition("^C")line=banner+endself._update_config_lines(line)self._current_parents=self._current_parents[:-1]try:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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)forlineinself.generator_config:ifnotline[0].isspace():self._current_parents=()self.indent_level=0returnlinespaces=self.get_leading_space_count(line)ifspaces==self.indent_level:passelifspaces>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)ifspaces!=self.indent_level:self.indent_level=spacesifself.is_banner_start(line):banner_line=self._build_banner(line)ifbanner_lineisNoneornotbanner_line[0].isspace():self._current_parents=()self.indent_level=0returnbanner_lineline=banner_lineself._update_config_lines(line)returnNone
@staticmethoddef_match_type_check(line:str,pattern:str,match_type:str)->bool:"""Checks pattern for exact match or regex."""ifmatch_type=="exact"andline==pattern:returnTrueifmatch_type=="startswith"andline.startswith(pattern):returnTrueifmatch_type=="endswith"andline.endswith(pattern):returnTrueifmatch_type=="regex"andre.match(pattern,line):returnTruereturnFalse
def_remove_parents(self,line:str,current_spaces:int)->t.Tuple[str,...]:"""Remove parents from ``self._curent_parents`` based on indent levels. Args: line: A line of text in the config. current_spaces: The number of spaces the current line is indented. Returns: The config lines parent config lines. """deindent_level=1try:previous_parent=self._current_parents[-1]previous_indent=self.get_leading_space_count(previous_parent)whileprevious_indent>current_spaces:deindent_level+=1previous_parent=self._current_parents[-deindent_level]previous_indent=self.get_leading_space_count(previous_parent)exceptIndexError:raiseIndexError(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],)returnparents
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)
@staticmethoddefget_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 >>> """returnlen(config_line)-len(config_line.lstrip())
defis_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. """ifself.banner_endinline:returnTruereturnFalse
defis_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. """forbanner_startinself.banner_start:ifnotline:returnFalseifline.lstrip().startswith(banner_start):returnTruereturnFalse
defis_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 >>> """forcomment_charinself.comment_chars:ifline.lstrip().startswith(comment_char):returnTruereturnFalse
def__init__(self,config:str):"""Create ConfigParser Object. Args: config: The config text to parse. """self.config=configself._config:t.Optional[str]=Noneself._current_parents:t.Tuple[str,...]=()self.generator_config=(lineforlineinself.config_lines_only.splitlines())self.config_lines:t.List[ConfigLine]=[]self.build_config_relationship()
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")iflen(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_configelse:final_config=config_textreturnfinal_config
def__init__(self,config:str):"""Create ConfigParser Object. Args: config (str): The config text to parse. """super(FastironConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config (str): The config text to parse. """self._banner_end:t.Optional[str]=Nonesuper(CiscoConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config (str): The config text to parse. """self._indent_level=0super(BaseSpaceConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config: The config text to parse. """self.config=configself._config:t.Optional[str]=Noneself._current_parents:t.Tuple[str,...]=()self.generator_config=(lineforlineinself.config_lines_only.splitlines())self.config_lines:t.List[ConfigLine]=[]self.build_config_relationship()
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=[]forlineinself.generator_config:ifnotself.is_banner_end(line):banner_config.append(line)else:banner_config.append(line)line="\n".join(banner_config)ifline.endswith(self.banner_end):banner,end,_=line.rpartition(self.banner_end)line=banner+endself._update_config_lines(line)self._current_parents=self._current_parents[:-1]try:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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. """ifself.is_banner_one_line(config_line):self._update_config_lines(config_line)try:returnnext(self.generator_config)exceptStopIteration:returnNonereturnsuper(CiscoConfigParser,self)._build_banner(config_line)
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=[]forlineinself.generator_config:ifnotself.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)ifline.endswith("^C"):banner,end,_=line.rpartition("^C")line=banner+endself._update_config_lines(line)self._current_parents=self._current_parents[:-1]try:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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)forlineinself.generator_config:ifnotline[0].isspace():self._current_parents=()self.indent_level=0returnlinespaces=self.get_leading_space_count(line)ifspaces==self.indent_level:passelifspaces>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)ifspaces!=self.indent_level:self.indent_level=spacesifself.is_banner_start(line):banner_line=self._build_banner(line)ifbanner_lineisNoneornotbanner_line[0].isspace():self._current_parents=()self.indent_level=0returnbanner_lineline=banner_lineself._update_config_lines(line)returnNone
@staticmethoddef_match_type_check(line:str,pattern:str,match_type:str)->bool:"""Checks pattern for exact match or regex."""ifmatch_type=="exact"andline==pattern:returnTrueifmatch_type=="startswith"andline.startswith(pattern):returnTrueifmatch_type=="endswith"andline.endswith(pattern):returnTrueifmatch_type=="regex"andre.match(pattern,line):returnTruereturnFalse
def_remove_parents(self,line:str,current_spaces:int)->t.Tuple[str,...]:"""Remove parents from ``self._curent_parents`` based on indent levels. Args: line: A line of text in the config. current_spaces: The number of spaces the current line is indented. Returns: The config lines parent config lines. """deindent_level=1try:previous_parent=self._current_parents[-1]previous_indent=self.get_leading_space_count(previous_parent)whileprevious_indent>current_spaces:deindent_level+=1previous_parent=self._current_parents[-deindent_level]previous_indent=self.get_leading_space_count(previous_parent)exceptIndexError:raiseIndexError(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],)returnparents
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)
@staticmethoddefget_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 >>> """returnlen(config_line)-len(config_line.lstrip())
defis_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. """ifself.banner_endinline:returnTruereturnFalse
@staticmethoddefis_banner_one_line(config_line:str)->bool:"""Determine if all banner config is on one line."""_,delimeter,banner=config_line.partition("^C")# if the banner is the delimeter is a single line empty banner. e.g banner motd ^C^C which ios allows.ifbanner=="^C":returnTrue# Based on NXOS configs, the banner delimeter is ignored until another char is usedbanner_config_start=banner.lstrip(delimeter)ifdelimeternotinbanner_config_start:returnFalsereturnTrue
defis_banner_start(self,line:str)->bool:"""Determine if the line starts a banner config."""state=super(CiscoConfigParser,self).is_banner_start(line)ifstate:self.banner_end=linereturnstate
defis_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. """forbanner_startinself.banner_start:ifnotline:returnFalseifline.lstrip().startswith(banner_start):returnTruereturnFalse
defis_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 >>> """forcomment_charinself.comment_chars:ifline.lstrip().startswith(comment_char):returnTruereturnFalse
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)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config (str): The config text to parse. """self._indent_level=0super(BaseSpaceConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config: The config text to parse. """self.config=configself._config:t.Optional[str]=Noneself._current_parents:t.Tuple[str,...]=()self.generator_config=(lineforlineinself.config_lines_only.splitlines())self.config_lines:t.List[ConfigLine]=[]self.build_config_relationship()
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=[]forlineinself.generator_config:ifnotself.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)ifline.endswith("^C"):banner,end,_=line.rpartition("^C")line=banner+endself._update_config_lines(line)self._current_parents=self._current_parents[:-1]try:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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"["inline:updated_line=self.uncommon_data.get(line.split('"')[1],None)ifnotupdated_line:raiseValueError("Input line is malformed.")line=updated_lineself._update_config_lines(line)forlineinself.generator_config:ifnotline[0].isspace():self._current_parents=()self.indent_level=0returnlinespaces=self.get_leading_space_count(line)ifspaces==self.indent_level:passelifspaces>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)ifspaces!=self.indent_level:self.indent_level=spacesself._update_config_lines(line)returnNone
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)forlineinself.generator_config:ifnotline[0].isspace():self._current_parents=()self.indent_level=0returnlinespaces=self.get_leading_space_count(line)ifspaces==self.indent_level:passelifspaces>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)ifspaces!=self.indent_level:self.indent_level=spacesifself.is_banner_start(line):banner_line=self._build_banner(line)ifbanner_lineisNoneornotbanner_line[0].isspace():self._current_parents=()self.indent_level=0returnbanner_lineline=banner_lineself._update_config_lines(line)returnNone
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={}forgroup_matchinregex_result:result.update({group_match[0].split('"')[1]:group_match[1]})returnresult
@staticmethoddef_match_type_check(line:str,pattern:str,match_type:str)->bool:"""Checks pattern for exact match or regex."""ifmatch_type=="exact"andline==pattern:returnTrueifmatch_type=="startswith"andline.startswith(pattern):returnTrueifmatch_type=="endswith"andline.endswith(pattern):returnTrueifmatch_type=="regex"andre.match(pattern,line):returnTruereturnFalse
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)"returnre.sub(pattern,r"\1 [\2]\n",config)
def_remove_parents(self,line:str,current_spaces:int)->t.Tuple[str,...]:"""Remove parents from ``self._curent_parents`` based on indent levels. Args: line: A line of text in the config. current_spaces: The number of spaces the current line is indented. Returns: The config lines parent config lines. """deindent_level=1try:previous_parent=self._current_parents[-1]previous_indent=self.get_leading_space_count(previous_parent)whileprevious_indent>current_spaces:deindent_level+=1previous_parent=self._current_parents[-deindent_level]previous_indent=self.get_leading_space_count(previous_parent)exceptIndexError:raiseIndexError(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],)returnparents
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)
@staticmethoddefget_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 >>> """returnlen(config_line)-len(config_line.lstrip())
defis_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. """ifself.banner_endinline:returnTruereturnFalse
defis_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. """forbanner_startinself.banner_start:ifnotline:returnFalseifline.lstrip().startswith(banner_start):returnTruereturnFalse
defis_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 >>> """forcomment_charinself.comment_chars:ifline.lstrip().startswith(comment_char):returnTruereturnFalse
defis_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 >>> """forend_nextin["end","next"]:ifline.lstrip()==end_next:returnTruereturnFalse
def__init__(self,config:str):"""Initialize the _HPEConfigParser object."""self.delimiter=""self._banner_end:t.Optional[str]=Nonesuper(_HPEConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config (str): The config text to parse. """self._indent_level=0super(BaseSpaceConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config: The config text to parse. """self.config=configself._config:t.Optional[str]=Noneself._current_parents:t.Tuple[str,...]=()self.generator_config=(lineforlineinself.config_lines_only.splitlines())self.config_lines:t.List[ConfigLine]=[]self.build_config_relationship()
def_build_banner(self,config_line:str)->t.Optional[str]:""" Builds a banner configuration based on the given config_line. Args: config_line (str): The configuration line to process. Returns: Optional[str]: The next configuration line, or None if there are no more lines. Raises: ValueError: If the banner end cannot be parsed. """ifself.is_banner_one_line(config_line):self._update_config_lines(config_line)try:returnnext(self.generator_config)exceptStopIteration:returnNoneself._update_config_lines(config_line)self._current_parents+=(config_line,)banner_config=[]forlineinself.generator_config:ifnotself.is_banner_end(line):banner_config.append(line)else:banner_config.append(line)line="\n".join(banner_config)ifline.endswith(self.delimiter):banner,end,_=line.rpartition(self.delimiter)line=banner+endself._update_config_lines(line)self._current_parents=self._current_parents[:-1]try:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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=[]forlineinself.generator_config:ifnotself.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)ifline.endswith("^C"):banner,end,_=line.rpartition("^C")line=banner+endself._update_config_lines(line)self._current_parents=self._current_parents[:-1]try:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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)forlineinself.generator_config:ifnotline[0].isspace():self._current_parents=()self.indent_level=0returnlinespaces=self.get_leading_space_count(line)ifspaces==self.indent_level:passelifspaces>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)ifspaces!=self.indent_level:self.indent_level=spacesifself.is_banner_start(line):banner_line=self._build_banner(line)ifbanner_lineisNoneornotbanner_line[0].isspace():self._current_parents=()self.indent_level=0returnbanner_lineline=banner_lineself._update_config_lines(line)returnNone
@staticmethoddef_match_type_check(line:str,pattern:str,match_type:str)->bool:"""Checks pattern for exact match or regex."""ifmatch_type=="exact"andline==pattern:returnTrueifmatch_type=="startswith"andline.startswith(pattern):returnTrueifmatch_type=="endswith"andline.endswith(pattern):returnTrueifmatch_type=="regex"andre.match(pattern,line):returnTruereturnFalse
def_remove_parents(self,line:str,current_spaces:int)->t.Tuple[str,...]:"""Remove parents from ``self._curent_parents`` based on indent levels. Args: line: A line of text in the config. current_spaces: The number of spaces the current line is indented. Returns: The config lines parent config lines. """deindent_level=1try:previous_parent=self._current_parents[-1]previous_indent=self.get_leading_space_count(previous_parent)whileprevious_indent>current_spaces:deindent_level+=1previous_parent=self._current_parents[-deindent_level]previous_indent=self.get_leading_space_count(previous_parent)exceptIndexError:raiseIndexError(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],)returnparents
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)
defbuild_config_relationship(self)->t.List[ConfigLine]:r"""This is a custom build method for HPE Network OS. HP config is a bit different from other network operating systems. It uses comments (#) to demarcate sections of the config. Each new section that starts without a leading space is a new section. That new section may or may not have children. Each config line that has a leading space but not a parent is just a single config line. Single lines that have leading spaces also sometimes differs between models (e.g., 59XX vs 79XX series). Examples: >>> from netutils.config.parser import _HPEConfigParser, ConfigLine >>> config = '''# ... version 7.1.045, Release 2418P06 ... # ... sysname NTC123456 ... # ... vlan 101 ... name Test-Vlan-101 ... description Test Vlan 101 ... #''' >>> config_tree = _HPEConfigParser(config) >>> config_tree.build_config_relationship() == \ ... [ ... ConfigLine(config_line="version 7.1.045, Release 2418P06", parents=()), ... ConfigLine(config_line=" sysname NTC123456", parents=()), ... ConfigLine(config_line="vlan 101", parents=()), ... ConfigLine(config_line=" name Test-Vlan-101", parents=("vlan 101",)), ... ConfigLine(config_line=" description Test Vlan 101", parents=("vlan 101",)), ... ] True >>> """new_section=Trueforlineinself.generator_config:ifline.startswith(tuple(self.comment_chars)):# Closing any previous sectionsself._current_parents=()self.indent_level=0new_section=Truecontinueifline.strip().startswith(tuple(self.comment_chars)):# Just ignore comments inside sectionscontinueifself.is_banner_start(line):# Special case for bannersself._build_banner(line)continuecurrent_spaces=self.get_leading_space_count(line)ifline[0].isspace()else0ifcurrent_spaces>self.indent_levelandnotnew_section:previous_config=self.config_lines[-1]self._current_parents+=(previous_config.config_line,)elifcurrent_spaces<self.indent_level:self._current_parents=self._remove_parents(line,current_spaces)new_section=Falseself.indent_level=current_spacesself._update_config_lines(line)returnself.config_lines
@staticmethoddefget_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 >>> """returnlen(config_line)-len(config_line.lstrip())
defis_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. """ifself.banner_endinline:returnTruereturnFalse
defis_banner_one_line(self,config_line:str)->bool:"""Checks if the given configuration line represents a one-line banner."""self.set_delimiter(config_line.strip())_,_delimeter,banner=config_line.partition(self.delimiter)banner_config_start=banner.lstrip(_delimeter)if_delimeternotinbanner_config_start:returnFalsereturnTrue
defis_banner_start(self,line:str)->bool:"""Checks if the given line is the start of a banner."""state=super(_HPEConfigParser,self).is_banner_start(line)ifstate:self.banner_end=linereturnstate
defis_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. """forbanner_startinself.banner_start:ifnotline:returnFalseifline.lstrip().startswith(banner_start):returnTruereturnFalse
defis_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 >>> """forcomment_charinself.comment_chars:ifline.lstrip().startswith(comment_char):returnTruereturnFalse
defset_delimiter(self,config_line:str)->None:"""Find delimiter character in banner and set self.delimiter to be it."""banner_parsed=self.regex_banner.match(config_line)ifbanner_parsedand"banner_delimiter"inbanner_parsed.groupdict():self.delimiter=banner_parsed.groupdict()["banner_delimiter"]returnNoneraiseValueError("Unable to find banner delimiter.")
def__init__(self,config:str):"""Initialize the _HPEConfigParser object."""self.delimiter=""self._banner_end:t.Optional[str]=Nonesuper(_HPEConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config (str): The config text to parse. """self._indent_level=0super(BaseSpaceConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config: The config text to parse. """self.config=configself._config:t.Optional[str]=Noneself._current_parents:t.Tuple[str,...]=()self.generator_config=(lineforlineinself.config_lines_only.splitlines())self.config_lines:t.List[ConfigLine]=[]self.build_config_relationship()
def_build_banner(self,config_line:str)->t.Optional[str]:""" Builds a banner configuration based on the given config_line. Args: config_line (str): The configuration line to process. Returns: Optional[str]: The next configuration line, or None if there are no more lines. Raises: ValueError: If the banner end cannot be parsed. """ifself.is_banner_one_line(config_line):self._update_config_lines(config_line)try:returnnext(self.generator_config)exceptStopIteration:returnNoneself._update_config_lines(config_line)self._current_parents+=(config_line,)banner_config=[]forlineinself.generator_config:ifnotself.is_banner_end(line):banner_config.append(line)else:banner_config.append(line)line="\n".join(banner_config)ifline.endswith(self.delimiter):banner,end,_=line.rpartition(self.delimiter)line=banner+endself._update_config_lines(line)self._current_parents=self._current_parents[:-1]try:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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=[]forlineinself.generator_config:ifnotself.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)ifline.endswith("^C"):banner,end,_=line.rpartition("^C")line=banner+endself._update_config_lines(line)self._current_parents=self._current_parents[:-1]try:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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)forlineinself.generator_config:ifnotline[0].isspace():self._current_parents=()self.indent_level=0returnlinespaces=self.get_leading_space_count(line)ifspaces==self.indent_level:passelifspaces>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)ifspaces!=self.indent_level:self.indent_level=spacesifself.is_banner_start(line):banner_line=self._build_banner(line)ifbanner_lineisNoneornotbanner_line[0].isspace():self._current_parents=()self.indent_level=0returnbanner_lineline=banner_lineself._update_config_lines(line)returnNone
@staticmethoddef_match_type_check(line:str,pattern:str,match_type:str)->bool:"""Checks pattern for exact match or regex."""ifmatch_type=="exact"andline==pattern:returnTrueifmatch_type=="startswith"andline.startswith(pattern):returnTrueifmatch_type=="endswith"andline.endswith(pattern):returnTrueifmatch_type=="regex"andre.match(pattern,line):returnTruereturnFalse
def_remove_parents(self,line:str,current_spaces:int)->t.Tuple[str,...]:"""Remove parents from ``self._curent_parents`` based on indent levels. Args: line: A line of text in the config. current_spaces: The number of spaces the current line is indented. Returns: The config lines parent config lines. """deindent_level=1try:previous_parent=self._current_parents[-1]previous_indent=self.get_leading_space_count(previous_parent)whileprevious_indent>current_spaces:deindent_level+=1previous_parent=self._current_parents[-deindent_level]previous_indent=self.get_leading_space_count(previous_parent)exceptIndexError:raiseIndexError(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],)returnparents
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)
defbuild_config_relationship(self)->t.List[ConfigLine]:r"""This is a custom build method for HPE Network OS. HP config is a bit different from other network operating systems. It uses comments (#) to demarcate sections of the config. Each new section that starts without a leading space is a new section. That new section may or may not have children. Each config line that has a leading space but not a parent is just a single config line. Single lines that have leading spaces also sometimes differs between models (e.g., 59XX vs 79XX series). Examples: >>> from netutils.config.parser import _HPEConfigParser, ConfigLine >>> config = '''# ... version 7.1.045, Release 2418P06 ... # ... sysname NTC123456 ... # ... vlan 101 ... name Test-Vlan-101 ... description Test Vlan 101 ... #''' >>> config_tree = _HPEConfigParser(config) >>> config_tree.build_config_relationship() == \ ... [ ... ConfigLine(config_line="version 7.1.045, Release 2418P06", parents=()), ... ConfigLine(config_line=" sysname NTC123456", parents=()), ... ConfigLine(config_line="vlan 101", parents=()), ... ConfigLine(config_line=" name Test-Vlan-101", parents=("vlan 101",)), ... ConfigLine(config_line=" description Test Vlan 101", parents=("vlan 101",)), ... ] True >>> """new_section=Trueforlineinself.generator_config:ifline.startswith(tuple(self.comment_chars)):# Closing any previous sectionsself._current_parents=()self.indent_level=0new_section=Truecontinueifline.strip().startswith(tuple(self.comment_chars)):# Just ignore comments inside sectionscontinueifself.is_banner_start(line):# Special case for bannersself._build_banner(line)continuecurrent_spaces=self.get_leading_space_count(line)ifline[0].isspace()else0ifcurrent_spaces>self.indent_levelandnotnew_section:previous_config=self.config_lines[-1]self._current_parents+=(previous_config.config_line,)elifcurrent_spaces<self.indent_level:self._current_parents=self._remove_parents(line,current_spaces)new_section=Falseself.indent_level=current_spacesself._update_config_lines(line)returnself.config_lines
@staticmethoddefget_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 >>> """returnlen(config_line)-len(config_line.lstrip())
defis_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. """ifself.banner_endinline:returnTruereturnFalse
defis_banner_one_line(self,config_line:str)->bool:"""Checks if the given configuration line represents a one-line banner."""self.set_delimiter(config_line.strip())_,_delimeter,banner=config_line.partition(self.delimiter)banner_config_start=banner.lstrip(_delimeter)if_delimeternotinbanner_config_start:returnFalsereturnTrue
defis_banner_start(self,line:str)->bool:"""Checks if the given line is the start of a banner."""state=super(_HPEConfigParser,self).is_banner_start(line)ifstate:self.banner_end=linereturnstate
defis_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. """forbanner_startinself.banner_start:ifnotline:returnFalseifline.lstrip().startswith(banner_start):returnTruereturnFalse
defis_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 >>> """forcomment_charinself.comment_chars:ifline.lstrip().startswith(comment_char):returnTruereturnFalse
defset_delimiter(self,config_line:str)->None:"""Find delimiter character in banner and set self.delimiter to be it."""banner_parsed=self.regex_banner.match(config_line)ifbanner_parsedand"banner_delimiter"inbanner_parsed.groupdict():self.delimiter=banner_parsed.groupdict()["banner_delimiter"]returnNoneraiseValueError("Unable to find banner delimiter.")
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)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config (str): The config text to parse. """self._banner_end:t.Optional[str]=Nonesuper(CiscoConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config (str): The config text to parse. """self._indent_level=0super(BaseSpaceConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config: The config text to parse. """self.config=configself._config:t.Optional[str]=Noneself._current_parents:t.Tuple[str,...]=()self.generator_config=(lineforlineinself.config_lines_only.splitlines())self.config_lines:t.List[ConfigLine]=[]self.build_config_relationship()
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)returnsuper(IOSConfigParser,self)._build_banner(config_line)
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. """ifself.is_banner_one_line(config_line):self._update_config_lines(config_line)try:returnnext(self.generator_config)exceptStopIteration:returnNonereturnsuper(CiscoConfigParser,self)._build_banner(config_line)
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=[]forlineinself.generator_config:ifnotself.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)ifline.endswith("^C"):banner,end,_=line.rpartition("^C")line=banner+endself._update_config_lines(line)self._current_parents=self._current_parents[:-1]try:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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)forlineinself.generator_config:ifnotline[0].isspace():self._current_parents=()self.indent_level=0returnlinespaces=self.get_leading_space_count(line)ifspaces==self.indent_level:passelifspaces>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)ifspaces!=self.indent_level:self.indent_level=spacesifself.is_banner_start(line):banner_line=self._build_banner(line)ifbanner_lineisNoneornotbanner_line[0].isspace():self._current_parents=()self.indent_level=0returnbanner_lineline=banner_lineself._update_config_lines(line)returnNone
@staticmethoddef_match_type_check(line:str,pattern:str,match_type:str)->bool:"""Checks pattern for exact match or regex."""ifmatch_type=="exact"andline==pattern:returnTrueifmatch_type=="startswith"andline.startswith(pattern):returnTrueifmatch_type=="endswith"andline.endswith(pattern):returnTrueifmatch_type=="regex"andre.match(pattern,line):returnTruereturnFalse
def_remove_parents(self,line:str,current_spaces:int)->t.Tuple[str,...]:"""Remove parents from ``self._curent_parents`` based on indent levels. Args: line: A line of text in the config. current_spaces: The number of spaces the current line is indented. Returns: The config lines parent config lines. """deindent_level=1try:previous_parent=self._current_parents[-1]previous_indent=self.get_leading_space_count(previous_parent)whileprevious_indent>current_spaces:deindent_level+=1previous_parent=self._current_parents[-deindent_level]previous_indent=self.get_leading_space_count(previous_parent)exceptIndexError:raiseIndexError(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],)returnparents
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: The current config line being evaluated. Returns: None """super(IOSConfigParser,self)._update_config_lines(config_line)entry=self.config_lines[-1]ifentryinself.unique_config_lines:self.same_line_children.add(entry)self.unique_config_lines.add(entry)
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_update_same_line_children_configs(self)->None:"""Update parents in ``self.config_lines`` per ``self.same_line_children``."""new_config_lines:t.List[ConfigLine]=[]forlineinself.config_lines:iflineinself.same_line_children:try:previous_line=new_config_lines[-1]exceptIndexErroraserror:raiseIndexError(f"This error is likely from a duplicate line detected at the line `{line.config_line}`, ""see https://netutils.readthedocs.io/en/latest/dev/dev_config/#duplicate-line-detection "f"for more details.\nOriginal Error: {error}")previous_config_line=previous_line.config_linecurrent_parents=previous_line.parents+(previous_config_line,)line=ConfigLine(line.config_line,current_parents)new_config_lines.append(line)self.config_lines=new_config_lines
@staticmethoddefget_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 >>> """returnlen(config_line)-len(config_line.lstrip())
defis_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. """ifself.banner_endinline:returnTruereturnFalse
@staticmethoddefis_banner_one_line(config_line:str)->bool:"""Determine if all banner config is on one line."""_,delimeter,banner=config_line.partition("^C")# if the banner is the delimeter is a single line empty banner. e.g banner motd ^C^C which ios allows.ifbanner=="^C":returnTrue# Based on NXOS configs, the banner delimeter is ignored until another char is usedbanner_config_start=banner.lstrip(delimeter)ifdelimeternotinbanner_config_start:returnFalsereturnTrue
defis_banner_start(self,line:str)->bool:"""Determine if the line starts a banner config."""state=super(CiscoConfigParser,self).is_banner_start(line)ifstate:self.banner_end=linereturnstate
defis_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. """forbanner_startinself.banner_start:ifnotline:returnFalseifline.lstrip().startswith(banner_start):returnTruereturnFalse
defis_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 >>> """forcomment_charinself.comment_chars:ifline.lstrip().startswith(comment_char):returnTruereturnFalse
def__init__(self,config:str):"""Create ConfigParser Object. Args: config (str): The config text to parse. """self.delimiter=""super(IOSXRConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config (str): The config text to parse. """self._banner_end:t.Optional[str]=Nonesuper(CiscoConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config (str): The config text to parse. """self._indent_level=0super(BaseSpaceConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config: The config text to parse. """self.config=configself._config:t.Optional[str]=Noneself._current_parents:t.Tuple[str,...]=()self.generator_config=(lineforlineinself.config_lines_only.splitlines())self.config_lines:t.List[ConfigLine]=[]self.build_config_relationship()
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=[]forlineinself.generator_config:ifnotself.is_banner_end(line):banner_config.append(line)else:banner_config.append(line)line="\n".join(banner_config)ifline.endswith(self.delimiter):banner,end,_=line.rpartition(self.delimiter)line=banner+endself._update_config_lines(line)self._current_parents=self._current_parents[:-1]try:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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. """ifself.is_banner_one_line(config_line):self._update_config_lines(config_line)try:returnnext(self.generator_config)exceptStopIteration:returnNonereturnsuper(CiscoConfigParser,self)._build_banner(config_line)
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=[]forlineinself.generator_config:ifnotself.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)ifline.endswith("^C"):banner,end,_=line.rpartition("^C")line=banner+endself._update_config_lines(line)self._current_parents=self._current_parents[:-1]try:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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)forlineinself.generator_config:ifnotline[0].isspace():self._current_parents=()self.indent_level=0returnlinespaces=self.get_leading_space_count(line)ifspaces==self.indent_level:passelifspaces>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)ifspaces!=self.indent_level:self.indent_level=spacesifself.is_banner_start(line):banner_line=self._build_banner(line)ifbanner_lineisNoneornotbanner_line[0].isspace():self._current_parents=()self.indent_level=0returnbanner_lineline=banner_lineself._update_config_lines(line)returnNone
@staticmethoddef_match_type_check(line:str,pattern:str,match_type:str)->bool:"""Checks pattern for exact match or regex."""ifmatch_type=="exact"andline==pattern:returnTrueifmatch_type=="startswith"andline.startswith(pattern):returnTrueifmatch_type=="endswith"andline.endswith(pattern):returnTrueifmatch_type=="regex"andre.match(pattern,line):returnTruereturnFalse
def_remove_parents(self,line:str,current_spaces:int)->t.Tuple[str,...]:"""Remove parents from ``self._curent_parents`` based on indent levels. Args: line: A line of text in the config. current_spaces: The number of spaces the current line is indented. Returns: The config lines parent config lines. """deindent_level=1try:previous_parent=self._current_parents[-1]previous_indent=self.get_leading_space_count(previous_parent)whileprevious_indent>current_spaces:deindent_level+=1previous_parent=self._current_parents[-deindent_level]previous_indent=self.get_leading_space_count(previous_parent)exceptIndexError:raiseIndexError(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],)returnparents
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)
@staticmethoddefget_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 >>> """returnlen(config_line)-len(config_line.lstrip())
defis_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. """ifself.banner_endinline:returnTruereturnFalse
@staticmethoddefis_banner_one_line(config_line:str)->bool:"""Determine if all banner config is on one line."""_,delimeter,banner=config_line.partition("^C")# if the banner is the delimeter is a single line empty banner. e.g banner motd ^C^C which ios allows.ifbanner=="^C":returnTrue# Based on NXOS configs, the banner delimeter is ignored until another char is usedbanner_config_start=banner.lstrip(delimeter)ifdelimeternotinbanner_config_start:returnFalsereturnTrue
defis_banner_start(self,line:str)->bool:"""Determine if the line starts a banner config."""state=super(CiscoConfigParser,self).is_banner_start(line)ifstate:self.banner_end=linereturnstate
defis_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. """forbanner_startinself.banner_start:ifnotline:returnFalseifline.lstrip().startswith(banner_start):returnTruereturnFalse
defis_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 >>> """forcomment_charinself.comment_chars:ifline.lstrip().startswith(comment_char):returnTruereturnFalse
defset_delimiter(self,config_line:str)->None:"""Find delimiter character in banner and set self.delimiter to be it."""banner_parsed=self.regex_banner.match(config_line)ifbanner_parsedand"banner_delimiter"inbanner_parsed.groupdict():self.delimiter=banner_parsed.groupdict()["banner_delimiter"]returnNoneraiseValueError("Unable to find banner delimiter.")
def__init__(self,config:str):"""Create ConfigParser Object. Args: config (str): The config text to parse. """self._indent_level=0super(BaseSpaceConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config: The config text to parse. """self.config=configself._config:t.Optional[str]=Noneself._current_parents:t.Tuple[str,...]=()self.generator_config=(lineforlineinself.config_lines_only.splitlines())self.config_lines:t.List[ConfigLine]=[]self.build_config_relationship()
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=[]forlineinself.generator_config:ifnotself.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)ifline.endswith("^C"):banner,end,_=line.rpartition("^C")line=banner+endself._update_config_lines(line)self._current_parents=self._current_parents[:-1]try:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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)forlineinself.generator_config:ifnotline[0].isspace():self._current_parents=()self.indent_level=0returnlinespaces=self.get_leading_space_count(line)ifspaces==self.indent_level:passelifspaces>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)ifspaces!=self.indent_level:self.indent_level=spacesifself.is_banner_start(line):banner_line=self._build_banner(line)ifbanner_lineisNoneornotbanner_line[0].isspace():self._current_parents=()self.indent_level=0returnbanner_lineline=banner_lineself._update_config_lines(line)returnNone
@staticmethoddef_match_type_check(line:str,pattern:str,match_type:str)->bool:"""Checks pattern for exact match or regex."""ifmatch_type=="exact"andline==pattern:returnTrueifmatch_type=="startswith"andline.startswith(pattern):returnTrueifmatch_type=="endswith"andline.endswith(pattern):returnTrueifmatch_type=="regex"andre.match(pattern,line):returnTruereturnFalse
def_remove_parents(self,line:str,current_spaces:int)->t.Tuple[str,...]:"""Remove parents from ``self._curent_parents`` based on indent levels. Args: line: A line of text in the config. current_spaces: The number of spaces the current line is indented. Returns: The config lines parent config lines. """deindent_level=1try:previous_parent=self._current_parents[-1]previous_indent=self.get_leading_space_count(previous_parent)whileprevious_indent>current_spaces:deindent_level+=1previous_parent=self._current_parents[-deindent_level]previous_indent=self.get_leading_space_count(previous_parent)exceptIndexError:raiseIndexError(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],)returnparents
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)
@staticmethoddefget_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 >>> """returnlen(config_line)-len(config_line.lstrip())
defis_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. """ifself.banner_endinline:returnTruereturnFalse
defis_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. """forbanner_startinself.banner_start:ifnotline:returnFalseifline.lstrip().startswith(banner_start):returnTruereturnFalse
defis_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 >>> """forcomment_charinself.comment_chars:ifline.lstrip().startswith(comment_char):returnTruereturnFalse
def__init__(self,config:str):"""Create ConfigParser Object. Args: config (str): The config text to parse. """self._indent_level=0super(BaseSpaceConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config: The config text to parse. """self.config=configself._config:t.Optional[str]=Noneself._current_parents:t.Tuple[str,...]=()self.generator_config=(lineforlineinself.config_lines_only.splitlines())self.config_lines:t.List[ConfigLine]=[]self.build_config_relationship()
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=[]forlineinself.generator_config:ifnotself.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)ifline.endswith("^C"):banner,end,_=line.rpartition("^C")line=banner+endself._update_config_lines(line)self._current_parents=self._current_parents[:-1]try:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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)forlineinself.generator_config:ifnotline[0].isspace():self._current_parents=()self.indent_level=0returnlinespaces=self.get_leading_space_count(line)ifspaces==self.indent_level:passelifspaces>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)ifspaces!=self.indent_level:self.indent_level=spacesifself.is_banner_start(line):banner_line=self._build_banner(line)ifbanner_lineisNoneornotbanner_line[0].isspace():self._current_parents=()self.indent_level=0returnbanner_lineline=banner_lineself._update_config_lines(line)returnNone
@staticmethoddef_match_type_check(line:str,pattern:str,match_type:str)->bool:"""Checks pattern for exact match or regex."""ifmatch_type=="exact"andline==pattern:returnTrueifmatch_type=="startswith"andline.startswith(pattern):returnTrueifmatch_type=="endswith"andline.endswith(pattern):returnTrueifmatch_type=="regex"andre.match(pattern,line):returnTruereturnFalse
def_remove_parents(self,line:str,current_spaces:int)->t.Tuple[str,...]:"""Remove parents from ``self._curent_parents`` based on indent levels. Args: line: A line of text in the config. current_spaces: The number of spaces the current line is indented. Returns: The config lines parent config lines. """deindent_level=1try:previous_parent=self._current_parents[-1]previous_indent=self.get_leading_space_count(previous_parent)whileprevious_indent>current_spaces:deindent_level+=1previous_parent=self._current_parents[-deindent_level]previous_indent=self.get_leading_space_count(previous_parent)exceptIndexError:raiseIndexError(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],)returnparents
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)
@staticmethoddefget_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 >>> """returnlen(config_line)-len(config_line.lstrip())
defis_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. """ifself.banner_endinline:returnTruereturnFalse
defis_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. """forbanner_startinself.banner_start:ifnotline:returnFalseifline.lstrip().startswith(banner_start):returnTruereturnFalse
defis_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 >>> """forcomment_charinself.comment_chars:ifline.lstrip().startswith(comment_char):returnTruereturnFalse
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(NXOSConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config (str): The config text to parse. """self._banner_end:t.Optional[str]=Nonesuper(CiscoConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config (str): The config text to parse. """self._indent_level=0super(BaseSpaceConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config: The config text to parse. """self.config=configself._config:t.Optional[str]=Noneself._current_parents:t.Tuple[str,...]=()self.generator_config=(lineforlineinself.config_lines_only.splitlines())self.config_lines:t.List[ConfigLine]=[]self.build_config_relationship()
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)returnsuper(NXOSConfigParser,self)._build_banner(config_line)
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. """ifself.is_banner_one_line(config_line):self._update_config_lines(config_line)try:returnnext(self.generator_config)exceptStopIteration:returnNonereturnsuper(CiscoConfigParser,self)._build_banner(config_line)
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=[]forlineinself.generator_config:ifnotself.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)ifline.endswith("^C"):banner,end,_=line.rpartition("^C")line=banner+endself._update_config_lines(line)self._current_parents=self._current_parents[:-1]try:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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)forlineinself.generator_config:ifnotline[0].isspace():self._current_parents=()self.indent_level=0returnlinespaces=self.get_leading_space_count(line)ifspaces==self.indent_level:passelifspaces>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)ifspaces!=self.indent_level:self.indent_level=spacesifself.is_banner_start(line):banner_line=self._build_banner(line)ifbanner_lineisNoneornotbanner_line[0].isspace():self._current_parents=()self.indent_level=0returnbanner_lineline=banner_lineself._update_config_lines(line)returnNone
@staticmethoddef_match_type_check(line:str,pattern:str,match_type:str)->bool:"""Checks pattern for exact match or regex."""ifmatch_type=="exact"andline==pattern:returnTrueifmatch_type=="startswith"andline.startswith(pattern):returnTrueifmatch_type=="endswith"andline.endswith(pattern):returnTrueifmatch_type=="regex"andre.match(pattern,line):returnTruereturnFalse
def_remove_parents(self,line:str,current_spaces:int)->t.Tuple[str,...]:"""Remove parents from ``self._curent_parents`` based on indent levels. Args: line: A line of text in the config. current_spaces: The number of spaces the current line is indented. Returns: The config lines parent config lines. """deindent_level=1try:previous_parent=self._current_parents[-1]previous_indent=self.get_leading_space_count(previous_parent)whileprevious_indent>current_spaces:deindent_level+=1previous_parent=self._current_parents[-deindent_level]previous_indent=self.get_leading_space_count(previous_parent)exceptIndexError:raiseIndexError(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],)returnparents
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)
@staticmethoddefget_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 >>> """returnlen(config_line)-len(config_line.lstrip())
defis_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. """ifself.banner_endinline:returnTruereturnFalse
@staticmethoddefis_banner_one_line(config_line:str)->bool:"""Determine if all banner config is on one line."""_,delimeter,banner=config_line.partition("^C")# if the banner is the delimeter is a single line empty banner. e.g banner motd ^C^C which ios allows.ifbanner=="^C":returnTrue# Based on NXOS configs, the banner delimeter is ignored until another char is usedbanner_config_start=banner.lstrip(delimeter)ifdelimeternotinbanner_config_start:returnFalsereturnTrue
defis_banner_start(self,line:str)->bool:"""Determine if the line starts a banner config."""state=super(CiscoConfigParser,self).is_banner_start(line)ifstate:self.banner_end=linereturnstate
defis_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. """forbanner_startinself.banner_start:ifnotline:returnFalseifline.lstrip().startswith(banner_start):returnTruereturnFalse
defis_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 >>> """forcomment_charinself.comment_chars:ifline.lstrip().startswith(comment_char):returnTruereturnFalse
def__init__(self,config:str):"""Create ConfigParser Object. Args: config (str): The config text to parse. """self._indent_level=0super(BaseSpaceConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config: The config text to parse. """self.config=configself._config:t.Optional[str]=Noneself._current_parents:t.Tuple[str,...]=()self.generator_config=(lineforlineinself.config_lines_only.splitlines())self.config_lines:t.List[ConfigLine]=[]self.build_config_relationship()
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=[]forlineinself.generator_config:ifnotself.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)ifline.endswith("^C"):banner,end,_=line.rpartition("^C")line=banner+endself._update_config_lines(line)self._current_parents=self._current_parents[:-1]try:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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)forlineinself.generator_config:ifnotline[0].isspace():self._current_parents=()self.indent_level=0returnlinespaces=self.get_leading_space_count(line)ifspaces==self.indent_level:passelifspaces>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)ifspaces!=self.indent_level:self.indent_level=spacesifself.is_banner_start(line):banner_line=self._build_banner(line)ifbanner_lineisNoneornotbanner_line[0].isspace():self._current_parents=()self.indent_level=0returnbanner_lineline=banner_lineself._update_config_lines(line)returnNone
@staticmethoddef_match_type_check(line:str,pattern:str,match_type:str)->bool:"""Checks pattern for exact match or regex."""ifmatch_type=="exact"andline==pattern:returnTrueifmatch_type=="startswith"andline.startswith(pattern):returnTrueifmatch_type=="endswith"andline.endswith(pattern):returnTrueifmatch_type=="regex"andre.match(pattern,line):returnTruereturnFalse
def_remove_parents(self,line:str,current_spaces:int)->t.Tuple[str,...]:"""Remove parents from ``self._curent_parents`` based on indent levels. Args: line: A line of text in the config. current_spaces: The number of spaces the current line is indented. Returns: The config lines parent config lines. """deindent_level=1try:previous_parent=self._current_parents[-1]previous_indent=self.get_leading_space_count(previous_parent)whileprevious_indent>current_spaces:deindent_level+=1previous_parent=self._current_parents[-deindent_level]previous_indent=self.get_leading_space_count(previous_parent)exceptIndexError:raiseIndexError(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],)returnparents
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)
@staticmethoddefget_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 >>> """returnlen(config_line)-len(config_line.lstrip())
defis_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. """ifself.banner_endinline:returnTruereturnFalse
defis_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. """forbanner_startinself.banner_start:ifnotline:returnFalseifline.lstrip().startswith(banner_start):returnTruereturnFalse
defis_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 >>> """forcomment_charinself.comment_chars:ifline.lstrip().startswith(comment_char):returnTruereturnFalse
def__init__(self,config:str):"""Create ConfigParser Object. Args: config (str): The config text to parse. """self._indent_level=0super(BaseSpaceConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config: The config text to parse. """self.config=configself._config:t.Optional[str]=Noneself._current_parents:t.Tuple[str,...]=()self.generator_config=(lineforlineinself.config_lines_only.splitlines())self.config_lines:t.List[ConfigLine]=[]self.build_config_relationship()
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=[]forlineinself.generator_config:ifnotself.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)ifline.endswith("^C"):banner,end,_=line.rpartition("^C")line=banner+endself._update_config_lines(line)self._current_parents=self._current_parents[:-1]try:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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)forlineinself.generator_config:ifnotline[0].isspace():self._current_parents=()self.indent_level=0returnlinespaces=self.get_leading_space_count(line)ifspaces==self.indent_level:passelifspaces>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)ifspaces!=self.indent_level:self.indent_level=spacesifself.is_banner_start(line):banner_line=self._build_banner(line)ifbanner_lineisNoneornotbanner_line[0].isspace():self._current_parents=()self.indent_level=0returnbanner_lineline=banner_lineself._update_config_lines(line)returnNone
@staticmethoddef_match_type_check(line:str,pattern:str,match_type:str)->bool:"""Checks pattern for exact match or regex."""ifmatch_type=="exact"andline==pattern:returnTrueifmatch_type=="startswith"andline.startswith(pattern):returnTrueifmatch_type=="endswith"andline.endswith(pattern):returnTrueifmatch_type=="regex"andre.match(pattern,line):returnTruereturnFalse
def_remove_parents(self,line:str,current_spaces:int)->t.Tuple[str,...]:"""Remove parents from ``self._curent_parents`` based on indent levels. Args: line: A line of text in the config. current_spaces: The number of spaces the current line is indented. Returns: The config lines parent config lines. """deindent_level=1try:previous_parent=self._current_parents[-1]previous_indent=self.get_leading_space_count(previous_parent)whileprevious_indent>current_spaces:deindent_level+=1previous_parent=self._current_parents[-deindent_level]previous_indent=self.get_leading_space_count(previous_parent)exceptIndexError:raiseIndexError(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],)returnparents
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)
@staticmethoddefget_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 >>> """returnlen(config_line)-len(config_line.lstrip())
defis_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. """ifself.banner_endinline:returnTruereturnFalse
defis_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. """forbanner_startinself.banner_start:ifnotline:returnFalseifline.lstrip().startswith(banner_start):returnTruereturnFalse
defis_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 >>> """forcomment_charinself.comment_chars:ifline.lstrip().startswith(comment_char):returnTruereturnFalse
def__init__(self,config:str):"""Create ConfigParser Object. Args: config (str): The config text to parse. """self._indent_level=0super(BaseSpaceConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config: The config text to parse. """self.config=configself._config:t.Optional[str]=Noneself._current_parents:t.Tuple[str,...]=()self.generator_config=(lineforlineinself.config_lines_only.splitlines())self.config_lines:t.List[ConfigLine]=[]self.build_config_relationship()
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=[]forlineinself.generator_config:ifnotself.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)ifline.endswith("^C"):banner,end,_=line.rpartition("^C")line=banner+endself._update_config_lines(line)self._current_parents=self._current_parents[:-1]try:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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)forlineinself.generator_config:ifnotline[0].isspace():self._current_parents=()self.indent_level=0returnlinespaces=self.get_leading_space_count(line)ifspaces==self.indent_level:passelifspaces>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)ifspaces!=self.indent_level:self.indent_level=spacesifself.is_banner_start(line):banner_line=self._build_banner(line)ifbanner_lineisNoneornotbanner_line[0].isspace():self._current_parents=()self.indent_level=0returnbanner_lineline=banner_lineself._update_config_lines(line)returnNone
def_get_section_title(self,line:str)->t.Union[str,bool]:"""Determine section title from banner. Args: line: A config line from the device that has been found to be a section title. Returns: The section's title from the section banner, else False. """section_title=re.match(r"^echo\s\"(?P<section_name>.+)\"",string=line)ifsection_title:returnsection_title.group("section_name")returnFalse
def_is_section_title(self,line:str)->bool:"""Determine if line is a section title in banner. Args: line: A config line from the device. Returns: True if line is a section, else False. """ifre.match(r"^echo\s\".+\"",string=line):returnTruereturnFalse
@staticmethoddef_match_type_check(line:str,pattern:str,match_type:str)->bool:"""Checks pattern for exact match or regex."""ifmatch_type=="exact"andline==pattern:returnTrueifmatch_type=="startswith"andline.startswith(pattern):returnTrueifmatch_type=="endswith"andline.endswith(pattern):returnTrueifmatch_type=="regex"andre.match(pattern,line):returnTruereturnFalse
def_remove_parents(self,line:str,current_spaces:int)->t.Tuple[str,...]:"""Remove parents from ``self._curent_parents`` based on indent levels. Args: line: A line of text in the config. current_spaces: The number of spaces the current line is indented. Returns: The config lines parent config lines. """deindent_level=1try:previous_parent=self._current_parents[-1]previous_indent=self.get_leading_space_count(previous_parent)whileprevious_indent>current_spaces:deindent_level+=1previous_parent=self._current_parents[-deindent_level]previous_indent=self.get_leading_space_count(previous_parent)exceptIndexError:raiseIndexError(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],)returnparents
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)
@staticmethoddefget_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 >>> """returnlen(config_line)-len(config_line.lstrip())
defis_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. """ifself.banner_endinline:returnTruereturnFalse
defis_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. """forbanner_startinself.banner_start:ifnotline:returnFalseifline.lstrip().startswith(banner_start):returnTruereturnFalse
defis_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 >>> """forcomment_charinself.comment_chars:ifline.lstrip().startswith(comment_char):returnTruereturnFalse
def__init__(self,config:str):# pylint: disable=super-init-not-called"""Create ConfigParser Object. Args: config: The config text to parse. """self.config=configself._config:t.Optional[str]=Noneself._current_parents:t.Tuple[str,...]=()self.generator_config=(lineforlinein(self._config_lines_only()))self.config_lines:t.List[ConfigLine]=[]self.build_config_relationship()
def__init__(self,config:str):"""Create ConfigParser Object. Args: config: The config text to parse. """self.config=configself._config:t.Optional[str]=Noneself._current_parents:t.Tuple[str,...]=()self.generator_config=(lineforlineinself.config_lines_only.splitlines())self.config_lines:t.List[ConfigLine]=[]self.build_config_relationship()
def_config_lines_only(self)->t.List[str]:"""Remove spaces and unwanted lines from config lines. Returns: An array with non-space and non-comment lines from ``config``. """banner_text=""config_lines=[]forlineinself.config.splitlines():ifline.startswith(self.section_char):continuestripped=line.strip()ifre.match(r"banner \w+ (?!\".+\")",stripped):banner_text+=line.lstrip()elifbanner_textandnotstripped.endswith('"'):banner_text+="\n"+lineelifbanner_textandstripped.endswith('"'):banner_text+="\n"+lineconfig_lines.append(banner_text)banner_text=""elifstripped:config_lines.append(stripped)returnconfig_lines
def__init__(self,config:str):"""Create ConfigParser Object. Args: config (str): The config text to parse. """self._indent_level=0super(BaseSpaceConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config: The config text to parse. """self.config=configself._config:t.Optional[str]=Noneself._current_parents:t.Tuple[str,...]=()self.generator_config=(lineforlineinself.config_lines_only.splitlines())self.config_lines:t.List[ConfigLine]=[]self.build_config_relationship()
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=[]forlineinself.generator_config:ifnotself.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)ifline.endswith("^C"):banner,end,_=line.rpartition("^C")line=banner+endself._update_config_lines(line)self._current_parents=self._current_parents[:-1]try:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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)forlineinself.generator_config:ifnotline[0].isspace():self._current_parents=()self.indent_level=0returnlinespaces=self.get_leading_space_count(line)ifspaces==self.indent_level:passelifspaces>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)ifspaces!=self.indent_level:self.indent_level=spacesifself.is_banner_start(line):banner_line=self._build_banner(line)ifbanner_lineisNoneornotbanner_line[0].isspace():self._current_parents=()self.indent_level=0returnbanner_lineline=banner_lineself._update_config_lines(line)returnNone
@staticmethoddef_match_type_check(line:str,pattern:str,match_type:str)->bool:"""Checks pattern for exact match or regex."""ifmatch_type=="exact"andline==pattern:returnTrueifmatch_type=="startswith"andline.startswith(pattern):returnTrueifmatch_type=="endswith"andline.endswith(pattern):returnTrueifmatch_type=="regex"andre.match(pattern,line):returnTruereturnFalse
def_remove_parents(self,line:str,current_spaces:int)->t.Tuple[str,...]:"""Remove parents from ``self._curent_parents`` based on indent levels. Args: line: A line of text in the config. current_spaces: The number of spaces the current line is indented. Returns: The config lines parent config lines. """deindent_level=1try:previous_parent=self._current_parents[-1]previous_indent=self.get_leading_space_count(previous_parent)whileprevious_indent>current_spaces:deindent_level+=1previous_parent=self._current_parents[-deindent_level]previous_indent=self.get_leading_space_count(previous_parent)exceptIndexError:raiseIndexError(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],)returnparents
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)
@staticmethoddefget_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 >>> """returnlen(config_line)-len(config_line.lstrip())
defis_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. """ifself.banner_endinline:returnTruereturnFalse
defis_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. """forbanner_startinself.banner_start:ifnotline:returnFalseifline.lstrip().startswith(banner_start):returnTruereturnFalse
defis_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 >>> """forcomment_charinself.comment_chars:ifline.lstrip().startswith(comment_char):returnTruereturnFalse
def__init__(self,config:str):"""Create ConfigParser Object. Args: config (str): The config text to parse. """self._indent_level=0super(BaseSpaceConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config: The config text to parse. """self.config=configself._config:t.Optional[str]=Noneself._current_parents:t.Tuple[str,...]=()self.generator_config=(lineforlineinself.config_lines_only.splitlines())self.config_lines:t.List[ConfigLine]=[]self.build_config_relationship()
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:t.List[str]=[]forlineinself.generator_config:# Note, this is a little fragile and will cause false positives if any line in# the middle of a multi-line banner starts with "set ".ifline.startswith("set "):# New command, save the banner and return the next lineifbanner_config:banner_string="\n".join(banner_config)self._update_config_lines(banner_string)self._current_parents=self._current_parents[:-1]returnlinebanner_config.append(line)# Edge case, the last line of the config is the bannerbanner_string="\n".join(banner_config)self._update_config_lines(banner_string)self._current_parents=self._current_parents[:-1]returnNone
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=[]forlineinself.generator_config:ifnotself.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)ifline.endswith("^C"):banner,end,_=line.rpartition("^C")line=banner+endself._update_config_lines(line)self._current_parents=self._current_parents[:-1]try:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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)forlineinself.generator_config:ifnotline[0].isspace():self._current_parents=()self.indent_level=0returnlinespaces=self.get_leading_space_count(line)ifspaces==self.indent_level:passelifspaces>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)ifspaces!=self.indent_level:self.indent_level=spacesifself.is_banner_start(line):banner_line=self._build_banner(line)ifbanner_lineisNoneornotbanner_line[0].isspace():self._current_parents=()self.indent_level=0returnbanner_lineline=banner_lineself._update_config_lines(line)returnNone
@staticmethoddef_match_type_check(line:str,pattern:str,match_type:str)->bool:"""Checks pattern for exact match or regex."""ifmatch_type=="exact"andline==pattern:returnTrueifmatch_type=="startswith"andline.startswith(pattern):returnTrueifmatch_type=="endswith"andline.endswith(pattern):returnTrueifmatch_type=="regex"andre.match(pattern,line):returnTruereturnFalse
def_remove_parents(self,line:str,current_spaces:int)->t.Tuple[str,...]:"""Remove parents from ``self._curent_parents`` based on indent levels. Args: line: A line of text in the config. current_spaces: The number of spaces the current line is indented. Returns: The config lines parent config lines. """deindent_level=1try:previous_parent=self._current_parents[-1]previous_indent=self.get_leading_space_count(previous_parent)whileprevious_indent>current_spaces:deindent_level+=1previous_parent=self._current_parents[-deindent_level]previous_indent=self.get_leading_space_count(previous_parent)exceptIndexError:raiseIndexError(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],)returnparents
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)
@staticmethoddefget_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 >>> """returnlen(config_line)-len(config_line.lstrip())
defis_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. """ifself.banner_endinline:returnTruereturnFalse
defis_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. """forbanner_startinself.banner_start:ifnotline:returnFalseifline.lstrip().startswith(banner_start):returnTruereturnFalse
defis_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 >>> """forcomment_charinself.comment_chars:ifline.lstrip().startswith(comment_char):returnTruereturnFalse
def__init__(self,config:str):"""Create ConfigParser Object. Args: config (str): The config text to parse. """self._indent_level=0super(BaseSpaceConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config: The config text to parse. """self.config=configself._config:t.Optional[str]=Noneself._current_parents:t.Tuple[str,...]=()self.generator_config=(lineforlineinself.config_lines_only.splitlines())self.config_lines:t.List[ConfigLine]=[]self.build_config_relationship()
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=[]forlineinself.generator_config:ifnotself.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)ifline.endswith("^C"):banner,end,_=line.rpartition("^C")line=banner+endself._update_config_lines(line)self._current_parents=self._current_parents[:-1]try:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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)forlineinself.generator_config:ifnotline[0].isspace():self._current_parents=()self.indent_level=0returnlinespaces=self.get_leading_space_count(line)ifspaces==self.indent_level:passelifspaces>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)ifspaces!=self.indent_level:self.indent_level=spacesifself.is_banner_start(line):banner_line=self._build_banner(line)ifbanner_lineisNoneornotbanner_line[0].isspace():self._current_parents=()self.indent_level=0returnbanner_lineline=banner_lineself._update_config_lines(line)returnNone
@staticmethoddef_match_type_check(line:str,pattern:str,match_type:str)->bool:"""Checks pattern for exact match or regex."""ifmatch_type=="exact"andline==pattern:returnTrueifmatch_type=="startswith"andline.startswith(pattern):returnTrueifmatch_type=="endswith"andline.endswith(pattern):returnTrueifmatch_type=="regex"andre.match(pattern,line):returnTruereturnFalse
def_remove_parents(self,line:str,current_spaces:int)->t.Tuple[str,...]:"""Remove parents from ``self._curent_parents`` based on indent levels. Args: line: A line of text in the config. current_spaces: The number of spaces the current line is indented. Returns: The config lines parent config lines. """deindent_level=1try:previous_parent=self._current_parents[-1]previous_indent=self.get_leading_space_count(previous_parent)whileprevious_indent>current_spaces:deindent_level+=1previous_parent=self._current_parents[-deindent_level]previous_indent=self.get_leading_space_count(previous_parent)exceptIndexError:raiseIndexError(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],)returnparents
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)
@staticmethoddefget_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 >>> """returnlen(config_line)-len(config_line.lstrip())
defis_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. """ifself.banner_endinline:returnTruereturnFalse
defis_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. """forbanner_startinself.banner_start:ifnotline:returnFalseifline.lstrip().startswith(banner_start):returnTruereturnFalse
defis_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 >>> """forcomment_charinself.comment_chars:ifline.lstrip().startswith(comment_char):returnTruereturnFalse
def__init__(self,config:str):"""Create ConfigParser Object. Args: config (str): The config text to parse. """self._indent_level=0super(BaseSpaceConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config: The config text to parse. """self.config=configself._config:t.Optional[str]=Noneself._current_parents:t.Tuple[str,...]=()self.generator_config=(lineforlineinself.config_lines_only.splitlines())self.config_lines:t.List[ConfigLine]=[]self.build_config_relationship()
def_build_banner(self,config_line:str)->t.Optional[str]:"""Handle banner config lines. Args: config_line: The start of the banner (system note) 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. """banner_config=[config_line]forlineinself.generator_config:ifnotself.is_banner_end(line):banner_config.append(line)else:banner_config.append(line)line="\n".join(banner_config)self._update_config_lines(line)try:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner (system note) end.")
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=[]forlineinself.generator_config:ifnotself.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)ifline.endswith("^C"):banner,end,_=line.rpartition("^C")line=banner+endself._update_config_lines(line)self._current_parents=self._current_parents[:-1]try:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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)forlineinself.generator_config:ifnotline[0].isspace():self._current_parents=()self.indent_level=0returnlinespaces=self.get_leading_space_count(line)ifspaces==self.indent_level:passelifspaces>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)ifspaces!=self.indent_level:self.indent_level=spacesifself.is_banner_start(line):banner_line=self._build_banner(line)ifbanner_lineisNoneornotbanner_line[0].isspace():self._current_parents=()self.indent_level=0returnbanner_lineline=banner_lineself._update_config_lines(line)returnNone
@staticmethoddef_match_type_check(line:str,pattern:str,match_type:str)->bool:"""Checks pattern for exact match or regex."""ifmatch_type=="exact"andline==pattern:returnTrueifmatch_type=="startswith"andline.startswith(pattern):returnTrueifmatch_type=="endswith"andline.endswith(pattern):returnTrueifmatch_type=="regex"andre.match(pattern,line):returnTruereturnFalse
def_remove_parents(self,line:str,current_spaces:int)->t.Tuple[str,...]:"""Remove parents from ``self._curent_parents`` based on indent levels. Args: line: A line of text in the config. current_spaces: The number of spaces the current line is indented. Returns: The config lines parent config lines. """deindent_level=1try:previous_parent=self._current_parents[-1]previous_indent=self.get_leading_space_count(previous_parent)whileprevious_indent>current_spaces:deindent_level+=1previous_parent=self._current_parents[-deindent_level]previous_indent=self.get_leading_space_count(previous_parent)exceptIndexError:raiseIndexError(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],)returnparents
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)
@staticmethoddefget_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 >>> """returnlen(config_line)-len(config_line.lstrip())
defis_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. """ifself.banner_endinline:returnTruereturnFalse
defis_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. """forbanner_startinself.banner_start:ifnotline:returnFalseifline.lstrip().startswith(banner_start):returnTruereturnFalse
defis_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 >>> """forcomment_charinself.comment_chars:ifline.lstrip().startswith(comment_char):returnTruereturnFalse
def__init__(self,config:str):"""Create ConfigParser Object. Args: config (str): The config text to parse. """self._indent_level=0super(BaseSpaceConfigParser,self).__init__(config)
def__init__(self,config:str):"""Create ConfigParser Object. Args: config: The config text to parse. """self.config=configself._config:t.Optional[str]=Noneself._current_parents:t.Tuple[str,...]=()self.generator_config=(lineforlineinself.config_lines_only.splitlines())self.config_lines:t.List[ConfigLine]=[]self.build_config_relationship()
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=[]forlineinself.generator_config:ifnotself.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)ifline.endswith("^C"):banner,end,_=line.rpartition("^C")line=banner+endself._update_config_lines(line)self._current_parents=self._current_parents[:-1]try:returnnext(self.generator_config)exceptStopIteration:returnNoneraiseValueError("Unable to parse banner end.")
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)forlineinself.generator_config:ifnotline[0].isspace():self._current_parents=()self.indent_level=0returnlinespaces=self.get_leading_space_count(line)ifspaces==self.indent_level:passelifspaces>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)ifspaces!=self.indent_level:self.indent_level=spacesifself.is_banner_start(line):banner_line=self._build_banner(line)ifbanner_lineisNoneornotbanner_line[0].isspace():self._current_parents=()self.indent_level=0returnbanner_lineline=banner_lineself._update_config_lines(line)returnNone
@staticmethoddef_match_type_check(line:str,pattern:str,match_type:str)->bool:"""Checks pattern for exact match or regex."""ifmatch_type=="exact"andline==pattern:returnTrueifmatch_type=="startswith"andline.startswith(pattern):returnTrueifmatch_type=="endswith"andline.endswith(pattern):returnTrueifmatch_type=="regex"andre.match(pattern,line):returnTruereturnFalse
def_remove_parents(self,line:str,current_spaces:int)->t.Tuple[str,...]:"""Remove parents from ``self._curent_parents`` based on indent levels. Args: line: A line of text in the config. current_spaces: The number of spaces the current line is indented. Returns: The config lines parent config lines. """deindent_level=1try:previous_parent=self._current_parents[-1]previous_indent=self.get_leading_space_count(previous_parent)whileprevious_indent>current_spaces:deindent_level+=1previous_parent=self._current_parents[-deindent_level]previous_indent=self.get_leading_space_count(previous_parent)exceptIndexError:raiseIndexError(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],)returnparents
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)
@staticmethoddefget_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 >>> """returnlen(config_line)-len(config_line.lstrip())
defis_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. """ifself.banner_endinline:returnTruereturnFalse
defis_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. """forbanner_startinself.banner_start:ifnotline:returnFalseifline.lstrip().startswith(banner_start):returnTruereturnFalse
defis_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 >>> """forcomment_charinself.comment_chars:ifline.lstrip().startswith(comment_char):returnTruereturnFalse