Skip to content

netutils.config.parser.BaseConfigParser Found Classes

ADVAFSP150F2ConfigParser

from netutils.config.parser import ADVAFSP150F2ConfigParser

Ancestors (MRO)

The Method Resolution Order is described below.

  1. ADVAFSP150F2ConfigParser
  2. _ADVAConfigParser
  3. BaseSpaceConfigParser
  4. BaseConfigParser

Descendant Classes

Attributes

Key Value Defined in
banner_start ['banner', 'vacant-message'] BaseSpaceConfigParser
banner_start ['banner', 'vacant-message'] BaseConfigParser
comment_chars ['!'] BaseSpaceConfigParser
comment_chars ['!'] BaseConfigParser

Methods

def __init__(self, config): BaseSpaceConfigParser

BaseSpaceConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self._indent_level = 0
        super(BaseSpaceConfigParser, self).__init__(config)

BaseConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config: The config text to parse.
        """
        self.config = config
        self._config: t.Optional[str] = None
        self._current_parents: t.Tuple[str, ...] = ()
        self.generator_config = (line for line in self.config_lines_only.splitlines())
        self.config_lines: t.List[ConfigLine] = []
        self.build_config_relationship()
def _build_banner(self, config_line): _ADVAConfigParser

_ADVAConfigParser

    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.
        """
        if config_line.endswith(self.banner_end):
            self._update_config_lines(config_line)
            try:
                return next(self.generator_config)
            except StopIteration:
                return None

        raise ValueError("Unable to parse banner end.")

BaseSpaceConfigParser

    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None

        Raises:
            ValueError: When the parser is unable to identify the end of the Banner.
        """
        self._update_config_lines(config_line)
        self._current_parents += (config_line,)
        banner_config = []
        for line in self.generator_config:
            if not self.is_banner_end(line):
                banner_config.append(line)
            else:
                line = normalise_delimiter_caret_c(self.banner_end, line)
                banner_config.append(line)
                line = "\n".join(banner_config)
                if line.endswith("^C"):
                    banner, end, _ = line.rpartition("^C")
                    line = banner + end
                self._update_config_lines(line)
                self._current_parents = self._current_parents[:-1]
                try:
                    return next(self.generator_config)
                except StopIteration:
                    return None

        raise ValueError("Unable to parse banner end.")
def _build_nested_config(self, line): BaseSpaceConfigParser
    def _build_nested_config(self, line: str) -> t.Optional[str]:
        """Handle building child config sections.

        Args:
            line: A configuration line from the configuration text.

        Returns:
            The next top-level configuration line in the configuration text or None when the last line of configuration
            text is a nested configuration line.

        Raises:
            IndexError: When the number of parents does not match the expected deindent level.
        """
        self._update_config_lines(line)
        for line in self.generator_config:
            if not line[0].isspace():
                self._current_parents = ()
                self.indent_level = 0
                return line

            spaces = self.get_leading_space_count(line)
            if spaces == self.indent_level:
                pass
            elif spaces > self.indent_level:
                previous_config = self.config_lines[-1]
                self._current_parents += (previous_config.config_line,)
            else:
                self._current_parents = self._remove_parents(line, spaces)

            if spaces != self.indent_level:
                self.indent_level = spaces

            if self.is_banner_start(line):
                banner_line = self._build_banner(line)
                if banner_line is None or not banner_line[0].isspace():
                    self._current_parents = ()
                    self.indent_level = 0
                    return banner_line
                line = banner_line

            self._update_config_lines(line)
        return None
def _match_type_check(line, pattern, match_type): BaseSpaceConfigParser
    @staticmethod
    def _match_type_check(line: str, pattern: str, match_type: str) -> bool:
        """Checks pattern for exact match or regex."""
        if match_type == "exact" and line == pattern:
            return True
        if match_type == "startswith" and line.startswith(pattern):
            return True
        if match_type == "endswith" and line.endswith(pattern):
            return True
        if match_type == "regex" and re.match(pattern, line):
            return True
        return False
def _remove_parents(self, line, current_spaces): BaseSpaceConfigParser
    def _remove_parents(self, line: str, current_spaces: int) -> t.Tuple[str, ...]:
        """Remove parents from ``self._curent_parents`` based on indent levels.

        Args:
            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 = 1
        try:
            previous_parent = self._current_parents[-1]
            previous_indent = self.get_leading_space_count(previous_parent)
            while previous_indent > current_spaces:
                deindent_level += 1
                previous_parent = self._current_parents[-deindent_level]
                previous_indent = self.get_leading_space_count(previous_parent)
        except IndexError:
            raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n")
        parents = self._current_parents[:-deindent_level] or (self._current_parents[0],)
        return parents
def _update_config_lines(self, config_line): BaseSpaceConfigParser
    def _update_config_lines(self, config_line: str) -> None:
        """Add a ``ConfigLine`` object to ``self.config_lines``.

        Args:
            config_line: The current config line being evaluated.

        Returns:
            None
        """
        entry = ConfigLine(config_line, self._current_parents)
        self.config_lines.append(entry)
def build_config_relationship(self): BaseSpaceConfigParser

BaseSpaceConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        r"""Parse text tree of config lines and their parents.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
            >>> config = (
            ...     "interface Ethernet1/1\n"
            ...     "  vlan 10\n"
            ...     "  no shutdown\n"
            ...     "interface Ethernet1/2\n"
            ...     "  shutdown\n"
            ... )
            >>> config_tree = BaseSpaceConfigParser(config)
            >>> config_tree.build_config_relationship() == \
            ... [
            ...     ConfigLine(config_line='interface Ethernet1/1', parents=()),
            ...     ConfigLine(config_line='  vlan 10', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='  no shutdown', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='interface Ethernet1/2', parents=(),),
            ...     ConfigLine(config_line='  shutdown', parents=('interface Ethernet1/2',))
            ... ]
            True
        """
        for index, line in enumerate(self.generator_config):
            current_spaces = self.get_leading_space_count(line) if line[0].isspace() else 0
            if index == 0 and line[0].isspace():
                self._current_parents = self._remove_parents(line, current_spaces)
            elif not line[0].isspace():
                self._current_parents = ()
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore
            else:
                previous_config = self.config_lines[-1]
                self._current_parents = (previous_config.config_line,)
                self.indent_level = self.get_leading_space_count(line)
                if not self.is_banner_start(line):
                    line = self._build_nested_config(line)  # type: ignore
                else:
                    line = self._build_banner(line)  # type: ignore
                    if line is not None and line[0].isspace():
                        line = self._build_nested_config(line)  # type: ignore
                    else:
                        self._current_parents = ()

            if line is None:
                break
            elif self.is_banner_start(line):
                line = self._build_banner(line)  # type: ignore
                # line can potentially be another banner start therefore we do a secondary check.
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore

            self._update_config_lines(line)
        return self.config_lines

BaseConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        """Parse text tree of config lines and their parents."""
        raise NotImplementedError()
def find_all_children(self, pattern, match_type=exact): BaseSpaceConfigParser
    def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]:
        """Returns configuration part for a specific pattern not including parents.

        Args:
            pattern: pattern that describes parent.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith")
            >>> print(bgp_conf)
            ['router bgp 45000', '  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if (
                parents
                and self._match_type_check(parents, pattern, match_type)
                or self._match_type_check(cfg_line.config_line, pattern, match_type)
            ):
                config.append(cfg_line.config_line)
        return config
def find_children_w_parents(self, parent_pattern, child_pattern, match_type=exact): BaseSpaceConfigParser
    def find_children_w_parents(
        self, parent_pattern: str, child_pattern: str, match_type: str = "exact"
    ) -> t.List[str]:
        """Returns configuration part for a specific pattern including parents and children.

        Args:
            parent_pattern: pattern that describes parent.
            child_pattern: pattern that describes child.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern="  address-family", match_type="regex")
            >>> print(bgp_conf)
            ['  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        potential_parents = [
            elem.parents[0]
            for elem in self.build_config_relationship()
            if self._match_type_check(elem.config_line, child_pattern, match_type)
        ]
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if parents in potential_parents and self._match_type_check(
                parents,  # type: ignore[arg-type]
                parent_pattern,
                match_type,
            ):
                config.append(cfg_line.config_line)
        return config
def get_leading_space_count(config_line): BaseSpaceConfigParser
    @staticmethod
    def get_leading_space_count(config_line: str) -> int:
        r"""Determine how many spaces the ``config_line`` is indented.

        Args:
           config_line: A line of text in the config.

        Returns:
            The number of leading spaces.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''interface GigabitEthernet1\n description link to ISP'''
            >>> config_line = " description link to ISP"
            >>> indent_level = BaseSpaceConfigParser(config).get_leading_space_count(config_line)
            >>> indent_level
            1
            >>>
        """
        return len(config_line) - len(config_line.lstrip())
def is_banner_end(self, line): BaseSpaceConfigParser
    def is_banner_end(self, line: str) -> bool:
        """Determine if line ends the banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line ends banner, else False.
        """
        if self.banner_end in line:
            return True
        return False
def is_banner_start(self, line): BaseSpaceConfigParser
    def is_banner_start(self, line: str) -> bool:
        """Determine if the line starts a banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line starts banner, else False.
        """
        for banner_start in self.banner_start:
            if not line:
                return False
            if line.lstrip().startswith(banner_start):
                return True
        return False
def is_comment(self, line): BaseSpaceConfigParser
    def is_comment(self, line: str) -> bool:
        """Determine if line is a comment.

        Args:
            line: A config line from the device.

        Returns:
            True if line is a comment, else False.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> BaseSpaceConfigParser("interface Ethernet1/1").is_comment("interface Ethernet1/1")
            False
            >>> BaseSpaceConfigParser("!").is_comment("!")
            True
            >>>
        """
        for comment_char in self.comment_chars:
            if line.lstrip().startswith(comment_char):
                return True
        return False

ADVAFSP150F3ConfigParser

from netutils.config.parser import ADVAFSP150F3ConfigParser

Ancestors (MRO)

The Method Resolution Order is described below.

  1. ADVAFSP150F3ConfigParser
  2. _ADVAConfigParser
  3. BaseSpaceConfigParser
  4. BaseConfigParser

Descendant Classes

Attributes

Key Value Defined in
banner_start ['banner', 'vacant-message'] BaseSpaceConfigParser
banner_start ['banner', 'vacant-message'] BaseConfigParser
comment_chars ['!'] BaseSpaceConfigParser
comment_chars ['!'] BaseConfigParser

Methods

def __init__(self, config): BaseSpaceConfigParser

BaseSpaceConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self._indent_level = 0
        super(BaseSpaceConfigParser, self).__init__(config)

BaseConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config: The config text to parse.
        """
        self.config = config
        self._config: t.Optional[str] = None
        self._current_parents: t.Tuple[str, ...] = ()
        self.generator_config = (line for line in self.config_lines_only.splitlines())
        self.config_lines: t.List[ConfigLine] = []
        self.build_config_relationship()
def _build_banner(self, config_line): _ADVAConfigParser

_ADVAConfigParser

    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.
        """
        if config_line.endswith(self.banner_end):
            self._update_config_lines(config_line)
            try:
                return next(self.generator_config)
            except StopIteration:
                return None

        raise ValueError("Unable to parse banner end.")

BaseSpaceConfigParser

    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None

        Raises:
            ValueError: When the parser is unable to identify the end of the Banner.
        """
        self._update_config_lines(config_line)
        self._current_parents += (config_line,)
        banner_config = []
        for line in self.generator_config:
            if not self.is_banner_end(line):
                banner_config.append(line)
            else:
                line = normalise_delimiter_caret_c(self.banner_end, line)
                banner_config.append(line)
                line = "\n".join(banner_config)
                if line.endswith("^C"):
                    banner, end, _ = line.rpartition("^C")
                    line = banner + end
                self._update_config_lines(line)
                self._current_parents = self._current_parents[:-1]
                try:
                    return next(self.generator_config)
                except StopIteration:
                    return None

        raise ValueError("Unable to parse banner end.")
def _build_nested_config(self, line): BaseSpaceConfigParser
    def _build_nested_config(self, line: str) -> t.Optional[str]:
        """Handle building child config sections.

        Args:
            line: A configuration line from the configuration text.

        Returns:
            The next top-level configuration line in the configuration text or None when the last line of configuration
            text is a nested configuration line.

        Raises:
            IndexError: When the number of parents does not match the expected deindent level.
        """
        self._update_config_lines(line)
        for line in self.generator_config:
            if not line[0].isspace():
                self._current_parents = ()
                self.indent_level = 0
                return line

            spaces = self.get_leading_space_count(line)
            if spaces == self.indent_level:
                pass
            elif spaces > self.indent_level:
                previous_config = self.config_lines[-1]
                self._current_parents += (previous_config.config_line,)
            else:
                self._current_parents = self._remove_parents(line, spaces)

            if spaces != self.indent_level:
                self.indent_level = spaces

            if self.is_banner_start(line):
                banner_line = self._build_banner(line)
                if banner_line is None or not banner_line[0].isspace():
                    self._current_parents = ()
                    self.indent_level = 0
                    return banner_line
                line = banner_line

            self._update_config_lines(line)
        return None
def _match_type_check(line, pattern, match_type): BaseSpaceConfigParser
    @staticmethod
    def _match_type_check(line: str, pattern: str, match_type: str) -> bool:
        """Checks pattern for exact match or regex."""
        if match_type == "exact" and line == pattern:
            return True
        if match_type == "startswith" and line.startswith(pattern):
            return True
        if match_type == "endswith" and line.endswith(pattern):
            return True
        if match_type == "regex" and re.match(pattern, line):
            return True
        return False
def _remove_parents(self, line, current_spaces): BaseSpaceConfigParser
    def _remove_parents(self, line: str, current_spaces: int) -> t.Tuple[str, ...]:
        """Remove parents from ``self._curent_parents`` based on indent levels.

        Args:
            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 = 1
        try:
            previous_parent = self._current_parents[-1]
            previous_indent = self.get_leading_space_count(previous_parent)
            while previous_indent > current_spaces:
                deindent_level += 1
                previous_parent = self._current_parents[-deindent_level]
                previous_indent = self.get_leading_space_count(previous_parent)
        except IndexError:
            raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n")
        parents = self._current_parents[:-deindent_level] or (self._current_parents[0],)
        return parents
def _update_config_lines(self, config_line): BaseSpaceConfigParser
    def _update_config_lines(self, config_line: str) -> None:
        """Add a ``ConfigLine`` object to ``self.config_lines``.

        Args:
            config_line: The current config line being evaluated.

        Returns:
            None
        """
        entry = ConfigLine(config_line, self._current_parents)
        self.config_lines.append(entry)
def build_config_relationship(self): BaseSpaceConfigParser

BaseSpaceConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        r"""Parse text tree of config lines and their parents.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
            >>> config = (
            ...     "interface Ethernet1/1\n"
            ...     "  vlan 10\n"
            ...     "  no shutdown\n"
            ...     "interface Ethernet1/2\n"
            ...     "  shutdown\n"
            ... )
            >>> config_tree = BaseSpaceConfigParser(config)
            >>> config_tree.build_config_relationship() == \
            ... [
            ...     ConfigLine(config_line='interface Ethernet1/1', parents=()),
            ...     ConfigLine(config_line='  vlan 10', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='  no shutdown', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='interface Ethernet1/2', parents=(),),
            ...     ConfigLine(config_line='  shutdown', parents=('interface Ethernet1/2',))
            ... ]
            True
        """
        for index, line in enumerate(self.generator_config):
            current_spaces = self.get_leading_space_count(line) if line[0].isspace() else 0
            if index == 0 and line[0].isspace():
                self._current_parents = self._remove_parents(line, current_spaces)
            elif not line[0].isspace():
                self._current_parents = ()
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore
            else:
                previous_config = self.config_lines[-1]
                self._current_parents = (previous_config.config_line,)
                self.indent_level = self.get_leading_space_count(line)
                if not self.is_banner_start(line):
                    line = self._build_nested_config(line)  # type: ignore
                else:
                    line = self._build_banner(line)  # type: ignore
                    if line is not None and line[0].isspace():
                        line = self._build_nested_config(line)  # type: ignore
                    else:
                        self._current_parents = ()

            if line is None:
                break
            elif self.is_banner_start(line):
                line = self._build_banner(line)  # type: ignore
                # line can potentially be another banner start therefore we do a secondary check.
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore

            self._update_config_lines(line)
        return self.config_lines

BaseConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        """Parse text tree of config lines and their parents."""
        raise NotImplementedError()
def find_all_children(self, pattern, match_type=exact): BaseSpaceConfigParser
    def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]:
        """Returns configuration part for a specific pattern not including parents.

        Args:
            pattern: pattern that describes parent.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith")
            >>> print(bgp_conf)
            ['router bgp 45000', '  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if (
                parents
                and self._match_type_check(parents, pattern, match_type)
                or self._match_type_check(cfg_line.config_line, pattern, match_type)
            ):
                config.append(cfg_line.config_line)
        return config
def find_children_w_parents(self, parent_pattern, child_pattern, match_type=exact): BaseSpaceConfigParser
    def find_children_w_parents(
        self, parent_pattern: str, child_pattern: str, match_type: str = "exact"
    ) -> t.List[str]:
        """Returns configuration part for a specific pattern including parents and children.

        Args:
            parent_pattern: pattern that describes parent.
            child_pattern: pattern that describes child.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern="  address-family", match_type="regex")
            >>> print(bgp_conf)
            ['  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        potential_parents = [
            elem.parents[0]
            for elem in self.build_config_relationship()
            if self._match_type_check(elem.config_line, child_pattern, match_type)
        ]
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if parents in potential_parents and self._match_type_check(
                parents,  # type: ignore[arg-type]
                parent_pattern,
                match_type,
            ):
                config.append(cfg_line.config_line)
        return config
def get_leading_space_count(config_line): BaseSpaceConfigParser
    @staticmethod
    def get_leading_space_count(config_line: str) -> int:
        r"""Determine how many spaces the ``config_line`` is indented.

        Args:
           config_line: A line of text in the config.

        Returns:
            The number of leading spaces.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''interface GigabitEthernet1\n description link to ISP'''
            >>> config_line = " description link to ISP"
            >>> indent_level = BaseSpaceConfigParser(config).get_leading_space_count(config_line)
            >>> indent_level
            1
            >>>
        """
        return len(config_line) - len(config_line.lstrip())
def is_banner_end(self, line): BaseSpaceConfigParser
    def is_banner_end(self, line: str) -> bool:
        """Determine if line ends the banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line ends banner, else False.
        """
        if self.banner_end in line:
            return True
        return False
def is_banner_start(self, line): BaseSpaceConfigParser
    def is_banner_start(self, line: str) -> bool:
        """Determine if the line starts a banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line starts banner, else False.
        """
        for banner_start in self.banner_start:
            if not line:
                return False
            if line.lstrip().startswith(banner_start):
                return True
        return False
def is_comment(self, line): BaseSpaceConfigParser
    def is_comment(self, line: str) -> bool:
        """Determine if line is a comment.

        Args:
            line: A config line from the device.

        Returns:
            True if line is a comment, else False.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> BaseSpaceConfigParser("interface Ethernet1/1").is_comment("interface Ethernet1/1")
            False
            >>> BaseSpaceConfigParser("!").is_comment("!")
            True
            >>>
        """
        for comment_char in self.comment_chars:
            if line.lstrip().startswith(comment_char):
                return True
        return False

AIREOSConfigParser

from netutils.config.parser import AIREOSConfigParser

Ancestors (MRO)

The Method Resolution Order is described below.

  1. AIREOSConfigParser
  2. CiscoConfigParser
  3. BaseSpaceConfigParser
  4. BaseConfigParser

Descendant Classes

Attributes

Key Value Defined in
banner_start ['banner', 'vacant-message'] BaseSpaceConfigParser
banner_start ['banner', 'vacant-message'] BaseConfigParser
comment_chars ['!'] BaseSpaceConfigParser
comment_chars ['!'] BaseConfigParser
regex_banner re.compile('^(banner\\s+\\S+|\\s*vacant-message)\\s+(?P<banner_delimiter>\\^C|.)') CiscoConfigParser

Methods

def __init__(self, config): CiscoConfigParser

CiscoConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self._banner_end: t.Optional[str] = None
        super(CiscoConfigParser, self).__init__(config)

BaseSpaceConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self._indent_level = 0
        super(BaseSpaceConfigParser, self).__init__(config)

BaseConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config: The config text to parse.
        """
        self.config = config
        self._config: t.Optional[str] = None
        self._current_parents: t.Tuple[str, ...] = ()
        self.generator_config = (line for line in self.config_lines_only.splitlines())
        self.config_lines: t.List[ConfigLine] = []
        self.build_config_relationship()
def _build_banner(self, config_line): AIREOSConfigParser

AIREOSConfigParser

    def _build_banner(self, config_line: str) -> None:
        raise NotImplementedError()

CiscoConfigParser

    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None when banner end is the end of the config text.

        Raises:
            ValueError: When the parser is unable to identify the End of the Banner.
        """
        if self.is_banner_one_line(config_line):
            self._update_config_lines(config_line)
            try:
                return next(self.generator_config)
            except StopIteration:
                return None
        return super(CiscoConfigParser, self)._build_banner(config_line)

BaseSpaceConfigParser

    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None

        Raises:
            ValueError: When the parser is unable to identify the end of the Banner.
        """
        self._update_config_lines(config_line)
        self._current_parents += (config_line,)
        banner_config = []
        for line in self.generator_config:
            if not self.is_banner_end(line):
                banner_config.append(line)
            else:
                line = normalise_delimiter_caret_c(self.banner_end, line)
                banner_config.append(line)
                line = "\n".join(banner_config)
                if line.endswith("^C"):
                    banner, end, _ = line.rpartition("^C")
                    line = banner + end
                self._update_config_lines(line)
                self._current_parents = self._current_parents[:-1]
                try:
                    return next(self.generator_config)
                except StopIteration:
                    return None

        raise ValueError("Unable to parse banner end.")
def _build_nested_config(self, line): BaseSpaceConfigParser
    def _build_nested_config(self, line: str) -> t.Optional[str]:
        """Handle building child config sections.

        Args:
            line: A configuration line from the configuration text.

        Returns:
            The next top-level configuration line in the configuration text or None when the last line of configuration
            text is a nested configuration line.

        Raises:
            IndexError: When the number of parents does not match the expected deindent level.
        """
        self._update_config_lines(line)
        for line in self.generator_config:
            if not line[0].isspace():
                self._current_parents = ()
                self.indent_level = 0
                return line

            spaces = self.get_leading_space_count(line)
            if spaces == self.indent_level:
                pass
            elif spaces > self.indent_level:
                previous_config = self.config_lines[-1]
                self._current_parents += (previous_config.config_line,)
            else:
                self._current_parents = self._remove_parents(line, spaces)

            if spaces != self.indent_level:
                self.indent_level = spaces

            if self.is_banner_start(line):
                banner_line = self._build_banner(line)
                if banner_line is None or not banner_line[0].isspace():
                    self._current_parents = ()
                    self.indent_level = 0
                    return banner_line
                line = banner_line

            self._update_config_lines(line)
        return None
def _match_type_check(line, pattern, match_type): BaseSpaceConfigParser
    @staticmethod
    def _match_type_check(line: str, pattern: str, match_type: str) -> bool:
        """Checks pattern for exact match or regex."""
        if match_type == "exact" and line == pattern:
            return True
        if match_type == "startswith" and line.startswith(pattern):
            return True
        if match_type == "endswith" and line.endswith(pattern):
            return True
        if match_type == "regex" and re.match(pattern, line):
            return True
        return False
def _remove_parents(self, line, current_spaces): BaseSpaceConfigParser
    def _remove_parents(self, line: str, current_spaces: int) -> t.Tuple[str, ...]:
        """Remove parents from ``self._curent_parents`` based on indent levels.

        Args:
            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 = 1
        try:
            previous_parent = self._current_parents[-1]
            previous_indent = self.get_leading_space_count(previous_parent)
            while previous_indent > current_spaces:
                deindent_level += 1
                previous_parent = self._current_parents[-deindent_level]
                previous_indent = self.get_leading_space_count(previous_parent)
        except IndexError:
            raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n")
        parents = self._current_parents[:-deindent_level] or (self._current_parents[0],)
        return parents
def _update_config_lines(self, config_line): BaseSpaceConfigParser
    def _update_config_lines(self, config_line: str) -> None:
        """Add a ``ConfigLine`` object to ``self.config_lines``.

        Args:
            config_line: The current config line being evaluated.

        Returns:
            None
        """
        entry = ConfigLine(config_line, self._current_parents)
        self.config_lines.append(entry)
def build_config_relationship(self): BaseSpaceConfigParser

BaseSpaceConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        r"""Parse text tree of config lines and their parents.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
            >>> config = (
            ...     "interface Ethernet1/1\n"
            ...     "  vlan 10\n"
            ...     "  no shutdown\n"
            ...     "interface Ethernet1/2\n"
            ...     "  shutdown\n"
            ... )
            >>> config_tree = BaseSpaceConfigParser(config)
            >>> config_tree.build_config_relationship() == \
            ... [
            ...     ConfigLine(config_line='interface Ethernet1/1', parents=()),
            ...     ConfigLine(config_line='  vlan 10', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='  no shutdown', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='interface Ethernet1/2', parents=(),),
            ...     ConfigLine(config_line='  shutdown', parents=('interface Ethernet1/2',))
            ... ]
            True
        """
        for index, line in enumerate(self.generator_config):
            current_spaces = self.get_leading_space_count(line) if line[0].isspace() else 0
            if index == 0 and line[0].isspace():
                self._current_parents = self._remove_parents(line, current_spaces)
            elif not line[0].isspace():
                self._current_parents = ()
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore
            else:
                previous_config = self.config_lines[-1]
                self._current_parents = (previous_config.config_line,)
                self.indent_level = self.get_leading_space_count(line)
                if not self.is_banner_start(line):
                    line = self._build_nested_config(line)  # type: ignore
                else:
                    line = self._build_banner(line)  # type: ignore
                    if line is not None and line[0].isspace():
                        line = self._build_nested_config(line)  # type: ignore
                    else:
                        self._current_parents = ()

            if line is None:
                break
            elif self.is_banner_start(line):
                line = self._build_banner(line)  # type: ignore
                # line can potentially be another banner start therefore we do a secondary check.
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore

            self._update_config_lines(line)
        return self.config_lines

BaseConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        """Parse text tree of config lines and their parents."""
        raise NotImplementedError()
def find_all_children(self, pattern, match_type=exact): BaseSpaceConfigParser
    def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]:
        """Returns configuration part for a specific pattern not including parents.

        Args:
            pattern: pattern that describes parent.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith")
            >>> print(bgp_conf)
            ['router bgp 45000', '  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if (
                parents
                and self._match_type_check(parents, pattern, match_type)
                or self._match_type_check(cfg_line.config_line, pattern, match_type)
            ):
                config.append(cfg_line.config_line)
        return config
def find_children_w_parents(self, parent_pattern, child_pattern, match_type=exact): BaseSpaceConfigParser
    def find_children_w_parents(
        self, parent_pattern: str, child_pattern: str, match_type: str = "exact"
    ) -> t.List[str]:
        """Returns configuration part for a specific pattern including parents and children.

        Args:
            parent_pattern: pattern that describes parent.
            child_pattern: pattern that describes child.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern="  address-family", match_type="regex")
            >>> print(bgp_conf)
            ['  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        potential_parents = [
            elem.parents[0]
            for elem in self.build_config_relationship()
            if self._match_type_check(elem.config_line, child_pattern, match_type)
        ]
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if parents in potential_parents and self._match_type_check(
                parents,  # type: ignore[arg-type]
                parent_pattern,
                match_type,
            ):
                config.append(cfg_line.config_line)
        return config
def get_leading_space_count(config_line): BaseSpaceConfigParser
    @staticmethod
    def get_leading_space_count(config_line: str) -> int:
        r"""Determine how many spaces the ``config_line`` is indented.

        Args:
           config_line: A line of text in the config.

        Returns:
            The number of leading spaces.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''interface GigabitEthernet1\n description link to ISP'''
            >>> config_line = " description link to ISP"
            >>> indent_level = BaseSpaceConfigParser(config).get_leading_space_count(config_line)
            >>> indent_level
            1
            >>>
        """
        return len(config_line) - len(config_line.lstrip())
def is_banner_end(self, line): BaseSpaceConfigParser
    def is_banner_end(self, line: str) -> bool:
        """Determine if line ends the banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line ends banner, else False.
        """
        if self.banner_end in line:
            return True
        return False
def is_banner_one_line(config_line): CiscoConfigParser
    @staticmethod
    def is_banner_one_line(config_line: str) -> bool:
        """Determine if all banner config is on one line."""
        _, delimeter, banner = config_line.partition("^C")
        # if the banner is the delimeter is a single line empty banner. e.g banner motd ^C^C which ios allows.
        if banner == "^C":
            return True
        # Based on NXOS configs, the banner delimeter is ignored until another char is used
        banner_config_start = banner.lstrip(delimeter)
        if delimeter not in banner_config_start:
            return False
        return True
def is_banner_start(self, line): CiscoConfigParser

CiscoConfigParser

    def is_banner_start(self, line: str) -> bool:
        """Determine if the line starts a banner config."""
        state = super(CiscoConfigParser, self).is_banner_start(line)
        if state:
            self.banner_end = line
        return state

BaseSpaceConfigParser

    def is_banner_start(self, line: str) -> bool:
        """Determine if the line starts a banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line starts banner, else False.
        """
        for banner_start in self.banner_start:
            if not line:
                return False
            if line.lstrip().startswith(banner_start):
                return True
        return False
def is_comment(self, line): BaseSpaceConfigParser
    def is_comment(self, line: str) -> bool:
        """Determine if line is a comment.

        Args:
            line: A config line from the device.

        Returns:
            True if line is a comment, else False.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> BaseSpaceConfigParser("interface Ethernet1/1").is_comment("interface Ethernet1/1")
            False
            >>> BaseSpaceConfigParser("!").is_comment("!")
            True
            >>>
        """
        for comment_char in self.comment_chars:
            if line.lstrip().startswith(comment_char):
                return True
        return False

ASAConfigParser

from netutils.config.parser import ASAConfigParser

Ancestors (MRO)

The Method Resolution Order is described below.

  1. ASAConfigParser
  2. CiscoConfigParser
  3. BaseSpaceConfigParser
  4. BaseConfigParser

Descendant Classes

Attributes

Key Value Defined in
banner_start ['banner', 'vacant-message'] BaseSpaceConfigParser
banner_start ['banner', 'vacant-message'] BaseConfigParser
comment_chars ['!'] BaseSpaceConfigParser
comment_chars ['!'] BaseConfigParser
regex_banner re.compile('^(banner\\s+\\S+|\\s*vacant-message)\\s+(?P<banner_delimiter>\\^C|.)') CiscoConfigParser

Methods

def __init__(self, config): ASAConfigParser

ASAConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config: The config text to parse.
        """
        self.unique_config_lines: t.Set[ConfigLine] = set()
        self.same_line_children: t.Set[ConfigLine] = set()
        super(ASAConfigParser, self).__init__(config)

CiscoConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self._banner_end: t.Optional[str] = None
        super(CiscoConfigParser, self).__init__(config)

BaseSpaceConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self._indent_level = 0
        super(BaseSpaceConfigParser, self).__init__(config)

BaseConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config: The config text to parse.
        """
        self.config = config
        self._config: t.Optional[str] = None
        self._current_parents: t.Tuple[str, ...] = ()
        self.generator_config = (line for line in self.config_lines_only.splitlines())
        self.config_lines: t.List[ConfigLine] = []
        self.build_config_relationship()
def _build_banner(self, config_line): CiscoConfigParser

CiscoConfigParser

    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None when banner end is the end of the config text.

        Raises:
            ValueError: When the parser is unable to identify the End of the Banner.
        """
        if self.is_banner_one_line(config_line):
            self._update_config_lines(config_line)
            try:
                return next(self.generator_config)
            except StopIteration:
                return None
        return super(CiscoConfigParser, self)._build_banner(config_line)

BaseSpaceConfigParser

    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None

        Raises:
            ValueError: When the parser is unable to identify the end of the Banner.
        """
        self._update_config_lines(config_line)
        self._current_parents += (config_line,)
        banner_config = []
        for line in self.generator_config:
            if not self.is_banner_end(line):
                banner_config.append(line)
            else:
                line = normalise_delimiter_caret_c(self.banner_end, line)
                banner_config.append(line)
                line = "\n".join(banner_config)
                if line.endswith("^C"):
                    banner, end, _ = line.rpartition("^C")
                    line = banner + end
                self._update_config_lines(line)
                self._current_parents = self._current_parents[:-1]
                try:
                    return next(self.generator_config)
                except StopIteration:
                    return None

        raise ValueError("Unable to parse banner end.")
def _build_nested_config(self, line): BaseSpaceConfigParser
    def _build_nested_config(self, line: str) -> t.Optional[str]:
        """Handle building child config sections.

        Args:
            line: A configuration line from the configuration text.

        Returns:
            The next top-level configuration line in the configuration text or None when the last line of configuration
            text is a nested configuration line.

        Raises:
            IndexError: When the number of parents does not match the expected deindent level.
        """
        self._update_config_lines(line)
        for line in self.generator_config:
            if not line[0].isspace():
                self._current_parents = ()
                self.indent_level = 0
                return line

            spaces = self.get_leading_space_count(line)
            if spaces == self.indent_level:
                pass
            elif spaces > self.indent_level:
                previous_config = self.config_lines[-1]
                self._current_parents += (previous_config.config_line,)
            else:
                self._current_parents = self._remove_parents(line, spaces)

            if spaces != self.indent_level:
                self.indent_level = spaces

            if self.is_banner_start(line):
                banner_line = self._build_banner(line)
                if banner_line is None or not banner_line[0].isspace():
                    self._current_parents = ()
                    self.indent_level = 0
                    return banner_line
                line = banner_line

            self._update_config_lines(line)
        return None
def _match_type_check(line, pattern, match_type): BaseSpaceConfigParser
    @staticmethod
    def _match_type_check(line: str, pattern: str, match_type: str) -> bool:
        """Checks pattern for exact match or regex."""
        if match_type == "exact" and line == pattern:
            return True
        if match_type == "startswith" and line.startswith(pattern):
            return True
        if match_type == "endswith" and line.endswith(pattern):
            return True
        if match_type == "regex" and re.match(pattern, line):
            return True
        return False
def _remove_parents(self, line, current_spaces): BaseSpaceConfigParser
    def _remove_parents(self, line: str, current_spaces: int) -> t.Tuple[str, ...]:
        """Remove parents from ``self._curent_parents`` based on indent levels.

        Args:
            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 = 1
        try:
            previous_parent = self._current_parents[-1]
            previous_indent = self.get_leading_space_count(previous_parent)
            while previous_indent > current_spaces:
                deindent_level += 1
                previous_parent = self._current_parents[-deindent_level]
                previous_indent = self.get_leading_space_count(previous_parent)
        except IndexError:
            raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n")
        parents = self._current_parents[:-deindent_level] or (self._current_parents[0],)
        return parents
def _update_config_lines(self, config_line): ASAConfigParser

ASAConfigParser

    def _update_config_lines(self, config_line: str) -> None:
        """Add a ``ConfigLine`` object to ``self.config_lines``.

        In addition to adding entries to config_lines, this also updates:
          * self.same_line_children
          * self.unique_config_lines

        Args:
            config_line (str): The current config line being evaluated.

        Returns:
            None
        """
        super(ASAConfigParser, self)._update_config_lines(config_line)
        entry = self.config_lines[-1]
        if entry in self.unique_config_lines:
            self.same_line_children.add(entry)
        self.unique_config_lines.add(entry)

BaseSpaceConfigParser

    def _update_config_lines(self, config_line: str) -> None:
        """Add a ``ConfigLine`` object to ``self.config_lines``.

        Args:
            config_line: The current config line being evaluated.

        Returns:
            None
        """
        entry = ConfigLine(config_line, self._current_parents)
        self.config_lines.append(entry)
def build_config_relationship(self): ASAConfigParser

ASAConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        r"""Parse text tree of config lines and their parents.

        Examples:
            >>> from netutils.config.parser import ASAConfigParser, ConfigLine
            >>> config = '''
            ... interface Management0/0
            ...  management-only
            ...  nameif Management
            ...  security-level 100
            ...  ip address 10.1.1.10 255.255.255.0'''
            >>> config_tree = ASAConfigParser(str(config))
            >>> config_tree.build_config_relationship() == \
            ... [
            ...     ConfigLine(config_line="interface Management0/0", parents=()),
            ...     ConfigLine(config_line=" management-only", parents=("interface Management0/0",)),
            ...     ConfigLine(config_line=" nameif Management", parents=("interface Management0/0",)),
            ...     ConfigLine(config_line=" security-level 100", parents=("interface Management0/0",)),
            ...     ConfigLine(config_line=" ip address 10.1.1.10 255.255.255.0", parents=("interface Management0/0",)),
            ... ]
            True
        """
        for index, line in enumerate(self.generator_config):
            current_spaces = self.get_leading_space_count(line) if line[0].isspace() else 0
            if index == 0 and line[0].isspace():
                self._current_parents = self._remove_parents(line, current_spaces)
            elif not line[0].isspace():
                self._current_parents = ()
            else:
                previous_config = self.config_lines[-1]
                self._current_parents = (previous_config.config_line,)
                self.indent_level = self.get_leading_space_count(line)
                if line is not None and line[0].isspace():
                    line = self._build_nested_config(line)  # type: ignore
                else:
                    self._current_parents = ()

            if line is None:
                break

            self._update_config_lines(line)

        return self.config_lines

BaseSpaceConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        r"""Parse text tree of config lines and their parents.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
            >>> config = (
            ...     "interface Ethernet1/1\n"
            ...     "  vlan 10\n"
            ...     "  no shutdown\n"
            ...     "interface Ethernet1/2\n"
            ...     "  shutdown\n"
            ... )
            >>> config_tree = BaseSpaceConfigParser(config)
            >>> config_tree.build_config_relationship() == \
            ... [
            ...     ConfigLine(config_line='interface Ethernet1/1', parents=()),
            ...     ConfigLine(config_line='  vlan 10', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='  no shutdown', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='interface Ethernet1/2', parents=(),),
            ...     ConfigLine(config_line='  shutdown', parents=('interface Ethernet1/2',))
            ... ]
            True
        """
        for index, line in enumerate(self.generator_config):
            current_spaces = self.get_leading_space_count(line) if line[0].isspace() else 0
            if index == 0 and line[0].isspace():
                self._current_parents = self._remove_parents(line, current_spaces)
            elif not line[0].isspace():
                self._current_parents = ()
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore
            else:
                previous_config = self.config_lines[-1]
                self._current_parents = (previous_config.config_line,)
                self.indent_level = self.get_leading_space_count(line)
                if not self.is_banner_start(line):
                    line = self._build_nested_config(line)  # type: ignore
                else:
                    line = self._build_banner(line)  # type: ignore
                    if line is not None and line[0].isspace():
                        line = self._build_nested_config(line)  # type: ignore
                    else:
                        self._current_parents = ()

            if line is None:
                break
            elif self.is_banner_start(line):
                line = self._build_banner(line)  # type: ignore
                # line can potentially be another banner start therefore we do a secondary check.
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore

            self._update_config_lines(line)
        return self.config_lines

BaseConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        """Parse text tree of config lines and their parents."""
        raise NotImplementedError()
def find_all_children(self, pattern, match_type=exact): BaseSpaceConfigParser
    def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]:
        """Returns configuration part for a specific pattern not including parents.

        Args:
            pattern: pattern that describes parent.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith")
            >>> print(bgp_conf)
            ['router bgp 45000', '  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if (
                parents
                and self._match_type_check(parents, pattern, match_type)
                or self._match_type_check(cfg_line.config_line, pattern, match_type)
            ):
                config.append(cfg_line.config_line)
        return config
def find_children_w_parents(self, parent_pattern, child_pattern, match_type=exact): BaseSpaceConfigParser
    def find_children_w_parents(
        self, parent_pattern: str, child_pattern: str, match_type: str = "exact"
    ) -> t.List[str]:
        """Returns configuration part for a specific pattern including parents and children.

        Args:
            parent_pattern: pattern that describes parent.
            child_pattern: pattern that describes child.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern="  address-family", match_type="regex")
            >>> print(bgp_conf)
            ['  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        potential_parents = [
            elem.parents[0]
            for elem in self.build_config_relationship()
            if self._match_type_check(elem.config_line, child_pattern, match_type)
        ]
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if parents in potential_parents and self._match_type_check(
                parents,  # type: ignore[arg-type]
                parent_pattern,
                match_type,
            ):
                config.append(cfg_line.config_line)
        return config
def get_leading_space_count(config_line): BaseSpaceConfigParser
    @staticmethod
    def get_leading_space_count(config_line: str) -> int:
        r"""Determine how many spaces the ``config_line`` is indented.

        Args:
           config_line: A line of text in the config.

        Returns:
            The number of leading spaces.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''interface GigabitEthernet1\n description link to ISP'''
            >>> config_line = " description link to ISP"
            >>> indent_level = BaseSpaceConfigParser(config).get_leading_space_count(config_line)
            >>> indent_level
            1
            >>>
        """
        return len(config_line) - len(config_line.lstrip())
def is_banner_end(self, line): BaseSpaceConfigParser
    def is_banner_end(self, line: str) -> bool:
        """Determine if line ends the banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line ends banner, else False.
        """
        if self.banner_end in line:
            return True
        return False
def is_banner_one_line(config_line): CiscoConfigParser
    @staticmethod
    def is_banner_one_line(config_line: str) -> bool:
        """Determine if all banner config is on one line."""
        _, delimeter, banner = config_line.partition("^C")
        # if the banner is the delimeter is a single line empty banner. e.g banner motd ^C^C which ios allows.
        if banner == "^C":
            return True
        # Based on NXOS configs, the banner delimeter is ignored until another char is used
        banner_config_start = banner.lstrip(delimeter)
        if delimeter not in banner_config_start:
            return False
        return True
def is_banner_start(self, line): ASAConfigParser

ASAConfigParser

    def is_banner_start(self, line: str) -> bool:
        """Determine if the line starts a banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line starts banner, else False.
        """
        for banner_start in self.banner_start:
            if not line:
                return False
            if line.startswith(banner_start):
                return True
        return False

CiscoConfigParser

    def is_banner_start(self, line: str) -> bool:
        """Determine if the line starts a banner config."""
        state = super(CiscoConfigParser, self).is_banner_start(line)
        if state:
            self.banner_end = line
        return state

BaseSpaceConfigParser

    def is_banner_start(self, line: str) -> bool:
        """Determine if the line starts a banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line starts banner, else False.
        """
        for banner_start in self.banner_start:
            if not line:
                return False
            if line.lstrip().startswith(banner_start):
                return True
        return False
def is_comment(self, line): BaseSpaceConfigParser
    def is_comment(self, line: str) -> bool:
        """Determine if line is a comment.

        Args:
            line: A config line from the device.

        Returns:
            True if line is a comment, else False.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> BaseSpaceConfigParser("interface Ethernet1/1").is_comment("interface Ethernet1/1")
            False
            >>> BaseSpaceConfigParser("!").is_comment("!")
            True
            >>>
        """
        for comment_char in self.comment_chars:
            if line.lstrip().startswith(comment_char):
                return True
        return False

ArubaConfigCXParser

from netutils.config.parser import ArubaConfigCXParser

Ancestors (MRO)

The Method Resolution Order is described below.

  1. ArubaConfigCXParser
  2. _ArubaBaseConfigParser
  3. BaseSpaceConfigParser
  4. BaseConfigParser

Descendant Classes

Attributes

Key Value Defined in
banner_end '!' _ArubaBaseConfigParser
banner_start ['banner', 'vacant-message'] BaseSpaceConfigParser
banner_start ['banner', 'vacant-message'] BaseConfigParser
comment_chars ['!'] _ArubaBaseConfigParser
comment_chars ['!'] BaseSpaceConfigParser
comment_chars ['!'] BaseConfigParser

Methods

def __init__(self, config): BaseSpaceConfigParser

BaseSpaceConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self._indent_level = 0
        super(BaseSpaceConfigParser, self).__init__(config)

BaseConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config: The config text to parse.
        """
        self.config = config
        self._config: t.Optional[str] = None
        self._current_parents: t.Tuple[str, ...] = ()
        self.generator_config = (line for line in self.config_lines_only.splitlines())
        self.config_lines: t.List[ConfigLine] = []
        self.build_config_relationship()
def _build_banner(self, config_line): _ArubaBaseConfigParser

_ArubaBaseConfigParser

    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None when banner end is the end of the config text.

        Raises:
            ValueError: When the parser is unable to identify the End of the Banner.
        """
        self._update_config_lines(config_line)
        self._current_parents += (config_line,)
        banner_config = []
        for line in self.generator_config:
            if not self.is_banner_end(line):
                banner_config.append(line)
            else:
                banner_config.append(line)
                line = "\n".join(banner_config)
                self._update_config_lines(line)
                self._current_parents = self._current_parents[:-1]
                try:
                    return next(self.generator_config)
                except StopIteration:
                    return None
        raise ValueError("Unable to parse banner end.")

BaseSpaceConfigParser

    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None

        Raises:
            ValueError: When the parser is unable to identify the end of the Banner.
        """
        self._update_config_lines(config_line)
        self._current_parents += (config_line,)
        banner_config = []
        for line in self.generator_config:
            if not self.is_banner_end(line):
                banner_config.append(line)
            else:
                line = normalise_delimiter_caret_c(self.banner_end, line)
                banner_config.append(line)
                line = "\n".join(banner_config)
                if line.endswith("^C"):
                    banner, end, _ = line.rpartition("^C")
                    line = banner + end
                self._update_config_lines(line)
                self._current_parents = self._current_parents[:-1]
                try:
                    return next(self.generator_config)
                except StopIteration:
                    return None

        raise ValueError("Unable to parse banner end.")
def _build_nested_config(self, line): BaseSpaceConfigParser
    def _build_nested_config(self, line: str) -> t.Optional[str]:
        """Handle building child config sections.

        Args:
            line: A configuration line from the configuration text.

        Returns:
            The next top-level configuration line in the configuration text or None when the last line of configuration
            text is a nested configuration line.

        Raises:
            IndexError: When the number of parents does not match the expected deindent level.
        """
        self._update_config_lines(line)
        for line in self.generator_config:
            if not line[0].isspace():
                self._current_parents = ()
                self.indent_level = 0
                return line

            spaces = self.get_leading_space_count(line)
            if spaces == self.indent_level:
                pass
            elif spaces > self.indent_level:
                previous_config = self.config_lines[-1]
                self._current_parents += (previous_config.config_line,)
            else:
                self._current_parents = self._remove_parents(line, spaces)

            if spaces != self.indent_level:
                self.indent_level = spaces

            if self.is_banner_start(line):
                banner_line = self._build_banner(line)
                if banner_line is None or not banner_line[0].isspace():
                    self._current_parents = ()
                    self.indent_level = 0
                    return banner_line
                line = banner_line

            self._update_config_lines(line)
        return None
def _match_type_check(line, pattern, match_type): BaseSpaceConfigParser
    @staticmethod
    def _match_type_check(line: str, pattern: str, match_type: str) -> bool:
        """Checks pattern for exact match or regex."""
        if match_type == "exact" and line == pattern:
            return True
        if match_type == "startswith" and line.startswith(pattern):
            return True
        if match_type == "endswith" and line.endswith(pattern):
            return True
        if match_type == "regex" and re.match(pattern, line):
            return True
        return False
def _parse_out_comments(self, config): _ArubaBaseConfigParser
    def _parse_out_comments(self, config: str) -> str:
        """Remove comments while retaining the banner end.

        Args:
            config (str): full config as a string.

        Returns:
            The non-comment lines from ``config``.
        """
        # Aruba AOS-CX uses "!" as both comments and the banner delimiter.
        # Even if another delimiter is used while creating the banner, show run changes the delimiter to use "!".
        # We need to remove comments while retaining the banner delimiter.

        config_lines = []
        banner_started = False
        banner_ended = False
        for line in config.splitlines():
            if self.is_banner_start(line):
                banner_started = True
                banner_ended = False
            if line and banner_started and not banner_ended:
                config_lines.append(line.rstrip())
                if line.lstrip().startswith(self.banner_end):
                    banner_ended = True
                    banner_started = False
            else:
                if line and not self.is_comment(line):
                    config_lines.append(line.rstrip())
        full_config = "\n".join(config_lines)
        return full_config
def _remove_parents(self, line, current_spaces): BaseSpaceConfigParser
    def _remove_parents(self, line: str, current_spaces: int) -> t.Tuple[str, ...]:
        """Remove parents from ``self._curent_parents`` based on indent levels.

        Args:
            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 = 1
        try:
            previous_parent = self._current_parents[-1]
            previous_indent = self.get_leading_space_count(previous_parent)
            while previous_indent > current_spaces:
                deindent_level += 1
                previous_parent = self._current_parents[-deindent_level]
                previous_indent = self.get_leading_space_count(previous_parent)
        except IndexError:
            raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n")
        parents = self._current_parents[:-deindent_level] or (self._current_parents[0],)
        return parents
def _update_config_lines(self, config_line): BaseSpaceConfigParser
    def _update_config_lines(self, config_line: str) -> None:
        """Add a ``ConfigLine`` object to ``self.config_lines``.

        Args:
            config_line: The current config line being evaluated.

        Returns:
            None
        """
        entry = ConfigLine(config_line, self._current_parents)
        self.config_lines.append(entry)
def build_config_relationship(self): BaseSpaceConfigParser

BaseSpaceConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        r"""Parse text tree of config lines and their parents.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
            >>> config = (
            ...     "interface Ethernet1/1\n"
            ...     "  vlan 10\n"
            ...     "  no shutdown\n"
            ...     "interface Ethernet1/2\n"
            ...     "  shutdown\n"
            ... )
            >>> config_tree = BaseSpaceConfigParser(config)
            >>> config_tree.build_config_relationship() == \
            ... [
            ...     ConfigLine(config_line='interface Ethernet1/1', parents=()),
            ...     ConfigLine(config_line='  vlan 10', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='  no shutdown', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='interface Ethernet1/2', parents=(),),
            ...     ConfigLine(config_line='  shutdown', parents=('interface Ethernet1/2',))
            ... ]
            True
        """
        for index, line in enumerate(self.generator_config):
            current_spaces = self.get_leading_space_count(line) if line[0].isspace() else 0
            if index == 0 and line[0].isspace():
                self._current_parents = self._remove_parents(line, current_spaces)
            elif not line[0].isspace():
                self._current_parents = ()
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore
            else:
                previous_config = self.config_lines[-1]
                self._current_parents = (previous_config.config_line,)
                self.indent_level = self.get_leading_space_count(line)
                if not self.is_banner_start(line):
                    line = self._build_nested_config(line)  # type: ignore
                else:
                    line = self._build_banner(line)  # type: ignore
                    if line is not None and line[0].isspace():
                        line = self._build_nested_config(line)  # type: ignore
                    else:
                        self._current_parents = ()

            if line is None:
                break
            elif self.is_banner_start(line):
                line = self._build_banner(line)  # type: ignore
                # line can potentially be another banner start therefore we do a secondary check.
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore

            self._update_config_lines(line)
        return self.config_lines

BaseConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        """Parse text tree of config lines and their parents."""
        raise NotImplementedError()
def find_all_children(self, pattern, match_type=exact): BaseSpaceConfigParser
    def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]:
        """Returns configuration part for a specific pattern not including parents.

        Args:
            pattern: pattern that describes parent.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith")
            >>> print(bgp_conf)
            ['router bgp 45000', '  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if (
                parents
                and self._match_type_check(parents, pattern, match_type)
                or self._match_type_check(cfg_line.config_line, pattern, match_type)
            ):
                config.append(cfg_line.config_line)
        return config
def find_children_w_parents(self, parent_pattern, child_pattern, match_type=exact): BaseSpaceConfigParser
    def find_children_w_parents(
        self, parent_pattern: str, child_pattern: str, match_type: str = "exact"
    ) -> t.List[str]:
        """Returns configuration part for a specific pattern including parents and children.

        Args:
            parent_pattern: pattern that describes parent.
            child_pattern: pattern that describes child.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern="  address-family", match_type="regex")
            >>> print(bgp_conf)
            ['  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        potential_parents = [
            elem.parents[0]
            for elem in self.build_config_relationship()
            if self._match_type_check(elem.config_line, child_pattern, match_type)
        ]
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if parents in potential_parents and self._match_type_check(
                parents,  # type: ignore[arg-type]
                parent_pattern,
                match_type,
            ):
                config.append(cfg_line.config_line)
        return config
def get_leading_space_count(config_line): BaseSpaceConfigParser
    @staticmethod
    def get_leading_space_count(config_line: str) -> int:
        r"""Determine how many spaces the ``config_line`` is indented.

        Args:
           config_line: A line of text in the config.

        Returns:
            The number of leading spaces.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''interface GigabitEthernet1\n description link to ISP'''
            >>> config_line = " description link to ISP"
            >>> indent_level = BaseSpaceConfigParser(config).get_leading_space_count(config_line)
            >>> indent_level
            1
            >>>
        """
        return len(config_line) - len(config_line.lstrip())
def is_banner_end(self, line): BaseSpaceConfigParser
    def is_banner_end(self, line: str) -> bool:
        """Determine if line ends the banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line ends banner, else False.
        """
        if self.banner_end in line:
            return True
        return False
def is_banner_start(self, line): BaseSpaceConfigParser
    def is_banner_start(self, line: str) -> bool:
        """Determine if the line starts a banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line starts banner, else False.
        """
        for banner_start in self.banner_start:
            if not line:
                return False
            if line.lstrip().startswith(banner_start):
                return True
        return False
def is_comment(self, line): BaseSpaceConfigParser
    def is_comment(self, line: str) -> bool:
        """Determine if line is a comment.

        Args:
            line: A config line from the device.

        Returns:
            True if line is a comment, else False.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> BaseSpaceConfigParser("interface Ethernet1/1").is_comment("interface Ethernet1/1")
            False
            >>> BaseSpaceConfigParser("!").is_comment("!")
            True
            >>>
        """
        for comment_char in self.comment_chars:
            if line.lstrip().startswith(comment_char):
                return True
        return False

ArubaConfigOSParser

from netutils.config.parser import ArubaConfigOSParser

Ancestors (MRO)

The Method Resolution Order is described below.

  1. ArubaConfigOSParser
  2. _ArubaBaseConfigParser
  3. BaseSpaceConfigParser
  4. BaseConfigParser

Descendant Classes

Attributes

Key Value Defined in
banner_end '!' _ArubaBaseConfigParser
banner_start ['banner', 'vacant-message'] BaseSpaceConfigParser
banner_start ['banner', 'vacant-message'] BaseConfigParser
comment_chars ['!'] _ArubaBaseConfigParser
comment_chars ['!'] BaseSpaceConfigParser
comment_chars ['!'] BaseConfigParser

Methods

def __init__(self, config): BaseSpaceConfigParser

BaseSpaceConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self._indent_level = 0
        super(BaseSpaceConfigParser, self).__init__(config)

BaseConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config: The config text to parse.
        """
        self.config = config
        self._config: t.Optional[str] = None
        self._current_parents: t.Tuple[str, ...] = ()
        self.generator_config = (line for line in self.config_lines_only.splitlines())
        self.config_lines: t.List[ConfigLine] = []
        self.build_config_relationship()
def _build_banner(self, config_line): _ArubaBaseConfigParser

_ArubaBaseConfigParser

    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None when banner end is the end of the config text.

        Raises:
            ValueError: When the parser is unable to identify the End of the Banner.
        """
        self._update_config_lines(config_line)
        self._current_parents += (config_line,)
        banner_config = []
        for line in self.generator_config:
            if not self.is_banner_end(line):
                banner_config.append(line)
            else:
                banner_config.append(line)
                line = "\n".join(banner_config)
                self._update_config_lines(line)
                self._current_parents = self._current_parents[:-1]
                try:
                    return next(self.generator_config)
                except StopIteration:
                    return None
        raise ValueError("Unable to parse banner end.")

BaseSpaceConfigParser

    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None

        Raises:
            ValueError: When the parser is unable to identify the end of the Banner.
        """
        self._update_config_lines(config_line)
        self._current_parents += (config_line,)
        banner_config = []
        for line in self.generator_config:
            if not self.is_banner_end(line):
                banner_config.append(line)
            else:
                line = normalise_delimiter_caret_c(self.banner_end, line)
                banner_config.append(line)
                line = "\n".join(banner_config)
                if line.endswith("^C"):
                    banner, end, _ = line.rpartition("^C")
                    line = banner + end
                self._update_config_lines(line)
                self._current_parents = self._current_parents[:-1]
                try:
                    return next(self.generator_config)
                except StopIteration:
                    return None

        raise ValueError("Unable to parse banner end.")
def _build_nested_config(self, line): BaseSpaceConfigParser
    def _build_nested_config(self, line: str) -> t.Optional[str]:
        """Handle building child config sections.

        Args:
            line: A configuration line from the configuration text.

        Returns:
            The next top-level configuration line in the configuration text or None when the last line of configuration
            text is a nested configuration line.

        Raises:
            IndexError: When the number of parents does not match the expected deindent level.
        """
        self._update_config_lines(line)
        for line in self.generator_config:
            if not line[0].isspace():
                self._current_parents = ()
                self.indent_level = 0
                return line

            spaces = self.get_leading_space_count(line)
            if spaces == self.indent_level:
                pass
            elif spaces > self.indent_level:
                previous_config = self.config_lines[-1]
                self._current_parents += (previous_config.config_line,)
            else:
                self._current_parents = self._remove_parents(line, spaces)

            if spaces != self.indent_level:
                self.indent_level = spaces

            if self.is_banner_start(line):
                banner_line = self._build_banner(line)
                if banner_line is None or not banner_line[0].isspace():
                    self._current_parents = ()
                    self.indent_level = 0
                    return banner_line
                line = banner_line

            self._update_config_lines(line)
        return None
def _match_type_check(line, pattern, match_type): BaseSpaceConfigParser
    @staticmethod
    def _match_type_check(line: str, pattern: str, match_type: str) -> bool:
        """Checks pattern for exact match or regex."""
        if match_type == "exact" and line == pattern:
            return True
        if match_type == "startswith" and line.startswith(pattern):
            return True
        if match_type == "endswith" and line.endswith(pattern):
            return True
        if match_type == "regex" and re.match(pattern, line):
            return True
        return False
def _parse_out_comments(self, config): _ArubaBaseConfigParser
    def _parse_out_comments(self, config: str) -> str:
        """Remove comments while retaining the banner end.

        Args:
            config (str): full config as a string.

        Returns:
            The non-comment lines from ``config``.
        """
        # Aruba AOS-CX uses "!" as both comments and the banner delimiter.
        # Even if another delimiter is used while creating the banner, show run changes the delimiter to use "!".
        # We need to remove comments while retaining the banner delimiter.

        config_lines = []
        banner_started = False
        banner_ended = False
        for line in config.splitlines():
            if self.is_banner_start(line):
                banner_started = True
                banner_ended = False
            if line and banner_started and not banner_ended:
                config_lines.append(line.rstrip())
                if line.lstrip().startswith(self.banner_end):
                    banner_ended = True
                    banner_started = False
            else:
                if line and not self.is_comment(line):
                    config_lines.append(line.rstrip())
        full_config = "\n".join(config_lines)
        return full_config
def _remove_parents(self, line, current_spaces): BaseSpaceConfigParser
    def _remove_parents(self, line: str, current_spaces: int) -> t.Tuple[str, ...]:
        """Remove parents from ``self._curent_parents`` based on indent levels.

        Args:
            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 = 1
        try:
            previous_parent = self._current_parents[-1]
            previous_indent = self.get_leading_space_count(previous_parent)
            while previous_indent > current_spaces:
                deindent_level += 1
                previous_parent = self._current_parents[-deindent_level]
                previous_indent = self.get_leading_space_count(previous_parent)
        except IndexError:
            raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n")
        parents = self._current_parents[:-deindent_level] or (self._current_parents[0],)
        return parents
def _update_config_lines(self, config_line): BaseSpaceConfigParser
    def _update_config_lines(self, config_line: str) -> None:
        """Add a ``ConfigLine`` object to ``self.config_lines``.

        Args:
            config_line: The current config line being evaluated.

        Returns:
            None
        """
        entry = ConfigLine(config_line, self._current_parents)
        self.config_lines.append(entry)
def build_config_relationship(self): BaseSpaceConfigParser

BaseSpaceConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        r"""Parse text tree of config lines and their parents.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
            >>> config = (
            ...     "interface Ethernet1/1\n"
            ...     "  vlan 10\n"
            ...     "  no shutdown\n"
            ...     "interface Ethernet1/2\n"
            ...     "  shutdown\n"
            ... )
            >>> config_tree = BaseSpaceConfigParser(config)
            >>> config_tree.build_config_relationship() == \
            ... [
            ...     ConfigLine(config_line='interface Ethernet1/1', parents=()),
            ...     ConfigLine(config_line='  vlan 10', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='  no shutdown', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='interface Ethernet1/2', parents=(),),
            ...     ConfigLine(config_line='  shutdown', parents=('interface Ethernet1/2',))
            ... ]
            True
        """
        for index, line in enumerate(self.generator_config):
            current_spaces = self.get_leading_space_count(line) if line[0].isspace() else 0
            if index == 0 and line[0].isspace():
                self._current_parents = self._remove_parents(line, current_spaces)
            elif not line[0].isspace():
                self._current_parents = ()
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore
            else:
                previous_config = self.config_lines[-1]
                self._current_parents = (previous_config.config_line,)
                self.indent_level = self.get_leading_space_count(line)
                if not self.is_banner_start(line):
                    line = self._build_nested_config(line)  # type: ignore
                else:
                    line = self._build_banner(line)  # type: ignore
                    if line is not None and line[0].isspace():
                        line = self._build_nested_config(line)  # type: ignore
                    else:
                        self._current_parents = ()

            if line is None:
                break
            elif self.is_banner_start(line):
                line = self._build_banner(line)  # type: ignore
                # line can potentially be another banner start therefore we do a secondary check.
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore

            self._update_config_lines(line)
        return self.config_lines

BaseConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        """Parse text tree of config lines and their parents."""
        raise NotImplementedError()
def find_all_children(self, pattern, match_type=exact): BaseSpaceConfigParser
    def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]:
        """Returns configuration part for a specific pattern not including parents.

        Args:
            pattern: pattern that describes parent.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith")
            >>> print(bgp_conf)
            ['router bgp 45000', '  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if (
                parents
                and self._match_type_check(parents, pattern, match_type)
                or self._match_type_check(cfg_line.config_line, pattern, match_type)
            ):
                config.append(cfg_line.config_line)
        return config
def find_children_w_parents(self, parent_pattern, child_pattern, match_type=exact): BaseSpaceConfigParser
    def find_children_w_parents(
        self, parent_pattern: str, child_pattern: str, match_type: str = "exact"
    ) -> t.List[str]:
        """Returns configuration part for a specific pattern including parents and children.

        Args:
            parent_pattern: pattern that describes parent.
            child_pattern: pattern that describes child.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern="  address-family", match_type="regex")
            >>> print(bgp_conf)
            ['  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        potential_parents = [
            elem.parents[0]
            for elem in self.build_config_relationship()
            if self._match_type_check(elem.config_line, child_pattern, match_type)
        ]
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if parents in potential_parents and self._match_type_check(
                parents,  # type: ignore[arg-type]
                parent_pattern,
                match_type,
            ):
                config.append(cfg_line.config_line)
        return config
def get_leading_space_count(config_line): BaseSpaceConfigParser
    @staticmethod
    def get_leading_space_count(config_line: str) -> int:
        r"""Determine how many spaces the ``config_line`` is indented.

        Args:
           config_line: A line of text in the config.

        Returns:
            The number of leading spaces.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''interface GigabitEthernet1\n description link to ISP'''
            >>> config_line = " description link to ISP"
            >>> indent_level = BaseSpaceConfigParser(config).get_leading_space_count(config_line)
            >>> indent_level
            1
            >>>
        """
        return len(config_line) - len(config_line.lstrip())
def is_banner_end(self, line): BaseSpaceConfigParser
    def is_banner_end(self, line: str) -> bool:
        """Determine if line ends the banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line ends banner, else False.
        """
        if self.banner_end in line:
            return True
        return False
def is_banner_start(self, line): BaseSpaceConfigParser
    def is_banner_start(self, line: str) -> bool:
        """Determine if the line starts a banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line starts banner, else False.
        """
        for banner_start in self.banner_start:
            if not line:
                return False
            if line.lstrip().startswith(banner_start):
                return True
        return False
def is_comment(self, line): BaseSpaceConfigParser
    def is_comment(self, line: str) -> bool:
        """Determine if line is a comment.

        Args:
            line: A config line from the device.

        Returns:
            True if line is a comment, else False.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> BaseSpaceConfigParser("interface Ethernet1/1").is_comment("interface Ethernet1/1")
            False
            >>> BaseSpaceConfigParser("!").is_comment("!")
            True
            >>>
        """
        for comment_char in self.comment_chars:
            if line.lstrip().startswith(comment_char):
                return True
        return False

ArubaConfigParser

from netutils.config.parser import ArubaConfigParser

Ancestors (MRO)

The Method Resolution Order is described below.

  1. ArubaConfigParser
  2. _ArubaBaseConfigParser
  3. BaseSpaceConfigParser
  4. BaseConfigParser

Descendant Classes

Attributes

Key Value Defined in
banner_end '!' _ArubaBaseConfigParser
banner_start ['banner', 'vacant-message'] BaseSpaceConfigParser
banner_start ['banner', 'vacant-message'] BaseConfigParser
comment_chars ['!'] _ArubaBaseConfigParser
comment_chars ['!'] BaseSpaceConfigParser
comment_chars ['!'] BaseConfigParser

Methods

def __init__(self, config): BaseSpaceConfigParser

BaseSpaceConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self._indent_level = 0
        super(BaseSpaceConfigParser, self).__init__(config)

BaseConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config: The config text to parse.
        """
        self.config = config
        self._config: t.Optional[str] = None
        self._current_parents: t.Tuple[str, ...] = ()
        self.generator_config = (line for line in self.config_lines_only.splitlines())
        self.config_lines: t.List[ConfigLine] = []
        self.build_config_relationship()
def _build_banner(self, config_line): _ArubaBaseConfigParser

_ArubaBaseConfigParser

    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None when banner end is the end of the config text.

        Raises:
            ValueError: When the parser is unable to identify the End of the Banner.
        """
        self._update_config_lines(config_line)
        self._current_parents += (config_line,)
        banner_config = []
        for line in self.generator_config:
            if not self.is_banner_end(line):
                banner_config.append(line)
            else:
                banner_config.append(line)
                line = "\n".join(banner_config)
                self._update_config_lines(line)
                self._current_parents = self._current_parents[:-1]
                try:
                    return next(self.generator_config)
                except StopIteration:
                    return None
        raise ValueError("Unable to parse banner end.")

BaseSpaceConfigParser

    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None

        Raises:
            ValueError: When the parser is unable to identify the end of the Banner.
        """
        self._update_config_lines(config_line)
        self._current_parents += (config_line,)
        banner_config = []
        for line in self.generator_config:
            if not self.is_banner_end(line):
                banner_config.append(line)
            else:
                line = normalise_delimiter_caret_c(self.banner_end, line)
                banner_config.append(line)
                line = "\n".join(banner_config)
                if line.endswith("^C"):
                    banner, end, _ = line.rpartition("^C")
                    line = banner + end
                self._update_config_lines(line)
                self._current_parents = self._current_parents[:-1]
                try:
                    return next(self.generator_config)
                except StopIteration:
                    return None

        raise ValueError("Unable to parse banner end.")
def _build_nested_config(self, line): BaseSpaceConfigParser
    def _build_nested_config(self, line: str) -> t.Optional[str]:
        """Handle building child config sections.

        Args:
            line: A configuration line from the configuration text.

        Returns:
            The next top-level configuration line in the configuration text or None when the last line of configuration
            text is a nested configuration line.

        Raises:
            IndexError: When the number of parents does not match the expected deindent level.
        """
        self._update_config_lines(line)
        for line in self.generator_config:
            if not line[0].isspace():
                self._current_parents = ()
                self.indent_level = 0
                return line

            spaces = self.get_leading_space_count(line)
            if spaces == self.indent_level:
                pass
            elif spaces > self.indent_level:
                previous_config = self.config_lines[-1]
                self._current_parents += (previous_config.config_line,)
            else:
                self._current_parents = self._remove_parents(line, spaces)

            if spaces != self.indent_level:
                self.indent_level = spaces

            if self.is_banner_start(line):
                banner_line = self._build_banner(line)
                if banner_line is None or not banner_line[0].isspace():
                    self._current_parents = ()
                    self.indent_level = 0
                    return banner_line
                line = banner_line

            self._update_config_lines(line)
        return None
def _match_type_check(line, pattern, match_type): BaseSpaceConfigParser
    @staticmethod
    def _match_type_check(line: str, pattern: str, match_type: str) -> bool:
        """Checks pattern for exact match or regex."""
        if match_type == "exact" and line == pattern:
            return True
        if match_type == "startswith" and line.startswith(pattern):
            return True
        if match_type == "endswith" and line.endswith(pattern):
            return True
        if match_type == "regex" and re.match(pattern, line):
            return True
        return False
def _parse_out_comments(self, config): _ArubaBaseConfigParser
    def _parse_out_comments(self, config: str) -> str:
        """Remove comments while retaining the banner end.

        Args:
            config (str): full config as a string.

        Returns:
            The non-comment lines from ``config``.
        """
        # Aruba AOS-CX uses "!" as both comments and the banner delimiter.
        # Even if another delimiter is used while creating the banner, show run changes the delimiter to use "!".
        # We need to remove comments while retaining the banner delimiter.

        config_lines = []
        banner_started = False
        banner_ended = False
        for line in config.splitlines():
            if self.is_banner_start(line):
                banner_started = True
                banner_ended = False
            if line and banner_started and not banner_ended:
                config_lines.append(line.rstrip())
                if line.lstrip().startswith(self.banner_end):
                    banner_ended = True
                    banner_started = False
            else:
                if line and not self.is_comment(line):
                    config_lines.append(line.rstrip())
        full_config = "\n".join(config_lines)
        return full_config
def _remove_parents(self, line, current_spaces): BaseSpaceConfigParser
    def _remove_parents(self, line: str, current_spaces: int) -> t.Tuple[str, ...]:
        """Remove parents from ``self._curent_parents`` based on indent levels.

        Args:
            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 = 1
        try:
            previous_parent = self._current_parents[-1]
            previous_indent = self.get_leading_space_count(previous_parent)
            while previous_indent > current_spaces:
                deindent_level += 1
                previous_parent = self._current_parents[-deindent_level]
                previous_indent = self.get_leading_space_count(previous_parent)
        except IndexError:
            raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n")
        parents = self._current_parents[:-deindent_level] or (self._current_parents[0],)
        return parents
def _update_config_lines(self, config_line): BaseSpaceConfigParser
    def _update_config_lines(self, config_line: str) -> None:
        """Add a ``ConfigLine`` object to ``self.config_lines``.

        Args:
            config_line: The current config line being evaluated.

        Returns:
            None
        """
        entry = ConfigLine(config_line, self._current_parents)
        self.config_lines.append(entry)
def build_config_relationship(self): BaseSpaceConfigParser

BaseSpaceConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        r"""Parse text tree of config lines and their parents.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
            >>> config = (
            ...     "interface Ethernet1/1\n"
            ...     "  vlan 10\n"
            ...     "  no shutdown\n"
            ...     "interface Ethernet1/2\n"
            ...     "  shutdown\n"
            ... )
            >>> config_tree = BaseSpaceConfigParser(config)
            >>> config_tree.build_config_relationship() == \
            ... [
            ...     ConfigLine(config_line='interface Ethernet1/1', parents=()),
            ...     ConfigLine(config_line='  vlan 10', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='  no shutdown', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='interface Ethernet1/2', parents=(),),
            ...     ConfigLine(config_line='  shutdown', parents=('interface Ethernet1/2',))
            ... ]
            True
        """
        for index, line in enumerate(self.generator_config):
            current_spaces = self.get_leading_space_count(line) if line[0].isspace() else 0
            if index == 0 and line[0].isspace():
                self._current_parents = self._remove_parents(line, current_spaces)
            elif not line[0].isspace():
                self._current_parents = ()
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore
            else:
                previous_config = self.config_lines[-1]
                self._current_parents = (previous_config.config_line,)
                self.indent_level = self.get_leading_space_count(line)
                if not self.is_banner_start(line):
                    line = self._build_nested_config(line)  # type: ignore
                else:
                    line = self._build_banner(line)  # type: ignore
                    if line is not None and line[0].isspace():
                        line = self._build_nested_config(line)  # type: ignore
                    else:
                        self._current_parents = ()

            if line is None:
                break
            elif self.is_banner_start(line):
                line = self._build_banner(line)  # type: ignore
                # line can potentially be another banner start therefore we do a secondary check.
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore

            self._update_config_lines(line)
        return self.config_lines

BaseConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        """Parse text tree of config lines and their parents."""
        raise NotImplementedError()
def find_all_children(self, pattern, match_type=exact): BaseSpaceConfigParser
    def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]:
        """Returns configuration part for a specific pattern not including parents.

        Args:
            pattern: pattern that describes parent.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith")
            >>> print(bgp_conf)
            ['router bgp 45000', '  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if (
                parents
                and self._match_type_check(parents, pattern, match_type)
                or self._match_type_check(cfg_line.config_line, pattern, match_type)
            ):
                config.append(cfg_line.config_line)
        return config
def find_children_w_parents(self, parent_pattern, child_pattern, match_type=exact): BaseSpaceConfigParser
    def find_children_w_parents(
        self, parent_pattern: str, child_pattern: str, match_type: str = "exact"
    ) -> t.List[str]:
        """Returns configuration part for a specific pattern including parents and children.

        Args:
            parent_pattern: pattern that describes parent.
            child_pattern: pattern that describes child.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern="  address-family", match_type="regex")
            >>> print(bgp_conf)
            ['  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        potential_parents = [
            elem.parents[0]
            for elem in self.build_config_relationship()
            if self._match_type_check(elem.config_line, child_pattern, match_type)
        ]
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if parents in potential_parents and self._match_type_check(
                parents,  # type: ignore[arg-type]
                parent_pattern,
                match_type,
            ):
                config.append(cfg_line.config_line)
        return config
def get_leading_space_count(config_line): BaseSpaceConfigParser
    @staticmethod
    def get_leading_space_count(config_line: str) -> int:
        r"""Determine how many spaces the ``config_line`` is indented.

        Args:
           config_line: A line of text in the config.

        Returns:
            The number of leading spaces.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''interface GigabitEthernet1\n description link to ISP'''
            >>> config_line = " description link to ISP"
            >>> indent_level = BaseSpaceConfigParser(config).get_leading_space_count(config_line)
            >>> indent_level
            1
            >>>
        """
        return len(config_line) - len(config_line.lstrip())
def is_banner_end(self, line): BaseSpaceConfigParser
    def is_banner_end(self, line: str) -> bool:
        """Determine if line ends the banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line ends banner, else False.
        """
        if self.banner_end in line:
            return True
        return False
def is_banner_start(self, line): BaseSpaceConfigParser
    def is_banner_start(self, line: str) -> bool:
        """Determine if the line starts a banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line starts banner, else False.
        """
        for banner_start in self.banner_start:
            if not line:
                return False
            if line.lstrip().startswith(banner_start):
                return True
        return False
def is_comment(self, line): BaseSpaceConfigParser
    def is_comment(self, line: str) -> bool:
        """Determine if line is a comment.

        Args:
            line: A config line from the device.

        Returns:
            True if line is a comment, else False.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> BaseSpaceConfigParser("interface Ethernet1/1").is_comment("interface Ethernet1/1")
            False
            >>> BaseSpaceConfigParser("!").is_comment("!")
            True
            >>>
        """
        for comment_char in self.comment_chars:
            if line.lstrip().startswith(comment_char):
                return True
        return False

BaseBraceConfigParser

from netutils.config.parser import BaseBraceConfigParser

Ancestors (MRO)

The Method Resolution Order is described below.

  1. BaseBraceConfigParser
  2. BaseConfigParser

Descendant Classes

The below Classes rely on: BaseBraceConfigParser.

Attributes

Key Value Defined in
banner_start ['banner', 'vacant-message'] BaseConfigParser
comment_chars ['!'] BaseConfigParser

Methods

def __init__(self, config): BaseConfigParser
    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config: The config text to parse.
        """
        self.config = config
        self._config: t.Optional[str] = None
        self._current_parents: t.Tuple[str, ...] = ()
        self.generator_config = (line for line in self.config_lines_only.splitlines())
        self.config_lines: t.List[ConfigLine] = []
        self.build_config_relationship()
def _build_multiline_config(self, delimiter): BaseBraceConfigParser
    def _build_multiline_config(self, delimiter: str) -> t.Optional[ConfigLine]:
        r"""Build config sections between characters demarcating multiline strings.

        Args:
            delimiter: The text to look for to end multiline config.

        Returns:
            The multiline string text that was added to ``self.config_lines``.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
            >>> config = (
            ...     'sys syslog {\n'
            ...     '    include "\n'
            ...     '        filter f_local0 {\n'
            ...     '        facility(local0) and not match(\"DEBUG");\n'
            ...     '    "\n'
            ...     '}'
            ... )
            >>> parser = BaseBraceConfigParser(config)
            >>> # '    include "' started a multiline config
            >>> parser.config_lines == \
            ... [
            ...     ConfigLine(config_line='sys syslog {', parents=()),
            ...     ConfigLine(config_line='    include "', parents=('sys syslog {',)),
            ...     ConfigLine(config_line='        filter f_local0 {', parents=('sys syslog {',)),
            ...     ConfigLine(config_line='        facility(local0) and not match("DEBUG");', parents=('sys syslog {', '        filter f_local0 {')),
            ...     ConfigLine(config_line='    "', parents=('sys syslog {', '        filter f_local0 {')),
            ...     ConfigLine(config_line='}', parents=('sys syslog {', '        filter f_local0 {'))
            ... ]
            True
        """
        multiline_config = []
        for line in self.generator_config:
            multiline_config.append(line)
            if line.lstrip() == delimiter:
                multiline_entry = ConfigLine("\n".join(multiline_config), self._current_parents)
                self.config_lines.append(multiline_entry)
                self._current_parents = self._current_parents[:-1]
                return multiline_entry
        return None
def build_config_relationship(self): BaseBraceConfigParser

BaseBraceConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        r"""Parse text tree of config lines and their parents.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
            >>> config = '''auth ldap system-auth {
            ...         port ldaps
            ...         servers { ams-lda01.ntc.com }
            ...     }
            ...     auth partition Common {
            ...         description "Repository for system objects and shared objects."
            ...     }
            ...     auth password-policy { }'''
            >>> config_tree = BaseBraceConfigParser(config)
            >>> config_tree.build_config_relationship() == \
            ... [
            ...     ConfigLine(config_line='auth ldap system-auth {', parents=()),
            ...     ConfigLine(config_line='        port ldaps', parents=('auth ldap system-auth {',)),
            ...     ConfigLine(config_line='        servers { ams-lda01.ntc.com }', parents=('auth ldap system-auth {',)),
            ...     ConfigLine(config_line='    }', parents=('auth ldap system-auth {',)),
            ...     ConfigLine(config_line='    auth partition Common {', parents=()),
            ...     ConfigLine(config_line='        description "Repository for system objects and shared objects."', parents=('    auth partition Common {',)), ConfigLine(config_line='    }', parents=('    auth partition Common {',)),
            ...     ConfigLine(config_line='    auth password-policy { }', parents=())
            ... ]
            True
        """
        for line in self.generator_config:
            self.config_lines.append(ConfigLine(line, self._current_parents))
            line_end = line[-1]
            if line.endswith("{"):
                self._current_parents += (line,)
            elif line.lstrip() == "}":
                self._current_parents = self._current_parents[:-1]
            elif line_end in self.multiline_delimiters and line.count(line_end) == 1:
                self._current_parents += (line,)
                self._build_multiline_config(line_end)

        return self.config_lines

BaseConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        """Parse text tree of config lines and their parents."""
        raise NotImplementedError()

BaseConfigParser

from netutils.config.parser import BaseConfigParser

Ancestors (MRO)

The Method Resolution Order is described below.

  1. BaseConfigParser

Descendant Classes

The below Classes rely on: BaseConfigParser.

Attributes

Key Value Defined in
banner_start ['banner', 'vacant-message'] BaseConfigParser
comment_chars ['!'] BaseConfigParser

Methods

def __init__(self, config): BaseConfigParser
    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config: The config text to parse.
        """
        self.config = config
        self._config: t.Optional[str] = None
        self._current_parents: t.Tuple[str, ...] = ()
        self.generator_config = (line for line in self.config_lines_only.splitlines())
        self.config_lines: t.List[ConfigLine] = []
        self.build_config_relationship()
def build_config_relationship(self): BaseConfigParser
    def build_config_relationship(self) -> t.List[ConfigLine]:
        """Parse text tree of config lines and their parents."""
        raise NotImplementedError()

BaseSpaceConfigParser

from netutils.config.parser import BaseSpaceConfigParser

Ancestors (MRO)

The Method Resolution Order is described below.

  1. BaseSpaceConfigParser
  2. BaseConfigParser

Descendant Classes

The below Classes rely on: BaseSpaceConfigParser.

Attributes

Key Value Defined in
banner_start ['banner', 'vacant-message'] BaseSpaceConfigParser
banner_start ['banner', 'vacant-message'] BaseConfigParser
comment_chars ['!'] BaseSpaceConfigParser
comment_chars ['!'] BaseConfigParser

Methods

def __init__(self, config): BaseSpaceConfigParser

BaseSpaceConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self._indent_level = 0
        super(BaseSpaceConfigParser, self).__init__(config)

BaseConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config: The config text to parse.
        """
        self.config = config
        self._config: t.Optional[str] = None
        self._current_parents: t.Tuple[str, ...] = ()
        self.generator_config = (line for line in self.config_lines_only.splitlines())
        self.config_lines: t.List[ConfigLine] = []
        self.build_config_relationship()
def _build_banner(self, config_line): BaseSpaceConfigParser
    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None

        Raises:
            ValueError: When the parser is unable to identify the end of the Banner.
        """
        self._update_config_lines(config_line)
        self._current_parents += (config_line,)
        banner_config = []
        for line in self.generator_config:
            if not self.is_banner_end(line):
                banner_config.append(line)
            else:
                line = normalise_delimiter_caret_c(self.banner_end, line)
                banner_config.append(line)
                line = "\n".join(banner_config)
                if line.endswith("^C"):
                    banner, end, _ = line.rpartition("^C")
                    line = banner + end
                self._update_config_lines(line)
                self._current_parents = self._current_parents[:-1]
                try:
                    return next(self.generator_config)
                except StopIteration:
                    return None

        raise ValueError("Unable to parse banner end.")
def _build_nested_config(self, line): BaseSpaceConfigParser
    def _build_nested_config(self, line: str) -> t.Optional[str]:
        """Handle building child config sections.

        Args:
            line: A configuration line from the configuration text.

        Returns:
            The next top-level configuration line in the configuration text or None when the last line of configuration
            text is a nested configuration line.

        Raises:
            IndexError: When the number of parents does not match the expected deindent level.
        """
        self._update_config_lines(line)
        for line in self.generator_config:
            if not line[0].isspace():
                self._current_parents = ()
                self.indent_level = 0
                return line

            spaces = self.get_leading_space_count(line)
            if spaces == self.indent_level:
                pass
            elif spaces > self.indent_level:
                previous_config = self.config_lines[-1]
                self._current_parents += (previous_config.config_line,)
            else:
                self._current_parents = self._remove_parents(line, spaces)

            if spaces != self.indent_level:
                self.indent_level = spaces

            if self.is_banner_start(line):
                banner_line = self._build_banner(line)
                if banner_line is None or not banner_line[0].isspace():
                    self._current_parents = ()
                    self.indent_level = 0
                    return banner_line
                line = banner_line

            self._update_config_lines(line)
        return None
def _match_type_check(line, pattern, match_type): BaseSpaceConfigParser
    @staticmethod
    def _match_type_check(line: str, pattern: str, match_type: str) -> bool:
        """Checks pattern for exact match or regex."""
        if match_type == "exact" and line == pattern:
            return True
        if match_type == "startswith" and line.startswith(pattern):
            return True
        if match_type == "endswith" and line.endswith(pattern):
            return True
        if match_type == "regex" and re.match(pattern, line):
            return True
        return False
def _remove_parents(self, line, current_spaces): BaseSpaceConfigParser
    def _remove_parents(self, line: str, current_spaces: int) -> t.Tuple[str, ...]:
        """Remove parents from ``self._curent_parents`` based on indent levels.

        Args:
            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 = 1
        try:
            previous_parent = self._current_parents[-1]
            previous_indent = self.get_leading_space_count(previous_parent)
            while previous_indent > current_spaces:
                deindent_level += 1
                previous_parent = self._current_parents[-deindent_level]
                previous_indent = self.get_leading_space_count(previous_parent)
        except IndexError:
            raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n")
        parents = self._current_parents[:-deindent_level] or (self._current_parents[0],)
        return parents
def _update_config_lines(self, config_line): BaseSpaceConfigParser
    def _update_config_lines(self, config_line: str) -> None:
        """Add a ``ConfigLine`` object to ``self.config_lines``.

        Args:
            config_line: The current config line being evaluated.

        Returns:
            None
        """
        entry = ConfigLine(config_line, self._current_parents)
        self.config_lines.append(entry)
def build_config_relationship(self): BaseSpaceConfigParser

BaseSpaceConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        r"""Parse text tree of config lines and their parents.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
            >>> config = (
            ...     "interface Ethernet1/1\n"
            ...     "  vlan 10\n"
            ...     "  no shutdown\n"
            ...     "interface Ethernet1/2\n"
            ...     "  shutdown\n"
            ... )
            >>> config_tree = BaseSpaceConfigParser(config)
            >>> config_tree.build_config_relationship() == \
            ... [
            ...     ConfigLine(config_line='interface Ethernet1/1', parents=()),
            ...     ConfigLine(config_line='  vlan 10', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='  no shutdown', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='interface Ethernet1/2', parents=(),),
            ...     ConfigLine(config_line='  shutdown', parents=('interface Ethernet1/2',))
            ... ]
            True
        """
        for index, line in enumerate(self.generator_config):
            current_spaces = self.get_leading_space_count(line) if line[0].isspace() else 0
            if index == 0 and line[0].isspace():
                self._current_parents = self._remove_parents(line, current_spaces)
            elif not line[0].isspace():
                self._current_parents = ()
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore
            else:
                previous_config = self.config_lines[-1]
                self._current_parents = (previous_config.config_line,)
                self.indent_level = self.get_leading_space_count(line)
                if not self.is_banner_start(line):
                    line = self._build_nested_config(line)  # type: ignore
                else:
                    line = self._build_banner(line)  # type: ignore
                    if line is not None and line[0].isspace():
                        line = self._build_nested_config(line)  # type: ignore
                    else:
                        self._current_parents = ()

            if line is None:
                break
            elif self.is_banner_start(line):
                line = self._build_banner(line)  # type: ignore
                # line can potentially be another banner start therefore we do a secondary check.
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore

            self._update_config_lines(line)
        return self.config_lines

BaseConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        """Parse text tree of config lines and their parents."""
        raise NotImplementedError()
def find_all_children(self, pattern, match_type=exact): BaseSpaceConfigParser
    def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]:
        """Returns configuration part for a specific pattern not including parents.

        Args:
            pattern: pattern that describes parent.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith")
            >>> print(bgp_conf)
            ['router bgp 45000', '  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if (
                parents
                and self._match_type_check(parents, pattern, match_type)
                or self._match_type_check(cfg_line.config_line, pattern, match_type)
            ):
                config.append(cfg_line.config_line)
        return config
def find_children_w_parents(self, parent_pattern, child_pattern, match_type=exact): BaseSpaceConfigParser
    def find_children_w_parents(
        self, parent_pattern: str, child_pattern: str, match_type: str = "exact"
    ) -> t.List[str]:
        """Returns configuration part for a specific pattern including parents and children.

        Args:
            parent_pattern: pattern that describes parent.
            child_pattern: pattern that describes child.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern="  address-family", match_type="regex")
            >>> print(bgp_conf)
            ['  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        potential_parents = [
            elem.parents[0]
            for elem in self.build_config_relationship()
            if self._match_type_check(elem.config_line, child_pattern, match_type)
        ]
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if parents in potential_parents and self._match_type_check(
                parents,  # type: ignore[arg-type]
                parent_pattern,
                match_type,
            ):
                config.append(cfg_line.config_line)
        return config
def get_leading_space_count(config_line): BaseSpaceConfigParser
    @staticmethod
    def get_leading_space_count(config_line: str) -> int:
        r"""Determine how many spaces the ``config_line`` is indented.

        Args:
           config_line: A line of text in the config.

        Returns:
            The number of leading spaces.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''interface GigabitEthernet1\n description link to ISP'''
            >>> config_line = " description link to ISP"
            >>> indent_level = BaseSpaceConfigParser(config).get_leading_space_count(config_line)
            >>> indent_level
            1
            >>>
        """
        return len(config_line) - len(config_line.lstrip())
def is_banner_end(self, line): BaseSpaceConfigParser
    def is_banner_end(self, line: str) -> bool:
        """Determine if line ends the banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line ends banner, else False.
        """
        if self.banner_end in line:
            return True
        return False
def is_banner_start(self, line): BaseSpaceConfigParser
    def is_banner_start(self, line: str) -> bool:
        """Determine if the line starts a banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line starts banner, else False.
        """
        for banner_start in self.banner_start:
            if not line:
                return False
            if line.lstrip().startswith(banner_start):
                return True
        return False
def is_comment(self, line): BaseSpaceConfigParser
    def is_comment(self, line: str) -> bool:
        """Determine if line is a comment.

        Args:
            line: A config line from the device.

        Returns:
            True if line is a comment, else False.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> BaseSpaceConfigParser("interface Ethernet1/1").is_comment("interface Ethernet1/1")
            False
            >>> BaseSpaceConfigParser("!").is_comment("!")
            True
            >>>
        """
        for comment_char in self.comment_chars:
            if line.lstrip().startswith(comment_char):
                return True
        return False

CienaConfigParser

from netutils.config.parser import CienaConfigParser

Ancestors (MRO)

The Method Resolution Order is described below.

  1. CienaConfigParser
  2. BaseSpaceConfigParser
  3. BaseConfigParser

Descendant Classes

Attributes

Key Value Defined in
banner_start ['banner', 'vacant-message'] BaseSpaceConfigParser
banner_start ['banner', 'vacant-message'] BaseConfigParser
comment_chars ['!'] BaseSpaceConfigParser
comment_chars ['!'] BaseConfigParser

Methods

def __init__(self, config): BaseSpaceConfigParser

BaseSpaceConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self._indent_level = 0
        super(BaseSpaceConfigParser, self).__init__(config)

BaseConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config: The config text to parse.
        """
        self.config = config
        self._config: t.Optional[str] = None
        self._current_parents: t.Tuple[str, ...] = ()
        self.generator_config = (line for line in self.config_lines_only.splitlines())
        self.config_lines: t.List[ConfigLine] = []
        self.build_config_relationship()
def _build_banner(self, config_line): BaseSpaceConfigParser
    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None

        Raises:
            ValueError: When the parser is unable to identify the end of the Banner.
        """
        self._update_config_lines(config_line)
        self._current_parents += (config_line,)
        banner_config = []
        for line in self.generator_config:
            if not self.is_banner_end(line):
                banner_config.append(line)
            else:
                line = normalise_delimiter_caret_c(self.banner_end, line)
                banner_config.append(line)
                line = "\n".join(banner_config)
                if line.endswith("^C"):
                    banner, end, _ = line.rpartition("^C")
                    line = banner + end
                self._update_config_lines(line)
                self._current_parents = self._current_parents[:-1]
                try:
                    return next(self.generator_config)
                except StopIteration:
                    return None

        raise ValueError("Unable to parse banner end.")
def _build_nested_config(self, line): BaseSpaceConfigParser
    def _build_nested_config(self, line: str) -> t.Optional[str]:
        """Handle building child config sections.

        Args:
            line: A configuration line from the configuration text.

        Returns:
            The next top-level configuration line in the configuration text or None when the last line of configuration
            text is a nested configuration line.

        Raises:
            IndexError: When the number of parents does not match the expected deindent level.
        """
        self._update_config_lines(line)
        for line in self.generator_config:
            if not line[0].isspace():
                self._current_parents = ()
                self.indent_level = 0
                return line

            spaces = self.get_leading_space_count(line)
            if spaces == self.indent_level:
                pass
            elif spaces > self.indent_level:
                previous_config = self.config_lines[-1]
                self._current_parents += (previous_config.config_line,)
            else:
                self._current_parents = self._remove_parents(line, spaces)

            if spaces != self.indent_level:
                self.indent_level = spaces

            if self.is_banner_start(line):
                banner_line = self._build_banner(line)
                if banner_line is None or not banner_line[0].isspace():
                    self._current_parents = ()
                    self.indent_level = 0
                    return banner_line
                line = banner_line

            self._update_config_lines(line)
        return None
def _match_type_check(line, pattern, match_type): BaseSpaceConfigParser
    @staticmethod
    def _match_type_check(line: str, pattern: str, match_type: str) -> bool:
        """Checks pattern for exact match or regex."""
        if match_type == "exact" and line == pattern:
            return True
        if match_type == "startswith" and line.startswith(pattern):
            return True
        if match_type == "endswith" and line.endswith(pattern):
            return True
        if match_type == "regex" and re.match(pattern, line):
            return True
        return False
def _remove_parents(self, line, current_spaces): BaseSpaceConfigParser
    def _remove_parents(self, line: str, current_spaces: int) -> t.Tuple[str, ...]:
        """Remove parents from ``self._curent_parents`` based on indent levels.

        Args:
            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 = 1
        try:
            previous_parent = self._current_parents[-1]
            previous_indent = self.get_leading_space_count(previous_parent)
            while previous_indent > current_spaces:
                deindent_level += 1
                previous_parent = self._current_parents[-deindent_level]
                previous_indent = self.get_leading_space_count(previous_parent)
        except IndexError:
            raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n")
        parents = self._current_parents[:-deindent_level] or (self._current_parents[0],)
        return parents
def _update_config_lines(self, config_line): BaseSpaceConfigParser
    def _update_config_lines(self, config_line: str) -> None:
        """Add a ``ConfigLine`` object to ``self.config_lines``.

        Args:
            config_line: The current config line being evaluated.

        Returns:
            None
        """
        entry = ConfigLine(config_line, self._current_parents)
        self.config_lines.append(entry)
def build_config_relationship(self): BaseSpaceConfigParser

BaseSpaceConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        r"""Parse text tree of config lines and their parents.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
            >>> config = (
            ...     "interface Ethernet1/1\n"
            ...     "  vlan 10\n"
            ...     "  no shutdown\n"
            ...     "interface Ethernet1/2\n"
            ...     "  shutdown\n"
            ... )
            >>> config_tree = BaseSpaceConfigParser(config)
            >>> config_tree.build_config_relationship() == \
            ... [
            ...     ConfigLine(config_line='interface Ethernet1/1', parents=()),
            ...     ConfigLine(config_line='  vlan 10', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='  no shutdown', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='interface Ethernet1/2', parents=(),),
            ...     ConfigLine(config_line='  shutdown', parents=('interface Ethernet1/2',))
            ... ]
            True
        """
        for index, line in enumerate(self.generator_config):
            current_spaces = self.get_leading_space_count(line) if line[0].isspace() else 0
            if index == 0 and line[0].isspace():
                self._current_parents = self._remove_parents(line, current_spaces)
            elif not line[0].isspace():
                self._current_parents = ()
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore
            else:
                previous_config = self.config_lines[-1]
                self._current_parents = (previous_config.config_line,)
                self.indent_level = self.get_leading_space_count(line)
                if not self.is_banner_start(line):
                    line = self._build_nested_config(line)  # type: ignore
                else:
                    line = self._build_banner(line)  # type: ignore
                    if line is not None and line[0].isspace():
                        line = self._build_nested_config(line)  # type: ignore
                    else:
                        self._current_parents = ()

            if line is None:
                break
            elif self.is_banner_start(line):
                line = self._build_banner(line)  # type: ignore
                # line can potentially be another banner start therefore we do a secondary check.
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore

            self._update_config_lines(line)
        return self.config_lines

BaseConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        """Parse text tree of config lines and their parents."""
        raise NotImplementedError()
def find_all_children(self, pattern, match_type=exact): BaseSpaceConfigParser
    def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]:
        """Returns configuration part for a specific pattern not including parents.

        Args:
            pattern: pattern that describes parent.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith")
            >>> print(bgp_conf)
            ['router bgp 45000', '  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if (
                parents
                and self._match_type_check(parents, pattern, match_type)
                or self._match_type_check(cfg_line.config_line, pattern, match_type)
            ):
                config.append(cfg_line.config_line)
        return config
def find_children_w_parents(self, parent_pattern, child_pattern, match_type=exact): BaseSpaceConfigParser
    def find_children_w_parents(
        self, parent_pattern: str, child_pattern: str, match_type: str = "exact"
    ) -> t.List[str]:
        """Returns configuration part for a specific pattern including parents and children.

        Args:
            parent_pattern: pattern that describes parent.
            child_pattern: pattern that describes child.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern="  address-family", match_type="regex")
            >>> print(bgp_conf)
            ['  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        potential_parents = [
            elem.parents[0]
            for elem in self.build_config_relationship()
            if self._match_type_check(elem.config_line, child_pattern, match_type)
        ]
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if parents in potential_parents and self._match_type_check(
                parents,  # type: ignore[arg-type]
                parent_pattern,
                match_type,
            ):
                config.append(cfg_line.config_line)
        return config
def get_leading_space_count(config_line): BaseSpaceConfigParser
    @staticmethod
    def get_leading_space_count(config_line: str) -> int:
        r"""Determine how many spaces the ``config_line`` is indented.

        Args:
           config_line: A line of text in the config.

        Returns:
            The number of leading spaces.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''interface GigabitEthernet1\n description link to ISP'''
            >>> config_line = " description link to ISP"
            >>> indent_level = BaseSpaceConfigParser(config).get_leading_space_count(config_line)
            >>> indent_level
            1
            >>>
        """
        return len(config_line) - len(config_line.lstrip())
def is_banner_end(self, line): BaseSpaceConfigParser
    def is_banner_end(self, line: str) -> bool:
        """Determine if line ends the banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line ends banner, else False.
        """
        if self.banner_end in line:
            return True
        return False
def is_banner_start(self, line): BaseSpaceConfigParser
    def is_banner_start(self, line: str) -> bool:
        """Determine if the line starts a banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line starts banner, else False.
        """
        for banner_start in self.banner_start:
            if not line:
                return False
            if line.lstrip().startswith(banner_start):
                return True
        return False
def is_comment(self, line): BaseSpaceConfigParser
    def is_comment(self, line: str) -> bool:
        """Determine if line is a comment.

        Args:
            line: A config line from the device.

        Returns:
            True if line is a comment, else False.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> BaseSpaceConfigParser("interface Ethernet1/1").is_comment("interface Ethernet1/1")
            False
            >>> BaseSpaceConfigParser("!").is_comment("!")
            True
            >>>
        """
        for comment_char in self.comment_chars:
            if line.lstrip().startswith(comment_char):
                return True
        return False

CiscoConfigParser

from netutils.config.parser import CiscoConfigParser

Ancestors (MRO)

The Method Resolution Order is described below.

  1. CiscoConfigParser
  2. BaseSpaceConfigParser
  3. BaseConfigParser

Descendant Classes

The below Classes rely on: CiscoConfigParser.

Attributes

Key Value Defined in
banner_start ['banner', 'vacant-message'] BaseSpaceConfigParser
banner_start ['banner', 'vacant-message'] BaseConfigParser
comment_chars ['!'] BaseSpaceConfigParser
comment_chars ['!'] BaseConfigParser
regex_banner re.compile('^(banner\\s+\\S+|\\s*vacant-message)\\s+(?P<banner_delimiter>\\^C|.)') CiscoConfigParser

Methods

def __init__(self, config): CiscoConfigParser

CiscoConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self._banner_end: t.Optional[str] = None
        super(CiscoConfigParser, self).__init__(config)

BaseSpaceConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self._indent_level = 0
        super(BaseSpaceConfigParser, self).__init__(config)

BaseConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config: The config text to parse.
        """
        self.config = config
        self._config: t.Optional[str] = None
        self._current_parents: t.Tuple[str, ...] = ()
        self.generator_config = (line for line in self.config_lines_only.splitlines())
        self.config_lines: t.List[ConfigLine] = []
        self.build_config_relationship()
def _build_banner(self, config_line): CiscoConfigParser

CiscoConfigParser

    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None when banner end is the end of the config text.

        Raises:
            ValueError: When the parser is unable to identify the End of the Banner.
        """
        if self.is_banner_one_line(config_line):
            self._update_config_lines(config_line)
            try:
                return next(self.generator_config)
            except StopIteration:
                return None
        return super(CiscoConfigParser, self)._build_banner(config_line)

BaseSpaceConfigParser

    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None

        Raises:
            ValueError: When the parser is unable to identify the end of the Banner.
        """
        self._update_config_lines(config_line)
        self._current_parents += (config_line,)
        banner_config = []
        for line in self.generator_config:
            if not self.is_banner_end(line):
                banner_config.append(line)
            else:
                line = normalise_delimiter_caret_c(self.banner_end, line)
                banner_config.append(line)
                line = "\n".join(banner_config)
                if line.endswith("^C"):
                    banner, end, _ = line.rpartition("^C")
                    line = banner + end
                self._update_config_lines(line)
                self._current_parents = self._current_parents[:-1]
                try:
                    return next(self.generator_config)
                except StopIteration:
                    return None

        raise ValueError("Unable to parse banner end.")
def _build_nested_config(self, line): BaseSpaceConfigParser
    def _build_nested_config(self, line: str) -> t.Optional[str]:
        """Handle building child config sections.

        Args:
            line: A configuration line from the configuration text.

        Returns:
            The next top-level configuration line in the configuration text or None when the last line of configuration
            text is a nested configuration line.

        Raises:
            IndexError: When the number of parents does not match the expected deindent level.
        """
        self._update_config_lines(line)
        for line in self.generator_config:
            if not line[0].isspace():
                self._current_parents = ()
                self.indent_level = 0
                return line

            spaces = self.get_leading_space_count(line)
            if spaces == self.indent_level:
                pass
            elif spaces > self.indent_level:
                previous_config = self.config_lines[-1]
                self._current_parents += (previous_config.config_line,)
            else:
                self._current_parents = self._remove_parents(line, spaces)

            if spaces != self.indent_level:
                self.indent_level = spaces

            if self.is_banner_start(line):
                banner_line = self._build_banner(line)
                if banner_line is None or not banner_line[0].isspace():
                    self._current_parents = ()
                    self.indent_level = 0
                    return banner_line
                line = banner_line

            self._update_config_lines(line)
        return None
def _match_type_check(line, pattern, match_type): BaseSpaceConfigParser
    @staticmethod
    def _match_type_check(line: str, pattern: str, match_type: str) -> bool:
        """Checks pattern for exact match or regex."""
        if match_type == "exact" and line == pattern:
            return True
        if match_type == "startswith" and line.startswith(pattern):
            return True
        if match_type == "endswith" and line.endswith(pattern):
            return True
        if match_type == "regex" and re.match(pattern, line):
            return True
        return False
def _remove_parents(self, line, current_spaces): BaseSpaceConfigParser
    def _remove_parents(self, line: str, current_spaces: int) -> t.Tuple[str, ...]:
        """Remove parents from ``self._curent_parents`` based on indent levels.

        Args:
            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 = 1
        try:
            previous_parent = self._current_parents[-1]
            previous_indent = self.get_leading_space_count(previous_parent)
            while previous_indent > current_spaces:
                deindent_level += 1
                previous_parent = self._current_parents[-deindent_level]
                previous_indent = self.get_leading_space_count(previous_parent)
        except IndexError:
            raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n")
        parents = self._current_parents[:-deindent_level] or (self._current_parents[0],)
        return parents
def _update_config_lines(self, config_line): BaseSpaceConfigParser
    def _update_config_lines(self, config_line: str) -> None:
        """Add a ``ConfigLine`` object to ``self.config_lines``.

        Args:
            config_line: The current config line being evaluated.

        Returns:
            None
        """
        entry = ConfigLine(config_line, self._current_parents)
        self.config_lines.append(entry)
def build_config_relationship(self): BaseSpaceConfigParser

BaseSpaceConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        r"""Parse text tree of config lines and their parents.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
            >>> config = (
            ...     "interface Ethernet1/1\n"
            ...     "  vlan 10\n"
            ...     "  no shutdown\n"
            ...     "interface Ethernet1/2\n"
            ...     "  shutdown\n"
            ... )
            >>> config_tree = BaseSpaceConfigParser(config)
            >>> config_tree.build_config_relationship() == \
            ... [
            ...     ConfigLine(config_line='interface Ethernet1/1', parents=()),
            ...     ConfigLine(config_line='  vlan 10', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='  no shutdown', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='interface Ethernet1/2', parents=(),),
            ...     ConfigLine(config_line='  shutdown', parents=('interface Ethernet1/2',))
            ... ]
            True
        """
        for index, line in enumerate(self.generator_config):
            current_spaces = self.get_leading_space_count(line) if line[0].isspace() else 0
            if index == 0 and line[0].isspace():
                self._current_parents = self._remove_parents(line, current_spaces)
            elif not line[0].isspace():
                self._current_parents = ()
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore
            else:
                previous_config = self.config_lines[-1]
                self._current_parents = (previous_config.config_line,)
                self.indent_level = self.get_leading_space_count(line)
                if not self.is_banner_start(line):
                    line = self._build_nested_config(line)  # type: ignore
                else:
                    line = self._build_banner(line)  # type: ignore
                    if line is not None and line[0].isspace():
                        line = self._build_nested_config(line)  # type: ignore
                    else:
                        self._current_parents = ()

            if line is None:
                break
            elif self.is_banner_start(line):
                line = self._build_banner(line)  # type: ignore
                # line can potentially be another banner start therefore we do a secondary check.
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore

            self._update_config_lines(line)
        return self.config_lines

BaseConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        """Parse text tree of config lines and their parents."""
        raise NotImplementedError()
def find_all_children(self, pattern, match_type=exact): BaseSpaceConfigParser
    def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]:
        """Returns configuration part for a specific pattern not including parents.

        Args:
            pattern: pattern that describes parent.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith")
            >>> print(bgp_conf)
            ['router bgp 45000', '  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if (
                parents
                and self._match_type_check(parents, pattern, match_type)
                or self._match_type_check(cfg_line.config_line, pattern, match_type)
            ):
                config.append(cfg_line.config_line)
        return config
def find_children_w_parents(self, parent_pattern, child_pattern, match_type=exact): BaseSpaceConfigParser
    def find_children_w_parents(
        self, parent_pattern: str, child_pattern: str, match_type: str = "exact"
    ) -> t.List[str]:
        """Returns configuration part for a specific pattern including parents and children.

        Args:
            parent_pattern: pattern that describes parent.
            child_pattern: pattern that describes child.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern="  address-family", match_type="regex")
            >>> print(bgp_conf)
            ['  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        potential_parents = [
            elem.parents[0]
            for elem in self.build_config_relationship()
            if self._match_type_check(elem.config_line, child_pattern, match_type)
        ]
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if parents in potential_parents and self._match_type_check(
                parents,  # type: ignore[arg-type]
                parent_pattern,
                match_type,
            ):
                config.append(cfg_line.config_line)
        return config
def get_leading_space_count(config_line): BaseSpaceConfigParser
    @staticmethod
    def get_leading_space_count(config_line: str) -> int:
        r"""Determine how many spaces the ``config_line`` is indented.

        Args:
           config_line: A line of text in the config.

        Returns:
            The number of leading spaces.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''interface GigabitEthernet1\n description link to ISP'''
            >>> config_line = " description link to ISP"
            >>> indent_level = BaseSpaceConfigParser(config).get_leading_space_count(config_line)
            >>> indent_level
            1
            >>>
        """
        return len(config_line) - len(config_line.lstrip())
def is_banner_end(self, line): BaseSpaceConfigParser
    def is_banner_end(self, line: str) -> bool:
        """Determine if line ends the banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line ends banner, else False.
        """
        if self.banner_end in line:
            return True
        return False
def is_banner_one_line(config_line): CiscoConfigParser
    @staticmethod
    def is_banner_one_line(config_line: str) -> bool:
        """Determine if all banner config is on one line."""
        _, delimeter, banner = config_line.partition("^C")
        # if the banner is the delimeter is a single line empty banner. e.g banner motd ^C^C which ios allows.
        if banner == "^C":
            return True
        # Based on NXOS configs, the banner delimeter is ignored until another char is used
        banner_config_start = banner.lstrip(delimeter)
        if delimeter not in banner_config_start:
            return False
        return True
def is_banner_start(self, line): CiscoConfigParser

CiscoConfigParser

    def is_banner_start(self, line: str) -> bool:
        """Determine if the line starts a banner config."""
        state = super(CiscoConfigParser, self).is_banner_start(line)
        if state:
            self.banner_end = line
        return state

BaseSpaceConfigParser

    def is_banner_start(self, line: str) -> bool:
        """Determine if the line starts a banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line starts banner, else False.
        """
        for banner_start in self.banner_start:
            if not line:
                return False
            if line.lstrip().startswith(banner_start):
                return True
        return False
def is_comment(self, line): BaseSpaceConfigParser
    def is_comment(self, line: str) -> bool:
        """Determine if line is a comment.

        Args:
            line: A config line from the device.

        Returns:
            True if line is a comment, else False.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> BaseSpaceConfigParser("interface Ethernet1/1").is_comment("interface Ethernet1/1")
            False
            >>> BaseSpaceConfigParser("!").is_comment("!")
            True
            >>>
        """
        for comment_char in self.comment_chars:
            if line.lstrip().startswith(comment_char):
                return True
        return False

EOSConfigParser

from netutils.config.parser import EOSConfigParser

Ancestors (MRO)

The Method Resolution Order is described below.

  1. EOSConfigParser
  2. BaseSpaceConfigParser
  3. BaseConfigParser

Descendant Classes

Attributes

Key Value Defined in
banner_end 'EOF' EOSConfigParser
banner_start ['banner', 'vacant-message'] BaseSpaceConfigParser
banner_start ['banner', 'vacant-message'] BaseConfigParser
comment_chars ['!'] BaseSpaceConfigParser
comment_chars ['!'] BaseConfigParser

Methods

def __init__(self, config): BaseSpaceConfigParser

BaseSpaceConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self._indent_level = 0
        super(BaseSpaceConfigParser, self).__init__(config)

BaseConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config: The config text to parse.
        """
        self.config = config
        self._config: t.Optional[str] = None
        self._current_parents: t.Tuple[str, ...] = ()
        self.generator_config = (line for line in self.config_lines_only.splitlines())
        self.config_lines: t.List[ConfigLine] = []
        self.build_config_relationship()
def _build_banner(self, config_line): EOSConfigParser

EOSConfigParser

    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None when banner end is the end of the config text.

        Raises:
            ValueError: When the parser is unable to identify the End of the Banner.
        """
        self._update_config_lines(config_line)
        self._current_parents += (config_line,)
        banner_config = []
        for line in self.generator_config:
            if not self.is_banner_end(line):
                banner_config.append(line)
            else:
                banner_config.append(line)
                line = "\n".join(banner_config)
                self._update_config_lines(line)
                self._current_parents = self._current_parents[:-1]
                try:
                    return next(self.generator_config)
                except StopIteration:
                    return None
        raise ValueError("Unable to parse banner end.")

BaseSpaceConfigParser

    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None

        Raises:
            ValueError: When the parser is unable to identify the end of the Banner.
        """
        self._update_config_lines(config_line)
        self._current_parents += (config_line,)
        banner_config = []
        for line in self.generator_config:
            if not self.is_banner_end(line):
                banner_config.append(line)
            else:
                line = normalise_delimiter_caret_c(self.banner_end, line)
                banner_config.append(line)
                line = "\n".join(banner_config)
                if line.endswith("^C"):
                    banner, end, _ = line.rpartition("^C")
                    line = banner + end
                self._update_config_lines(line)
                self._current_parents = self._current_parents[:-1]
                try:
                    return next(self.generator_config)
                except StopIteration:
                    return None

        raise ValueError("Unable to parse banner end.")
def _build_nested_config(self, line): BaseSpaceConfigParser
    def _build_nested_config(self, line: str) -> t.Optional[str]:
        """Handle building child config sections.

        Args:
            line: A configuration line from the configuration text.

        Returns:
            The next top-level configuration line in the configuration text or None when the last line of configuration
            text is a nested configuration line.

        Raises:
            IndexError: When the number of parents does not match the expected deindent level.
        """
        self._update_config_lines(line)
        for line in self.generator_config:
            if not line[0].isspace():
                self._current_parents = ()
                self.indent_level = 0
                return line

            spaces = self.get_leading_space_count(line)
            if spaces == self.indent_level:
                pass
            elif spaces > self.indent_level:
                previous_config = self.config_lines[-1]
                self._current_parents += (previous_config.config_line,)
            else:
                self._current_parents = self._remove_parents(line, spaces)

            if spaces != self.indent_level:
                self.indent_level = spaces

            if self.is_banner_start(line):
                banner_line = self._build_banner(line)
                if banner_line is None or not banner_line[0].isspace():
                    self._current_parents = ()
                    self.indent_level = 0
                    return banner_line
                line = banner_line

            self._update_config_lines(line)
        return None
def _match_type_check(line, pattern, match_type): BaseSpaceConfigParser
    @staticmethod
    def _match_type_check(line: str, pattern: str, match_type: str) -> bool:
        """Checks pattern for exact match or regex."""
        if match_type == "exact" and line == pattern:
            return True
        if match_type == "startswith" and line.startswith(pattern):
            return True
        if match_type == "endswith" and line.endswith(pattern):
            return True
        if match_type == "regex" and re.match(pattern, line):
            return True
        return False
def _remove_parents(self, line, current_spaces): BaseSpaceConfigParser
    def _remove_parents(self, line: str, current_spaces: int) -> t.Tuple[str, ...]:
        """Remove parents from ``self._curent_parents`` based on indent levels.

        Args:
            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 = 1
        try:
            previous_parent = self._current_parents[-1]
            previous_indent = self.get_leading_space_count(previous_parent)
            while previous_indent > current_spaces:
                deindent_level += 1
                previous_parent = self._current_parents[-deindent_level]
                previous_indent = self.get_leading_space_count(previous_parent)
        except IndexError:
            raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n")
        parents = self._current_parents[:-deindent_level] or (self._current_parents[0],)
        return parents
def _update_config_lines(self, config_line): BaseSpaceConfigParser
    def _update_config_lines(self, config_line: str) -> None:
        """Add a ``ConfigLine`` object to ``self.config_lines``.

        Args:
            config_line: The current config line being evaluated.

        Returns:
            None
        """
        entry = ConfigLine(config_line, self._current_parents)
        self.config_lines.append(entry)
def build_config_relationship(self): BaseSpaceConfigParser

BaseSpaceConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        r"""Parse text tree of config lines and their parents.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
            >>> config = (
            ...     "interface Ethernet1/1\n"
            ...     "  vlan 10\n"
            ...     "  no shutdown\n"
            ...     "interface Ethernet1/2\n"
            ...     "  shutdown\n"
            ... )
            >>> config_tree = BaseSpaceConfigParser(config)
            >>> config_tree.build_config_relationship() == \
            ... [
            ...     ConfigLine(config_line='interface Ethernet1/1', parents=()),
            ...     ConfigLine(config_line='  vlan 10', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='  no shutdown', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='interface Ethernet1/2', parents=(),),
            ...     ConfigLine(config_line='  shutdown', parents=('interface Ethernet1/2',))
            ... ]
            True
        """
        for index, line in enumerate(self.generator_config):
            current_spaces = self.get_leading_space_count(line) if line[0].isspace() else 0
            if index == 0 and line[0].isspace():
                self._current_parents = self._remove_parents(line, current_spaces)
            elif not line[0].isspace():
                self._current_parents = ()
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore
            else:
                previous_config = self.config_lines[-1]
                self._current_parents = (previous_config.config_line,)
                self.indent_level = self.get_leading_space_count(line)
                if not self.is_banner_start(line):
                    line = self._build_nested_config(line)  # type: ignore
                else:
                    line = self._build_banner(line)  # type: ignore
                    if line is not None and line[0].isspace():
                        line = self._build_nested_config(line)  # type: ignore
                    else:
                        self._current_parents = ()

            if line is None:
                break
            elif self.is_banner_start(line):
                line = self._build_banner(line)  # type: ignore
                # line can potentially be another banner start therefore we do a secondary check.
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore

            self._update_config_lines(line)
        return self.config_lines

BaseConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        """Parse text tree of config lines and their parents."""
        raise NotImplementedError()
def find_all_children(self, pattern, match_type=exact): BaseSpaceConfigParser
    def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]:
        """Returns configuration part for a specific pattern not including parents.

        Args:
            pattern: pattern that describes parent.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith")
            >>> print(bgp_conf)
            ['router bgp 45000', '  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if (
                parents
                and self._match_type_check(parents, pattern, match_type)
                or self._match_type_check(cfg_line.config_line, pattern, match_type)
            ):
                config.append(cfg_line.config_line)
        return config
def find_children_w_parents(self, parent_pattern, child_pattern, match_type=exact): BaseSpaceConfigParser
    def find_children_w_parents(
        self, parent_pattern: str, child_pattern: str, match_type: str = "exact"
    ) -> t.List[str]:
        """Returns configuration part for a specific pattern including parents and children.

        Args:
            parent_pattern: pattern that describes parent.
            child_pattern: pattern that describes child.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern="  address-family", match_type="regex")
            >>> print(bgp_conf)
            ['  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        potential_parents = [
            elem.parents[0]
            for elem in self.build_config_relationship()
            if self._match_type_check(elem.config_line, child_pattern, match_type)
        ]
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if parents in potential_parents and self._match_type_check(
                parents,  # type: ignore[arg-type]
                parent_pattern,
                match_type,
            ):
                config.append(cfg_line.config_line)
        return config
def get_leading_space_count(config_line): BaseSpaceConfigParser
    @staticmethod
    def get_leading_space_count(config_line: str) -> int:
        r"""Determine how many spaces the ``config_line`` is indented.

        Args:
           config_line: A line of text in the config.

        Returns:
            The number of leading spaces.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''interface GigabitEthernet1\n description link to ISP'''
            >>> config_line = " description link to ISP"
            >>> indent_level = BaseSpaceConfigParser(config).get_leading_space_count(config_line)
            >>> indent_level
            1
            >>>
        """
        return len(config_line) - len(config_line.lstrip())
def is_banner_end(self, line): BaseSpaceConfigParser
    def is_banner_end(self, line: str) -> bool:
        """Determine if line ends the banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line ends banner, else False.
        """
        if self.banner_end in line:
            return True
        return False
def is_banner_start(self, line): BaseSpaceConfigParser
    def is_banner_start(self, line: str) -> bool:
        """Determine if the line starts a banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line starts banner, else False.
        """
        for banner_start in self.banner_start:
            if not line:
                return False
            if line.lstrip().startswith(banner_start):
                return True
        return False
def is_comment(self, line): BaseSpaceConfigParser
    def is_comment(self, line: str) -> bool:
        """Determine if line is a comment.

        Args:
            line: A config line from the device.

        Returns:
            True if line is a comment, else False.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> BaseSpaceConfigParser("interface Ethernet1/1").is_comment("interface Ethernet1/1")
            False
            >>> BaseSpaceConfigParser("!").is_comment("!")
            True
            >>>
        """
        for comment_char in self.comment_chars:
            if line.lstrip().startswith(comment_char):
                return True
        return False

F5ConfigParser

from netutils.config.parser import F5ConfigParser

Ancestors (MRO)

The Method Resolution Order is described below.

  1. F5ConfigParser
  2. BaseBraceConfigParser
  3. BaseConfigParser

Descendant Classes

Attributes

Key Value Defined in
banner_start ['banner', 'vacant-message'] BaseConfigParser
comment_chars ['!'] BaseConfigParser

Methods

def __init__(self, config): F5ConfigParser

F5ConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config: The config text to parse.
        """
        super().__init__(self._clean_config_f5(config))

BaseConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config: The config text to parse.
        """
        self.config = config
        self._config: t.Optional[str] = None
        self._current_parents: t.Tuple[str, ...] = ()
        self.generator_config = (line for line in self.config_lines_only.splitlines())
        self.config_lines: t.List[ConfigLine] = []
        self.build_config_relationship()
def _build_multiline_config(self, delimiter): BaseBraceConfigParser
    def _build_multiline_config(self, delimiter: str) -> t.Optional[ConfigLine]:
        r"""Build config sections between characters demarcating multiline strings.

        Args:
            delimiter: The text to look for to end multiline config.

        Returns:
            The multiline string text that was added to ``self.config_lines``.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
            >>> config = (
            ...     'sys syslog {\n'
            ...     '    include "\n'
            ...     '        filter f_local0 {\n'
            ...     '        facility(local0) and not match(\"DEBUG");\n'
            ...     '    "\n'
            ...     '}'
            ... )
            >>> parser = BaseBraceConfigParser(config)
            >>> # '    include "' started a multiline config
            >>> parser.config_lines == \
            ... [
            ...     ConfigLine(config_line='sys syslog {', parents=()),
            ...     ConfigLine(config_line='    include "', parents=('sys syslog {',)),
            ...     ConfigLine(config_line='        filter f_local0 {', parents=('sys syslog {',)),
            ...     ConfigLine(config_line='        facility(local0) and not match("DEBUG");', parents=('sys syslog {', '        filter f_local0 {')),
            ...     ConfigLine(config_line='    "', parents=('sys syslog {', '        filter f_local0 {')),
            ...     ConfigLine(config_line='}', parents=('sys syslog {', '        filter f_local0 {'))
            ... ]
            True
        """
        multiline_config = []
        for line in self.generator_config:
            multiline_config.append(line)
            if line.lstrip() == delimiter:
                multiline_entry = ConfigLine("\n".join(multiline_config), self._current_parents)
                self.config_lines.append(multiline_entry)
                self._current_parents = self._current_parents[:-1]
                return multiline_entry
        return None
def _build_multiline_single_configuration_line(self, delimiter, prev_line): F5ConfigParser
    def _build_multiline_single_configuration_line(self, delimiter: str, prev_line: str) -> t.Optional[ConfigLine]:
        r"""Concatenate Multiline strings between delimiter when newlines causes string to traverse multiple lines.

        Args:
            delimiter: The text to look for to end multiline config.
            prev_line: The text from the previously analyzed line.

        Returns:
            The multiline string text that was added to ``self.config_lines``.

        Examples:
            >>> from netutils.config.parser import F5ConfigParser, ConfigLine
            >>> config = '''apm resource webtop-link aShare {
            ...     application-uri http://funshare.example.com
            ...     customization-group a_customization_group
            ... }
            ... apm sso form-based portal_ext_sso_form_based {
            ...     form-action /Citrix/Example/ExplicitAuth/LoginAttempt
            ...     form-field "LoginBtn Log+On
            ... StateContext "
            ...     form-password password
            ...     form-username username
            ...     passthru true
            ...     start-uri /Citrix/Example/ExplicitAuth/Login*
            ...     success-match-type cookie
            ...     success-match-value CtxsAuthId
            ... }
            ... '''
            >>>
            >>>
            >>> config_tree = F5ConfigParser(str(config))
            >>> print(config_tree.build_config_relationship())
            [ConfigLine(config_line='apm resource webtop-link aShare {', parents=()), ConfigLine(config_line='    application-uri http://funshare.example.com', parents=('apm resource webtop-link aShare {',)), ConfigLine(config_line='    customization-group a_customization_group', parents=('apm resource webtop-link aShare {',)), ConfigLine(config_line='}', parents=('apm resource webtop-link aShare {',)), ConfigLine(config_line='apm sso form-based portal_ext_sso_form_based {', parents=()), ConfigLine(config_line='    form-action /Citrix/Example/ExplicitAuth/LoginAttempt', parents=('apm sso form-based portal_ext_sso_form_based {',)), ConfigLine(config_line='    form-field "LoginBtn Log+On\nStateContext "', parents=('apm sso form-based portal_ext_sso_form_based {',)), ConfigLine(config_line='    form-password password', parents=()), ConfigLine(config_line='    form-username username', parents=()), ConfigLine(config_line='    passthru true', parents=()), ConfigLine(config_line='    start-uri /Citrix/Example/ExplicitAuth/Login*', parents=()), ConfigLine(config_line='    success-match-type cookie', parents=()), ConfigLine(config_line='    success-match-value CtxsAuthId', parents=()), ConfigLine(config_line='}', parents=())]
        """
        multiline_config = [prev_line]
        for line in self.generator_config:
            multiline_config.append(line)
            if line.endswith(delimiter):
                multiline_entry = ConfigLine("\n".join(multiline_config), self._current_parents)
                self.config_lines[-1] = multiline_entry
                self._current_parents = self._current_parents[:-1]
                return multiline_entry
        return None
def _clean_config_f5(self, config_text): F5ConfigParser
    def _clean_config_f5(self, config_text: str) -> str:
        """Removes all configuration items with 'ltm rule'.

        iRules are essentially impossible to parse with the lack of uniformity,
        therefore, this method ensures they are not included in ``self.config``.

        Args:
            config_text: The entire config as a string.

        Returns:
            The sanitized config with all iRules (ltm rule) stanzas removed.
        """
        config_split = config_text.split("ltm rule")
        if len(config_split) > 1:
            start_config = config_split[0]
            end_config = config_split[-1]
            _, ltm, clean_config = end_config.partition("ltm")
            final_config = start_config + ltm + clean_config
        else:
            final_config = config_text
        return final_config
def build_config_relationship(self): F5ConfigParser

F5ConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        r"""Parse text tree of config lines and their parents.

        Examples:
            >>> from netutils.config.parser import F5ConfigParser, ConfigLine
            >>> config = '''apm resource webtop-link aShare {
            ...     application-uri http://funshare.example.com
            ...     customization-group a_customization_group
            ... }
            ... apm sso form-based portal_ext_sso_form_based {
            ...     form-action /Citrix/Example/ExplicitAuth/LoginAttempt
            ...     form-field "LoginBtn Log+On
            ... StateContext "
            ...     form-password password
            ...     form-username username
            ...     passthru true
            ...     start-uri /Citrix/Example/ExplicitAuth/Login*
            ...     success-match-type cookie
            ...     success-match-value CtxsAuthId
            ... }
            ... '''
            >>>
            >>> config_tree = F5ConfigParser(config)
            >>> print(config_tree.build_config_relationship())
            [ConfigLine(config_line='apm resource webtop-link aShare {', parents=()), ConfigLine(config_line='    application-uri http://funshare.example.com', parents=('apm resource webtop-link aShare {',)), ConfigLine(config_line='    customization-group a_customization_group', parents=('apm resource webtop-link aShare {',)), ConfigLine(config_line='}', parents=('apm resource webtop-link aShare {',)), ConfigLine(config_line='apm sso form-based portal_ext_sso_form_based {', parents=()), ConfigLine(config_line='    form-action /Citrix/Example/ExplicitAuth/LoginAttempt', parents=('apm sso form-based portal_ext_sso_form_based {',)), ConfigLine(config_line='    form-field "LoginBtn Log+On\nStateContext "', parents=('apm sso form-based portal_ext_sso_form_based {',)), ConfigLine(config_line='    form-password password', parents=()), ConfigLine(config_line='    form-username username', parents=()), ConfigLine(config_line='    passthru true', parents=()), ConfigLine(config_line='    start-uri /Citrix/Example/ExplicitAuth/Login*', parents=()), ConfigLine(config_line='    success-match-type cookie', parents=()), ConfigLine(config_line='    success-match-value CtxsAuthId', parents=()), ConfigLine(config_line='}', parents=())]
        """
        for line in self.generator_config:
            self.config_lines.append(ConfigLine(line, self._current_parents))
            line_end = line[-1]
            if line.endswith("{"):
                self._current_parents += (line,)
            elif line.lstrip() == "}":
                self._current_parents = self._current_parents[:-1]
            elif any(
                delimiters in self.multiline_delimiters and line.count(delimiters) == 1
                for delimiters in self.multiline_delimiters
            ):
                for delimiter in self.multiline_delimiters:
                    if line.count(delimiter) == 1:
                        self._build_multiline_single_configuration_line(delimiter, line)
            elif line_end in self.multiline_delimiters and line.count(line_end) == 1:
                self._current_parents += (line,)
                self._build_multiline_config(line_end)

        return self.config_lines

BaseBraceConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        r"""Parse text tree of config lines and their parents.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
            >>> config = '''auth ldap system-auth {
            ...         port ldaps
            ...         servers { ams-lda01.ntc.com }
            ...     }
            ...     auth partition Common {
            ...         description "Repository for system objects and shared objects."
            ...     }
            ...     auth password-policy { }'''
            >>> config_tree = BaseBraceConfigParser(config)
            >>> config_tree.build_config_relationship() == \
            ... [
            ...     ConfigLine(config_line='auth ldap system-auth {', parents=()),
            ...     ConfigLine(config_line='        port ldaps', parents=('auth ldap system-auth {',)),
            ...     ConfigLine(config_line='        servers { ams-lda01.ntc.com }', parents=('auth ldap system-auth {',)),
            ...     ConfigLine(config_line='    }', parents=('auth ldap system-auth {',)),
            ...     ConfigLine(config_line='    auth partition Common {', parents=()),
            ...     ConfigLine(config_line='        description "Repository for system objects and shared objects."', parents=('    auth partition Common {',)), ConfigLine(config_line='    }', parents=('    auth partition Common {',)),
            ...     ConfigLine(config_line='    auth password-policy { }', parents=())
            ... ]
            True
        """
        for line in self.generator_config:
            self.config_lines.append(ConfigLine(line, self._current_parents))
            line_end = line[-1]
            if line.endswith("{"):
                self._current_parents += (line,)
            elif line.lstrip() == "}":
                self._current_parents = self._current_parents[:-1]
            elif line_end in self.multiline_delimiters and line.count(line_end) == 1:
                self._current_parents += (line,)
                self._build_multiline_config(line_end)

        return self.config_lines

BaseConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        """Parse text tree of config lines and their parents."""
        raise NotImplementedError()

FastironConfigParser

from netutils.config.parser import FastironConfigParser

Ancestors (MRO)

The Method Resolution Order is described below.

  1. FastironConfigParser
  2. CiscoConfigParser
  3. BaseSpaceConfigParser
  4. BaseConfigParser

Descendant Classes

Attributes

Key Value Defined in
banner_start ['banner', 'vacant-message'] BaseSpaceConfigParser
banner_start ['banner', 'vacant-message'] BaseConfigParser
comment_chars ['!'] BaseSpaceConfigParser
comment_chars ['!'] BaseConfigParser
regex_banner re.compile('^banner(\\smotd)?\\s+(?P<banner_delimiter>\\S)') FastironConfigParser
regex_banner re.compile('^(banner\\s+\\S+|\\s*vacant-message)\\s+(?P<banner_delimiter>\\^C|.)') CiscoConfigParser

Methods

def __init__(self, config): FastironConfigParser

FastironConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        super(FastironConfigParser, self).__init__(config)

CiscoConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self._banner_end: t.Optional[str] = None
        super(CiscoConfigParser, self).__init__(config)

BaseSpaceConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self._indent_level = 0
        super(BaseSpaceConfigParser, self).__init__(config)

BaseConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config: The config text to parse.
        """
        self.config = config
        self._config: t.Optional[str] = None
        self._current_parents: t.Tuple[str, ...] = ()
        self.generator_config = (line for line in self.config_lines_only.splitlines())
        self.config_lines: t.List[ConfigLine] = []
        self.build_config_relationship()
def _build_banner(self, config_line): FastironConfigParser

FastironConfigParser

    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None

        Raises:
            ValueError: When the parser is unable to identify the end of the Banner.
        """
        self._update_config_lines(config_line)
        self._current_parents += (config_line,)
        banner_config = []
        for line in self.generator_config:
            if not self.is_banner_end(line):
                banner_config.append(line)
            else:
                banner_config.append(line)
                line = "\n".join(banner_config)
                if line.endswith(self.banner_end):
                    banner, end, _ = line.rpartition(self.banner_end)
                    line = banner + end
                self._update_config_lines(line)
                self._current_parents = self._current_parents[:-1]
                try:
                    return next(self.generator_config)
                except StopIteration:
                    return None
        raise ValueError("Unable to parse banner end.")

CiscoConfigParser

    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None when banner end is the end of the config text.

        Raises:
            ValueError: When the parser is unable to identify the End of the Banner.
        """
        if self.is_banner_one_line(config_line):
            self._update_config_lines(config_line)
            try:
                return next(self.generator_config)
            except StopIteration:
                return None
        return super(CiscoConfigParser, self)._build_banner(config_line)

BaseSpaceConfigParser

    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None

        Raises:
            ValueError: When the parser is unable to identify the end of the Banner.
        """
        self._update_config_lines(config_line)
        self._current_parents += (config_line,)
        banner_config = []
        for line in self.generator_config:
            if not self.is_banner_end(line):
                banner_config.append(line)
            else:
                line = normalise_delimiter_caret_c(self.banner_end, line)
                banner_config.append(line)
                line = "\n".join(banner_config)
                if line.endswith("^C"):
                    banner, end, _ = line.rpartition("^C")
                    line = banner + end
                self._update_config_lines(line)
                self._current_parents = self._current_parents[:-1]
                try:
                    return next(self.generator_config)
                except StopIteration:
                    return None

        raise ValueError("Unable to parse banner end.")
def _build_nested_config(self, line): BaseSpaceConfigParser
    def _build_nested_config(self, line: str) -> t.Optional[str]:
        """Handle building child config sections.

        Args:
            line: A configuration line from the configuration text.

        Returns:
            The next top-level configuration line in the configuration text or None when the last line of configuration
            text is a nested configuration line.

        Raises:
            IndexError: When the number of parents does not match the expected deindent level.
        """
        self._update_config_lines(line)
        for line in self.generator_config:
            if not line[0].isspace():
                self._current_parents = ()
                self.indent_level = 0
                return line

            spaces = self.get_leading_space_count(line)
            if spaces == self.indent_level:
                pass
            elif spaces > self.indent_level:
                previous_config = self.config_lines[-1]
                self._current_parents += (previous_config.config_line,)
            else:
                self._current_parents = self._remove_parents(line, spaces)

            if spaces != self.indent_level:
                self.indent_level = spaces

            if self.is_banner_start(line):
                banner_line = self._build_banner(line)
                if banner_line is None or not banner_line[0].isspace():
                    self._current_parents = ()
                    self.indent_level = 0
                    return banner_line
                line = banner_line

            self._update_config_lines(line)
        return None
def _match_type_check(line, pattern, match_type): BaseSpaceConfigParser
    @staticmethod
    def _match_type_check(line: str, pattern: str, match_type: str) -> bool:
        """Checks pattern for exact match or regex."""
        if match_type == "exact" and line == pattern:
            return True
        if match_type == "startswith" and line.startswith(pattern):
            return True
        if match_type == "endswith" and line.endswith(pattern):
            return True
        if match_type == "regex" and re.match(pattern, line):
            return True
        return False
def _remove_parents(self, line, current_spaces): BaseSpaceConfigParser
    def _remove_parents(self, line: str, current_spaces: int) -> t.Tuple[str, ...]:
        """Remove parents from ``self._curent_parents`` based on indent levels.

        Args:
            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 = 1
        try:
            previous_parent = self._current_parents[-1]
            previous_indent = self.get_leading_space_count(previous_parent)
            while previous_indent > current_spaces:
                deindent_level += 1
                previous_parent = self._current_parents[-deindent_level]
                previous_indent = self.get_leading_space_count(previous_parent)
        except IndexError:
            raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n")
        parents = self._current_parents[:-deindent_level] or (self._current_parents[0],)
        return parents
def _update_config_lines(self, config_line): BaseSpaceConfigParser
    def _update_config_lines(self, config_line: str) -> None:
        """Add a ``ConfigLine`` object to ``self.config_lines``.

        Args:
            config_line: The current config line being evaluated.

        Returns:
            None
        """
        entry = ConfigLine(config_line, self._current_parents)
        self.config_lines.append(entry)
def build_config_relationship(self): BaseSpaceConfigParser

BaseSpaceConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        r"""Parse text tree of config lines and their parents.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
            >>> config = (
            ...     "interface Ethernet1/1\n"
            ...     "  vlan 10\n"
            ...     "  no shutdown\n"
            ...     "interface Ethernet1/2\n"
            ...     "  shutdown\n"
            ... )
            >>> config_tree = BaseSpaceConfigParser(config)
            >>> config_tree.build_config_relationship() == \
            ... [
            ...     ConfigLine(config_line='interface Ethernet1/1', parents=()),
            ...     ConfigLine(config_line='  vlan 10', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='  no shutdown', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='interface Ethernet1/2', parents=(),),
            ...     ConfigLine(config_line='  shutdown', parents=('interface Ethernet1/2',))
            ... ]
            True
        """
        for index, line in enumerate(self.generator_config):
            current_spaces = self.get_leading_space_count(line) if line[0].isspace() else 0
            if index == 0 and line[0].isspace():
                self._current_parents = self._remove_parents(line, current_spaces)
            elif not line[0].isspace():
                self._current_parents = ()
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore
            else:
                previous_config = self.config_lines[-1]
                self._current_parents = (previous_config.config_line,)
                self.indent_level = self.get_leading_space_count(line)
                if not self.is_banner_start(line):
                    line = self._build_nested_config(line)  # type: ignore
                else:
                    line = self._build_banner(line)  # type: ignore
                    if line is not None and line[0].isspace():
                        line = self._build_nested_config(line)  # type: ignore
                    else:
                        self._current_parents = ()

            if line is None:
                break
            elif self.is_banner_start(line):
                line = self._build_banner(line)  # type: ignore
                # line can potentially be another banner start therefore we do a secondary check.
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore

            self._update_config_lines(line)
        return self.config_lines

BaseConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        """Parse text tree of config lines and their parents."""
        raise NotImplementedError()
def find_all_children(self, pattern, match_type=exact): BaseSpaceConfigParser
    def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]:
        """Returns configuration part for a specific pattern not including parents.

        Args:
            pattern: pattern that describes parent.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith")
            >>> print(bgp_conf)
            ['router bgp 45000', '  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if (
                parents
                and self._match_type_check(parents, pattern, match_type)
                or self._match_type_check(cfg_line.config_line, pattern, match_type)
            ):
                config.append(cfg_line.config_line)
        return config
def find_children_w_parents(self, parent_pattern, child_pattern, match_type=exact): BaseSpaceConfigParser
    def find_children_w_parents(
        self, parent_pattern: str, child_pattern: str, match_type: str = "exact"
    ) -> t.List[str]:
        """Returns configuration part for a specific pattern including parents and children.

        Args:
            parent_pattern: pattern that describes parent.
            child_pattern: pattern that describes child.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern="  address-family", match_type="regex")
            >>> print(bgp_conf)
            ['  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        potential_parents = [
            elem.parents[0]
            for elem in self.build_config_relationship()
            if self._match_type_check(elem.config_line, child_pattern, match_type)
        ]
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if parents in potential_parents and self._match_type_check(
                parents,  # type: ignore[arg-type]
                parent_pattern,
                match_type,
            ):
                config.append(cfg_line.config_line)
        return config
def get_leading_space_count(config_line): BaseSpaceConfigParser
    @staticmethod
    def get_leading_space_count(config_line: str) -> int:
        r"""Determine how many spaces the ``config_line`` is indented.

        Args:
           config_line: A line of text in the config.

        Returns:
            The number of leading spaces.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''interface GigabitEthernet1\n description link to ISP'''
            >>> config_line = " description link to ISP"
            >>> indent_level = BaseSpaceConfigParser(config).get_leading_space_count(config_line)
            >>> indent_level
            1
            >>>
        """
        return len(config_line) - len(config_line.lstrip())
def is_banner_end(self, line): BaseSpaceConfigParser
    def is_banner_end(self, line: str) -> bool:
        """Determine if line ends the banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line ends banner, else False.
        """
        if self.banner_end in line:
            return True
        return False
def is_banner_one_line(config_line): CiscoConfigParser
    @staticmethod
    def is_banner_one_line(config_line: str) -> bool:
        """Determine if all banner config is on one line."""
        _, delimeter, banner = config_line.partition("^C")
        # if the banner is the delimeter is a single line empty banner. e.g banner motd ^C^C which ios allows.
        if banner == "^C":
            return True
        # Based on NXOS configs, the banner delimeter is ignored until another char is used
        banner_config_start = banner.lstrip(delimeter)
        if delimeter not in banner_config_start:
            return False
        return True
def is_banner_start(self, line): CiscoConfigParser

CiscoConfigParser

    def is_banner_start(self, line: str) -> bool:
        """Determine if the line starts a banner config."""
        state = super(CiscoConfigParser, self).is_banner_start(line)
        if state:
            self.banner_end = line
        return state

BaseSpaceConfigParser

    def is_banner_start(self, line: str) -> bool:
        """Determine if the line starts a banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line starts banner, else False.
        """
        for banner_start in self.banner_start:
            if not line:
                return False
            if line.lstrip().startswith(banner_start):
                return True
        return False
def is_comment(self, line): BaseSpaceConfigParser
    def is_comment(self, line: str) -> bool:
        """Determine if line is a comment.

        Args:
            line: A config line from the device.

        Returns:
            True if line is a comment, else False.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> BaseSpaceConfigParser("interface Ethernet1/1").is_comment("interface Ethernet1/1")
            False
            >>> BaseSpaceConfigParser("!").is_comment("!")
            True
            >>>
        """
        for comment_char in self.comment_chars:
            if line.lstrip().startswith(comment_char):
                return True
        return False

FortinetConfigParser

from netutils.config.parser import FortinetConfigParser

Ancestors (MRO)

The Method Resolution Order is described below.

  1. FortinetConfigParser
  2. BaseSpaceConfigParser
  3. BaseConfigParser

Descendant Classes

Attributes

Key Value Defined in
banner_start ['banner', 'vacant-message'] BaseSpaceConfigParser
banner_start ['banner', 'vacant-message'] BaseConfigParser
comment_chars ['!'] BaseSpaceConfigParser
comment_chars ['!'] BaseConfigParser

Methods

def __init__(self, config): FortinetConfigParser

FortinetConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self.uncommon_data = self._get_uncommon_lines(config)
        super(FortinetConfigParser, self).__init__(config)

BaseSpaceConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self._indent_level = 0
        super(BaseSpaceConfigParser, self).__init__(config)

BaseConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config: The config text to parse.
        """
        self.config = config
        self._config: t.Optional[str] = None
        self._current_parents: t.Tuple[str, ...] = ()
        self.generator_config = (line for line in self.config_lines_only.splitlines())
        self.config_lines: t.List[ConfigLine] = []
        self.build_config_relationship()
def _build_banner(self, config_line): BaseSpaceConfigParser
    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None

        Raises:
            ValueError: When the parser is unable to identify the end of the Banner.
        """
        self._update_config_lines(config_line)
        self._current_parents += (config_line,)
        banner_config = []
        for line in self.generator_config:
            if not self.is_banner_end(line):
                banner_config.append(line)
            else:
                line = normalise_delimiter_caret_c(self.banner_end, line)
                banner_config.append(line)
                line = "\n".join(banner_config)
                if line.endswith("^C"):
                    banner, end, _ = line.rpartition("^C")
                    line = banner + end
                self._update_config_lines(line)
                self._current_parents = self._current_parents[:-1]
                try:
                    return next(self.generator_config)
                except StopIteration:
                    return None

        raise ValueError("Unable to parse banner end.")
def _build_nested_config(self, line): FortinetConfigParser

FortinetConfigParser

    def _build_nested_config(self, line: str) -> t.Optional[str]:
        """Handle building child config sections.

        Args:
            line: A configuration line from the configuration text.

        Returns:
            The next top-level configuration line in the configuration text or None when the last line of configuration
            text is a nested configuration line.

        Raises:
            IndexError: When the number of parents does not match the expected deindent level.
        """
        if "[" in line:
            updated_line = self.uncommon_data.get(line.split('"')[1], None)
            if not updated_line:
                raise ValueError("Input line is malformed.")
            line = updated_line
        self._update_config_lines(line)
        for line in self.generator_config:
            if not line[0].isspace():
                self._current_parents = ()
                self.indent_level = 0
                return line

            spaces = self.get_leading_space_count(line)
            if spaces == self.indent_level:
                pass
            elif spaces > self.indent_level:
                previous_config = self.config_lines[-1]
                self._current_parents += (previous_config.config_line,)
            else:
                self._current_parents = self._remove_parents(line, spaces)

            if spaces != self.indent_level:
                self.indent_level = spaces

            self._update_config_lines(line)
        return None

BaseSpaceConfigParser

    def _build_nested_config(self, line: str) -> t.Optional[str]:
        """Handle building child config sections.

        Args:
            line: A configuration line from the configuration text.

        Returns:
            The next top-level configuration line in the configuration text or None when the last line of configuration
            text is a nested configuration line.

        Raises:
            IndexError: When the number of parents does not match the expected deindent level.
        """
        self._update_config_lines(line)
        for line in self.generator_config:
            if not line[0].isspace():
                self._current_parents = ()
                self.indent_level = 0
                return line

            spaces = self.get_leading_space_count(line)
            if spaces == self.indent_level:
                pass
            elif spaces > self.indent_level:
                previous_config = self.config_lines[-1]
                self._current_parents += (previous_config.config_line,)
            else:
                self._current_parents = self._remove_parents(line, spaces)

            if spaces != self.indent_level:
                self.indent_level = spaces

            if self.is_banner_start(line):
                banner_line = self._build_banner(line)
                if banner_line is None or not banner_line[0].isspace():
                    self._current_parents = ()
                    self.indent_level = 0
                    return banner_line
                line = banner_line

            self._update_config_lines(line)
        return None
def _get_uncommon_lines(self, config): FortinetConfigParser
    def _get_uncommon_lines(self, config: str) -> t.Dict[str, str]:
        """Regex to find replacemsg lines which can contain html/css data.

        Args:
            config: Original config before parsing.

        Returns:
            dict: dictionary with replace message name as key, html/css data as value.
        """
        pattern = r"(config system replacemsg.*\n)(\s{4}set\sbuffer\s\"[\S\s]*?\"\n)"
        regex_result = re.findall(pattern, config)
        result = {}
        for group_match in regex_result:
            result.update({group_match[0].split('"')[1]: group_match[1]})
        return result
def _match_type_check(line, pattern, match_type): BaseSpaceConfigParser
    @staticmethod
    def _match_type_check(line: str, pattern: str, match_type: str) -> bool:
        """Checks pattern for exact match or regex."""
        if match_type == "exact" and line == pattern:
            return True
        if match_type == "startswith" and line.startswith(pattern):
            return True
        if match_type == "endswith" and line.endswith(pattern):
            return True
        if match_type == "regex" and re.match(pattern, line):
            return True
        return False
def _parse_out_offending(self, config): FortinetConfigParser
    def _parse_out_offending(self, config: str) -> str:
        """Preprocess out strings that offend the normal spaced configuration syntax.

        Args:
            config (str): full config as a string.
        """
        # This will grab everything between quotes after the 'set buffer' sub-command.
        # Its explicitly looking for "\n to end the captured data.  This is to support html
        # data that is supported in Fortinet config with double quotes within the html.
        pattern = r"(config system replacemsg.*(\".*\")\n)(\s{4}set\sbuffer\s\"[\S\s]*?\"\n)"
        return re.sub(pattern, r"\1    [\2]\n", config)
def _remove_parents(self, line, current_spaces): BaseSpaceConfigParser
    def _remove_parents(self, line: str, current_spaces: int) -> t.Tuple[str, ...]:
        """Remove parents from ``self._curent_parents`` based on indent levels.

        Args:
            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 = 1
        try:
            previous_parent = self._current_parents[-1]
            previous_indent = self.get_leading_space_count(previous_parent)
            while previous_indent > current_spaces:
                deindent_level += 1
                previous_parent = self._current_parents[-deindent_level]
                previous_indent = self.get_leading_space_count(previous_parent)
        except IndexError:
            raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n")
        parents = self._current_parents[:-deindent_level] or (self._current_parents[0],)
        return parents
def _update_config_lines(self, config_line): BaseSpaceConfigParser
    def _update_config_lines(self, config_line: str) -> None:
        """Add a ``ConfigLine`` object to ``self.config_lines``.

        Args:
            config_line: The current config line being evaluated.

        Returns:
            None
        """
        entry = ConfigLine(config_line, self._current_parents)
        self.config_lines.append(entry)
def build_config_relationship(self): BaseSpaceConfigParser

BaseSpaceConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        r"""Parse text tree of config lines and their parents.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
            >>> config = (
            ...     "interface Ethernet1/1\n"
            ...     "  vlan 10\n"
            ...     "  no shutdown\n"
            ...     "interface Ethernet1/2\n"
            ...     "  shutdown\n"
            ... )
            >>> config_tree = BaseSpaceConfigParser(config)
            >>> config_tree.build_config_relationship() == \
            ... [
            ...     ConfigLine(config_line='interface Ethernet1/1', parents=()),
            ...     ConfigLine(config_line='  vlan 10', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='  no shutdown', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='interface Ethernet1/2', parents=(),),
            ...     ConfigLine(config_line='  shutdown', parents=('interface Ethernet1/2',))
            ... ]
            True
        """
        for index, line in enumerate(self.generator_config):
            current_spaces = self.get_leading_space_count(line) if line[0].isspace() else 0
            if index == 0 and line[0].isspace():
                self._current_parents = self._remove_parents(line, current_spaces)
            elif not line[0].isspace():
                self._current_parents = ()
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore
            else:
                previous_config = self.config_lines[-1]
                self._current_parents = (previous_config.config_line,)
                self.indent_level = self.get_leading_space_count(line)
                if not self.is_banner_start(line):
                    line = self._build_nested_config(line)  # type: ignore
                else:
                    line = self._build_banner(line)  # type: ignore
                    if line is not None and line[0].isspace():
                        line = self._build_nested_config(line)  # type: ignore
                    else:
                        self._current_parents = ()

            if line is None:
                break
            elif self.is_banner_start(line):
                line = self._build_banner(line)  # type: ignore
                # line can potentially be another banner start therefore we do a secondary check.
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore

            self._update_config_lines(line)
        return self.config_lines

BaseConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        """Parse text tree of config lines and their parents."""
        raise NotImplementedError()
def find_all_children(self, pattern, match_type=exact): BaseSpaceConfigParser
    def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]:
        """Returns configuration part for a specific pattern not including parents.

        Args:
            pattern: pattern that describes parent.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith")
            >>> print(bgp_conf)
            ['router bgp 45000', '  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if (
                parents
                and self._match_type_check(parents, pattern, match_type)
                or self._match_type_check(cfg_line.config_line, pattern, match_type)
            ):
                config.append(cfg_line.config_line)
        return config
def find_children_w_parents(self, parent_pattern, child_pattern, match_type=exact): BaseSpaceConfigParser
    def find_children_w_parents(
        self, parent_pattern: str, child_pattern: str, match_type: str = "exact"
    ) -> t.List[str]:
        """Returns configuration part for a specific pattern including parents and children.

        Args:
            parent_pattern: pattern that describes parent.
            child_pattern: pattern that describes child.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern="  address-family", match_type="regex")
            >>> print(bgp_conf)
            ['  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        potential_parents = [
            elem.parents[0]
            for elem in self.build_config_relationship()
            if self._match_type_check(elem.config_line, child_pattern, match_type)
        ]
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if parents in potential_parents and self._match_type_check(
                parents,  # type: ignore[arg-type]
                parent_pattern,
                match_type,
            ):
                config.append(cfg_line.config_line)
        return config
def get_leading_space_count(config_line): BaseSpaceConfigParser
    @staticmethod
    def get_leading_space_count(config_line: str) -> int:
        r"""Determine how many spaces the ``config_line`` is indented.

        Args:
           config_line: A line of text in the config.

        Returns:
            The number of leading spaces.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''interface GigabitEthernet1\n description link to ISP'''
            >>> config_line = " description link to ISP"
            >>> indent_level = BaseSpaceConfigParser(config).get_leading_space_count(config_line)
            >>> indent_level
            1
            >>>
        """
        return len(config_line) - len(config_line.lstrip())
def is_banner_end(self, line): BaseSpaceConfigParser
    def is_banner_end(self, line: str) -> bool:
        """Determine if line ends the banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line ends banner, else False.
        """
        if self.banner_end in line:
            return True
        return False
def is_banner_start(self, line): BaseSpaceConfigParser
    def is_banner_start(self, line: str) -> bool:
        """Determine if the line starts a banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line starts banner, else False.
        """
        for banner_start in self.banner_start:
            if not line:
                return False
            if line.lstrip().startswith(banner_start):
                return True
        return False
def is_comment(self, line): BaseSpaceConfigParser
    def is_comment(self, line: str) -> bool:
        """Determine if line is a comment.

        Args:
            line: A config line from the device.

        Returns:
            True if line is a comment, else False.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> BaseSpaceConfigParser("interface Ethernet1/1").is_comment("interface Ethernet1/1")
            False
            >>> BaseSpaceConfigParser("!").is_comment("!")
            True
            >>>
        """
        for comment_char in self.comment_chars:
            if line.lstrip().startswith(comment_char):
                return True
        return False
def is_end_next(self, line): FortinetConfigParser
    def is_end_next(self, line: str) -> bool:
        """Determine if line has 'end' or 'next' in it.

        Args:
            line: A config line from the device.

        Returns:
            True if line has 'end' or 'next', else False.

        Examples:
            >>> from netutils.config.parser import FortinetConfigParser
            >>> FortinetConfigParser("config system virtual-switch").is_end_next("config system virtual-switch")
            False
            >>> FortinetConfigParser("end").is_end_next("end")
            True
            >>>
        """
        for end_next in ["end", "next"]:
            if line.lstrip() == end_next:
                return True
        return False

HPComwareConfigParser

from netutils.config.parser import HPComwareConfigParser

Ancestors (MRO)

The Method Resolution Order is described below.

  1. HPComwareConfigParser
  2. _HPEConfigParser
  3. BaseSpaceConfigParser
  4. BaseConfigParser

Descendant Classes

Attributes

Key Value Defined in
banner_start ['banner', 'vacant-message'] BaseSpaceConfigParser
banner_start ['banner', 'vacant-message'] BaseConfigParser
comment_chars ['!'] BaseSpaceConfigParser
comment_chars ['!'] BaseConfigParser
regex_banner re.compile('^\\s*header\\s(\\w+)\\s+(?P<banner_delimiter>\\^C|\\S?)') _HPEConfigParser

Methods

def __init__(self, config): _HPEConfigParser

_HPEConfigParser

    def __init__(self, config: str):
        """Initialize the _HPEConfigParser object."""
        self.delimiter = ""
        self._banner_end: t.Optional[str] = None
        super(_HPEConfigParser, self).__init__(config)

BaseSpaceConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self._indent_level = 0
        super(BaseSpaceConfigParser, self).__init__(config)

BaseConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config: The config text to parse.
        """
        self.config = config
        self._config: t.Optional[str] = None
        self._current_parents: t.Tuple[str, ...] = ()
        self.generator_config = (line for line in self.config_lines_only.splitlines())
        self.config_lines: t.List[ConfigLine] = []
        self.build_config_relationship()
def _build_banner(self, config_line): _HPEConfigParser

_HPEConfigParser

    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.
        """
        if self.is_banner_one_line(config_line):
            self._update_config_lines(config_line)
            try:
                return next(self.generator_config)
            except StopIteration:
                return None
        self._update_config_lines(config_line)
        self._current_parents += (config_line,)
        banner_config = []
        for line in self.generator_config:
            if not self.is_banner_end(line):
                banner_config.append(line)
            else:
                banner_config.append(line)
                line = "\n".join(banner_config)
                if line.endswith(self.delimiter):
                    banner, end, _ = line.rpartition(self.delimiter)
                    line = banner + end
                self._update_config_lines(line)
                self._current_parents = self._current_parents[:-1]
                try:
                    return next(self.generator_config)
                except StopIteration:
                    return None
        raise ValueError("Unable to parse banner end.")

BaseSpaceConfigParser

    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None

        Raises:
            ValueError: When the parser is unable to identify the end of the Banner.
        """
        self._update_config_lines(config_line)
        self._current_parents += (config_line,)
        banner_config = []
        for line in self.generator_config:
            if not self.is_banner_end(line):
                banner_config.append(line)
            else:
                line = normalise_delimiter_caret_c(self.banner_end, line)
                banner_config.append(line)
                line = "\n".join(banner_config)
                if line.endswith("^C"):
                    banner, end, _ = line.rpartition("^C")
                    line = banner + end
                self._update_config_lines(line)
                self._current_parents = self._current_parents[:-1]
                try:
                    return next(self.generator_config)
                except StopIteration:
                    return None

        raise ValueError("Unable to parse banner end.")
def _build_nested_config(self, line): BaseSpaceConfigParser
    def _build_nested_config(self, line: str) -> t.Optional[str]:
        """Handle building child config sections.

        Args:
            line: A configuration line from the configuration text.

        Returns:
            The next top-level configuration line in the configuration text or None when the last line of configuration
            text is a nested configuration line.

        Raises:
            IndexError: When the number of parents does not match the expected deindent level.
        """
        self._update_config_lines(line)
        for line in self.generator_config:
            if not line[0].isspace():
                self._current_parents = ()
                self.indent_level = 0
                return line

            spaces = self.get_leading_space_count(line)
            if spaces == self.indent_level:
                pass
            elif spaces > self.indent_level:
                previous_config = self.config_lines[-1]
                self._current_parents += (previous_config.config_line,)
            else:
                self._current_parents = self._remove_parents(line, spaces)

            if spaces != self.indent_level:
                self.indent_level = spaces

            if self.is_banner_start(line):
                banner_line = self._build_banner(line)
                if banner_line is None or not banner_line[0].isspace():
                    self._current_parents = ()
                    self.indent_level = 0
                    return banner_line
                line = banner_line

            self._update_config_lines(line)
        return None
def _match_type_check(line, pattern, match_type): BaseSpaceConfigParser
    @staticmethod
    def _match_type_check(line: str, pattern: str, match_type: str) -> bool:
        """Checks pattern for exact match or regex."""
        if match_type == "exact" and line == pattern:
            return True
        if match_type == "startswith" and line.startswith(pattern):
            return True
        if match_type == "endswith" and line.endswith(pattern):
            return True
        if match_type == "regex" and re.match(pattern, line):
            return True
        return False
def _remove_parents(self, line, current_spaces): BaseSpaceConfigParser
    def _remove_parents(self, line: str, current_spaces: int) -> t.Tuple[str, ...]:
        """Remove parents from ``self._curent_parents`` based on indent levels.

        Args:
            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 = 1
        try:
            previous_parent = self._current_parents[-1]
            previous_indent = self.get_leading_space_count(previous_parent)
            while previous_indent > current_spaces:
                deindent_level += 1
                previous_parent = self._current_parents[-deindent_level]
                previous_indent = self.get_leading_space_count(previous_parent)
        except IndexError:
            raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n")
        parents = self._current_parents[:-deindent_level] or (self._current_parents[0],)
        return parents
def _update_config_lines(self, config_line): BaseSpaceConfigParser
    def _update_config_lines(self, config_line: str) -> None:
        """Add a ``ConfigLine`` object to ``self.config_lines``.

        Args:
            config_line: The current config line being evaluated.

        Returns:
            None
        """
        entry = ConfigLine(config_line, self._current_parents)
        self.config_lines.append(entry)
def build_config_relationship(self): _HPEConfigParser

_HPEConfigParser

    def build_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 = True
        for line in self.generator_config:
            if line.startswith(tuple(self.comment_chars)):
                # Closing any previous sections
                self._current_parents = ()
                self.indent_level = 0
                new_section = True
                continue
            if line.strip().startswith(tuple(self.comment_chars)):
                # Just ignore comments inside sections
                continue
            if self.is_banner_start(line):
                # Special case for banners
                self._build_banner(line)
                continue

            current_spaces = self.get_leading_space_count(line) if line[0].isspace() else 0
            if current_spaces > self.indent_level and not new_section:
                previous_config = self.config_lines[-1]
                self._current_parents += (previous_config.config_line,)
            elif current_spaces < self.indent_level:
                self._current_parents = self._remove_parents(line, current_spaces)

            new_section = False
            self.indent_level = current_spaces
            self._update_config_lines(line)
        return self.config_lines

BaseSpaceConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        r"""Parse text tree of config lines and their parents.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
            >>> config = (
            ...     "interface Ethernet1/1\n"
            ...     "  vlan 10\n"
            ...     "  no shutdown\n"
            ...     "interface Ethernet1/2\n"
            ...     "  shutdown\n"
            ... )
            >>> config_tree = BaseSpaceConfigParser(config)
            >>> config_tree.build_config_relationship() == \
            ... [
            ...     ConfigLine(config_line='interface Ethernet1/1', parents=()),
            ...     ConfigLine(config_line='  vlan 10', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='  no shutdown', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='interface Ethernet1/2', parents=(),),
            ...     ConfigLine(config_line='  shutdown', parents=('interface Ethernet1/2',))
            ... ]
            True
        """
        for index, line in enumerate(self.generator_config):
            current_spaces = self.get_leading_space_count(line) if line[0].isspace() else 0
            if index == 0 and line[0].isspace():
                self._current_parents = self._remove_parents(line, current_spaces)
            elif not line[0].isspace():
                self._current_parents = ()
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore
            else:
                previous_config = self.config_lines[-1]
                self._current_parents = (previous_config.config_line,)
                self.indent_level = self.get_leading_space_count(line)
                if not self.is_banner_start(line):
                    line = self._build_nested_config(line)  # type: ignore
                else:
                    line = self._build_banner(line)  # type: ignore
                    if line is not None and line[0].isspace():
                        line = self._build_nested_config(line)  # type: ignore
                    else:
                        self._current_parents = ()

            if line is None:
                break
            elif self.is_banner_start(line):
                line = self._build_banner(line)  # type: ignore
                # line can potentially be another banner start therefore we do a secondary check.
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore

            self._update_config_lines(line)
        return self.config_lines

BaseConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        """Parse text tree of config lines and their parents."""
        raise NotImplementedError()
def find_all_children(self, pattern, match_type=exact): BaseSpaceConfigParser
    def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]:
        """Returns configuration part for a specific pattern not including parents.

        Args:
            pattern: pattern that describes parent.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith")
            >>> print(bgp_conf)
            ['router bgp 45000', '  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if (
                parents
                and self._match_type_check(parents, pattern, match_type)
                or self._match_type_check(cfg_line.config_line, pattern, match_type)
            ):
                config.append(cfg_line.config_line)
        return config
def find_children_w_parents(self, parent_pattern, child_pattern, match_type=exact): BaseSpaceConfigParser
    def find_children_w_parents(
        self, parent_pattern: str, child_pattern: str, match_type: str = "exact"
    ) -> t.List[str]:
        """Returns configuration part for a specific pattern including parents and children.

        Args:
            parent_pattern: pattern that describes parent.
            child_pattern: pattern that describes child.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern="  address-family", match_type="regex")
            >>> print(bgp_conf)
            ['  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        potential_parents = [
            elem.parents[0]
            for elem in self.build_config_relationship()
            if self._match_type_check(elem.config_line, child_pattern, match_type)
        ]
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if parents in potential_parents and self._match_type_check(
                parents,  # type: ignore[arg-type]
                parent_pattern,
                match_type,
            ):
                config.append(cfg_line.config_line)
        return config
def get_leading_space_count(config_line): BaseSpaceConfigParser
    @staticmethod
    def get_leading_space_count(config_line: str) -> int:
        r"""Determine how many spaces the ``config_line`` is indented.

        Args:
           config_line: A line of text in the config.

        Returns:
            The number of leading spaces.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''interface GigabitEthernet1\n description link to ISP'''
            >>> config_line = " description link to ISP"
            >>> indent_level = BaseSpaceConfigParser(config).get_leading_space_count(config_line)
            >>> indent_level
            1
            >>>
        """
        return len(config_line) - len(config_line.lstrip())
def is_banner_end(self, line): BaseSpaceConfigParser
    def is_banner_end(self, line: str) -> bool:
        """Determine if line ends the banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line ends banner, else False.
        """
        if self.banner_end in line:
            return True
        return False
def is_banner_one_line(self, config_line): _HPEConfigParser
    def is_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 _delimeter not in banner_config_start:
            return False
        return True
def is_banner_start(self, line): _HPEConfigParser

_HPEConfigParser

    def is_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)
        if state:
            self.banner_end = line
        return state

BaseSpaceConfigParser

    def is_banner_start(self, line: str) -> bool:
        """Determine if the line starts a banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line starts banner, else False.
        """
        for banner_start in self.banner_start:
            if not line:
                return False
            if line.lstrip().startswith(banner_start):
                return True
        return False
def is_comment(self, line): BaseSpaceConfigParser
    def is_comment(self, line: str) -> bool:
        """Determine if line is a comment.

        Args:
            line: A config line from the device.

        Returns:
            True if line is a comment, else False.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> BaseSpaceConfigParser("interface Ethernet1/1").is_comment("interface Ethernet1/1")
            False
            >>> BaseSpaceConfigParser("!").is_comment("!")
            True
            >>>
        """
        for comment_char in self.comment_chars:
            if line.lstrip().startswith(comment_char):
                return True
        return False
def set_delimiter(self, config_line): _HPEConfigParser
    def set_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)
        if banner_parsed and "banner_delimiter" in banner_parsed.groupdict():
            self.delimiter = banner_parsed.groupdict()["banner_delimiter"]
            return None
        raise ValueError("Unable to find banner delimiter.")

HPEConfigParser

from netutils.config.parser import HPEConfigParser

Ancestors (MRO)

The Method Resolution Order is described below.

  1. HPEConfigParser
  2. _HPEConfigParser
  3. BaseSpaceConfigParser
  4. BaseConfigParser

Descendant Classes

Attributes

Key Value Defined in
banner_start ['banner', 'vacant-message'] BaseSpaceConfigParser
banner_start ['banner', 'vacant-message'] BaseConfigParser
comment_chars ['!'] BaseSpaceConfigParser
comment_chars ['!'] BaseConfigParser
regex_banner re.compile('^\\s*header\\s(\\w+)\\s+(?P<banner_delimiter>\\^C|\\S?)') _HPEConfigParser

Methods

def __init__(self, *args, **kwargs): HPEConfigParser

HPEConfigParser

            def __init__(self: t.Any, *args: t.Any, **kwargs: t.Any) -> None:
                warnings.warn(custom_message, DeprecationWarning, stacklevel=2)
                original_init(self, *args, **kwargs)

_HPEConfigParser

    def __init__(self, config: str):
        """Initialize the _HPEConfigParser object."""
        self.delimiter = ""
        self._banner_end: t.Optional[str] = None
        super(_HPEConfigParser, self).__init__(config)

BaseSpaceConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self._indent_level = 0
        super(BaseSpaceConfigParser, self).__init__(config)

BaseConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config: The config text to parse.
        """
        self.config = config
        self._config: t.Optional[str] = None
        self._current_parents: t.Tuple[str, ...] = ()
        self.generator_config = (line for line in self.config_lines_only.splitlines())
        self.config_lines: t.List[ConfigLine] = []
        self.build_config_relationship()
def _build_banner(self, config_line): _HPEConfigParser

_HPEConfigParser

    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.
        """
        if self.is_banner_one_line(config_line):
            self._update_config_lines(config_line)
            try:
                return next(self.generator_config)
            except StopIteration:
                return None
        self._update_config_lines(config_line)
        self._current_parents += (config_line,)
        banner_config = []
        for line in self.generator_config:
            if not self.is_banner_end(line):
                banner_config.append(line)
            else:
                banner_config.append(line)
                line = "\n".join(banner_config)
                if line.endswith(self.delimiter):
                    banner, end, _ = line.rpartition(self.delimiter)
                    line = banner + end
                self._update_config_lines(line)
                self._current_parents = self._current_parents[:-1]
                try:
                    return next(self.generator_config)
                except StopIteration:
                    return None
        raise ValueError("Unable to parse banner end.")

BaseSpaceConfigParser

    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None

        Raises:
            ValueError: When the parser is unable to identify the end of the Banner.
        """
        self._update_config_lines(config_line)
        self._current_parents += (config_line,)
        banner_config = []
        for line in self.generator_config:
            if not self.is_banner_end(line):
                banner_config.append(line)
            else:
                line = normalise_delimiter_caret_c(self.banner_end, line)
                banner_config.append(line)
                line = "\n".join(banner_config)
                if line.endswith("^C"):
                    banner, end, _ = line.rpartition("^C")
                    line = banner + end
                self._update_config_lines(line)
                self._current_parents = self._current_parents[:-1]
                try:
                    return next(self.generator_config)
                except StopIteration:
                    return None

        raise ValueError("Unable to parse banner end.")
def _build_nested_config(self, line): BaseSpaceConfigParser
    def _build_nested_config(self, line: str) -> t.Optional[str]:
        """Handle building child config sections.

        Args:
            line: A configuration line from the configuration text.

        Returns:
            The next top-level configuration line in the configuration text or None when the last line of configuration
            text is a nested configuration line.

        Raises:
            IndexError: When the number of parents does not match the expected deindent level.
        """
        self._update_config_lines(line)
        for line in self.generator_config:
            if not line[0].isspace():
                self._current_parents = ()
                self.indent_level = 0
                return line

            spaces = self.get_leading_space_count(line)
            if spaces == self.indent_level:
                pass
            elif spaces > self.indent_level:
                previous_config = self.config_lines[-1]
                self._current_parents += (previous_config.config_line,)
            else:
                self._current_parents = self._remove_parents(line, spaces)

            if spaces != self.indent_level:
                self.indent_level = spaces

            if self.is_banner_start(line):
                banner_line = self._build_banner(line)
                if banner_line is None or not banner_line[0].isspace():
                    self._current_parents = ()
                    self.indent_level = 0
                    return banner_line
                line = banner_line

            self._update_config_lines(line)
        return None
def _match_type_check(line, pattern, match_type): BaseSpaceConfigParser
    @staticmethod
    def _match_type_check(line: str, pattern: str, match_type: str) -> bool:
        """Checks pattern for exact match or regex."""
        if match_type == "exact" and line == pattern:
            return True
        if match_type == "startswith" and line.startswith(pattern):
            return True
        if match_type == "endswith" and line.endswith(pattern):
            return True
        if match_type == "regex" and re.match(pattern, line):
            return True
        return False
def _remove_parents(self, line, current_spaces): BaseSpaceConfigParser
    def _remove_parents(self, line: str, current_spaces: int) -> t.Tuple[str, ...]:
        """Remove parents from ``self._curent_parents`` based on indent levels.

        Args:
            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 = 1
        try:
            previous_parent = self._current_parents[-1]
            previous_indent = self.get_leading_space_count(previous_parent)
            while previous_indent > current_spaces:
                deindent_level += 1
                previous_parent = self._current_parents[-deindent_level]
                previous_indent = self.get_leading_space_count(previous_parent)
        except IndexError:
            raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n")
        parents = self._current_parents[:-deindent_level] or (self._current_parents[0],)
        return parents
def _update_config_lines(self, config_line): BaseSpaceConfigParser
    def _update_config_lines(self, config_line: str) -> None:
        """Add a ``ConfigLine`` object to ``self.config_lines``.

        Args:
            config_line: The current config line being evaluated.

        Returns:
            None
        """
        entry = ConfigLine(config_line, self._current_parents)
        self.config_lines.append(entry)
def build_config_relationship(self): _HPEConfigParser

_HPEConfigParser

    def build_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 = True
        for line in self.generator_config:
            if line.startswith(tuple(self.comment_chars)):
                # Closing any previous sections
                self._current_parents = ()
                self.indent_level = 0
                new_section = True
                continue
            if line.strip().startswith(tuple(self.comment_chars)):
                # Just ignore comments inside sections
                continue
            if self.is_banner_start(line):
                # Special case for banners
                self._build_banner(line)
                continue

            current_spaces = self.get_leading_space_count(line) if line[0].isspace() else 0
            if current_spaces > self.indent_level and not new_section:
                previous_config = self.config_lines[-1]
                self._current_parents += (previous_config.config_line,)
            elif current_spaces < self.indent_level:
                self._current_parents = self._remove_parents(line, current_spaces)

            new_section = False
            self.indent_level = current_spaces
            self._update_config_lines(line)
        return self.config_lines

BaseSpaceConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        r"""Parse text tree of config lines and their parents.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
            >>> config = (
            ...     "interface Ethernet1/1\n"
            ...     "  vlan 10\n"
            ...     "  no shutdown\n"
            ...     "interface Ethernet1/2\n"
            ...     "  shutdown\n"
            ... )
            >>> config_tree = BaseSpaceConfigParser(config)
            >>> config_tree.build_config_relationship() == \
            ... [
            ...     ConfigLine(config_line='interface Ethernet1/1', parents=()),
            ...     ConfigLine(config_line='  vlan 10', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='  no shutdown', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='interface Ethernet1/2', parents=(),),
            ...     ConfigLine(config_line='  shutdown', parents=('interface Ethernet1/2',))
            ... ]
            True
        """
        for index, line in enumerate(self.generator_config):
            current_spaces = self.get_leading_space_count(line) if line[0].isspace() else 0
            if index == 0 and line[0].isspace():
                self._current_parents = self._remove_parents(line, current_spaces)
            elif not line[0].isspace():
                self._current_parents = ()
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore
            else:
                previous_config = self.config_lines[-1]
                self._current_parents = (previous_config.config_line,)
                self.indent_level = self.get_leading_space_count(line)
                if not self.is_banner_start(line):
                    line = self._build_nested_config(line)  # type: ignore
                else:
                    line = self._build_banner(line)  # type: ignore
                    if line is not None and line[0].isspace():
                        line = self._build_nested_config(line)  # type: ignore
                    else:
                        self._current_parents = ()

            if line is None:
                break
            elif self.is_banner_start(line):
                line = self._build_banner(line)  # type: ignore
                # line can potentially be another banner start therefore we do a secondary check.
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore

            self._update_config_lines(line)
        return self.config_lines

BaseConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        """Parse text tree of config lines and their parents."""
        raise NotImplementedError()
def find_all_children(self, pattern, match_type=exact): BaseSpaceConfigParser
    def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]:
        """Returns configuration part for a specific pattern not including parents.

        Args:
            pattern: pattern that describes parent.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith")
            >>> print(bgp_conf)
            ['router bgp 45000', '  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if (
                parents
                and self._match_type_check(parents, pattern, match_type)
                or self._match_type_check(cfg_line.config_line, pattern, match_type)
            ):
                config.append(cfg_line.config_line)
        return config
def find_children_w_parents(self, parent_pattern, child_pattern, match_type=exact): BaseSpaceConfigParser
    def find_children_w_parents(
        self, parent_pattern: str, child_pattern: str, match_type: str = "exact"
    ) -> t.List[str]:
        """Returns configuration part for a specific pattern including parents and children.

        Args:
            parent_pattern: pattern that describes parent.
            child_pattern: pattern that describes child.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern="  address-family", match_type="regex")
            >>> print(bgp_conf)
            ['  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        potential_parents = [
            elem.parents[0]
            for elem in self.build_config_relationship()
            if self._match_type_check(elem.config_line, child_pattern, match_type)
        ]
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if parents in potential_parents and self._match_type_check(
                parents,  # type: ignore[arg-type]
                parent_pattern,
                match_type,
            ):
                config.append(cfg_line.config_line)
        return config
def get_leading_space_count(config_line): BaseSpaceConfigParser
    @staticmethod
    def get_leading_space_count(config_line: str) -> int:
        r"""Determine how many spaces the ``config_line`` is indented.

        Args:
           config_line: A line of text in the config.

        Returns:
            The number of leading spaces.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''interface GigabitEthernet1\n description link to ISP'''
            >>> config_line = " description link to ISP"
            >>> indent_level = BaseSpaceConfigParser(config).get_leading_space_count(config_line)
            >>> indent_level
            1
            >>>
        """
        return len(config_line) - len(config_line.lstrip())
def is_banner_end(self, line): BaseSpaceConfigParser
    def is_banner_end(self, line: str) -> bool:
        """Determine if line ends the banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line ends banner, else False.
        """
        if self.banner_end in line:
            return True
        return False
def is_banner_one_line(self, config_line): _HPEConfigParser
    def is_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 _delimeter not in banner_config_start:
            return False
        return True
def is_banner_start(self, line): _HPEConfigParser

_HPEConfigParser

    def is_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)
        if state:
            self.banner_end = line
        return state

BaseSpaceConfigParser

    def is_banner_start(self, line: str) -> bool:
        """Determine if the line starts a banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line starts banner, else False.
        """
        for banner_start in self.banner_start:
            if not line:
                return False
            if line.lstrip().startswith(banner_start):
                return True
        return False
def is_comment(self, line): BaseSpaceConfigParser
    def is_comment(self, line: str) -> bool:
        """Determine if line is a comment.

        Args:
            line: A config line from the device.

        Returns:
            True if line is a comment, else False.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> BaseSpaceConfigParser("interface Ethernet1/1").is_comment("interface Ethernet1/1")
            False
            >>> BaseSpaceConfigParser("!").is_comment("!")
            True
            >>>
        """
        for comment_char in self.comment_chars:
            if line.lstrip().startswith(comment_char):
                return True
        return False
def set_delimiter(self, config_line): _HPEConfigParser
    def set_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)
        if banner_parsed and "banner_delimiter" in banner_parsed.groupdict():
            self.delimiter = banner_parsed.groupdict()["banner_delimiter"]
            return None
        raise ValueError("Unable to find banner delimiter.")

IOSConfigParser

from netutils.config.parser import IOSConfigParser

Ancestors (MRO)

The Method Resolution Order is described below.

  1. IOSConfigParser
  2. CiscoConfigParser
  3. BaseSpaceConfigParser
  4. BaseConfigParser

Descendant Classes

Attributes

Key Value Defined in
banner_start ['banner', 'vacant-message'] BaseSpaceConfigParser
banner_start ['banner', 'vacant-message'] BaseConfigParser
comment_chars ['!'] BaseSpaceConfigParser
comment_chars ['!'] BaseConfigParser
regex_banner re.compile('^(banner\\s+\\S+|\\s*vacant-message)\\s+(?P<banner_delimiter>\\^C|.)') CiscoConfigParser

Methods

def __init__(self, config): IOSConfigParser

IOSConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self.unique_config_lines: t.Set[ConfigLine] = set()
        self.same_line_children: t.Set[ConfigLine] = set()
        super(IOSConfigParser, self).__init__(config)

CiscoConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self._banner_end: t.Optional[str] = None
        super(CiscoConfigParser, self).__init__(config)

BaseSpaceConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self._indent_level = 0
        super(BaseSpaceConfigParser, self).__init__(config)

BaseConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config: The config text to parse.
        """
        self.config = config
        self._config: t.Optional[str] = None
        self._current_parents: t.Tuple[str, ...] = ()
        self.generator_config = (line for line in self.config_lines_only.splitlines())
        self.config_lines: t.List[ConfigLine] = []
        self.build_config_relationship()
def _build_banner(self, config_line): IOSConfigParser

IOSConfigParser

    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None when banner end is the end of the config text.

        Raises:
            ValueError: When the parser is unable to identify the End of the Banner.
        """
        config_line = normalise_delimiter_caret_c(self.banner_end, config_line)
        return super(IOSConfigParser, self)._build_banner(config_line)

CiscoConfigParser

    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None when banner end is the end of the config text.

        Raises:
            ValueError: When the parser is unable to identify the End of the Banner.
        """
        if self.is_banner_one_line(config_line):
            self._update_config_lines(config_line)
            try:
                return next(self.generator_config)
            except StopIteration:
                return None
        return super(CiscoConfigParser, self)._build_banner(config_line)

BaseSpaceConfigParser

    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None

        Raises:
            ValueError: When the parser is unable to identify the end of the Banner.
        """
        self._update_config_lines(config_line)
        self._current_parents += (config_line,)
        banner_config = []
        for line in self.generator_config:
            if not self.is_banner_end(line):
                banner_config.append(line)
            else:
                line = normalise_delimiter_caret_c(self.banner_end, line)
                banner_config.append(line)
                line = "\n".join(banner_config)
                if line.endswith("^C"):
                    banner, end, _ = line.rpartition("^C")
                    line = banner + end
                self._update_config_lines(line)
                self._current_parents = self._current_parents[:-1]
                try:
                    return next(self.generator_config)
                except StopIteration:
                    return None

        raise ValueError("Unable to parse banner end.")
def _build_nested_config(self, line): BaseSpaceConfigParser
    def _build_nested_config(self, line: str) -> t.Optional[str]:
        """Handle building child config sections.

        Args:
            line: A configuration line from the configuration text.

        Returns:
            The next top-level configuration line in the configuration text or None when the last line of configuration
            text is a nested configuration line.

        Raises:
            IndexError: When the number of parents does not match the expected deindent level.
        """
        self._update_config_lines(line)
        for line in self.generator_config:
            if not line[0].isspace():
                self._current_parents = ()
                self.indent_level = 0
                return line

            spaces = self.get_leading_space_count(line)
            if spaces == self.indent_level:
                pass
            elif spaces > self.indent_level:
                previous_config = self.config_lines[-1]
                self._current_parents += (previous_config.config_line,)
            else:
                self._current_parents = self._remove_parents(line, spaces)

            if spaces != self.indent_level:
                self.indent_level = spaces

            if self.is_banner_start(line):
                banner_line = self._build_banner(line)
                if banner_line is None or not banner_line[0].isspace():
                    self._current_parents = ()
                    self.indent_level = 0
                    return banner_line
                line = banner_line

            self._update_config_lines(line)
        return None
def _match_type_check(line, pattern, match_type): BaseSpaceConfigParser
    @staticmethod
    def _match_type_check(line: str, pattern: str, match_type: str) -> bool:
        """Checks pattern for exact match or regex."""
        if match_type == "exact" and line == pattern:
            return True
        if match_type == "startswith" and line.startswith(pattern):
            return True
        if match_type == "endswith" and line.endswith(pattern):
            return True
        if match_type == "regex" and re.match(pattern, line):
            return True
        return False
def _remove_parents(self, line, current_spaces): BaseSpaceConfigParser
    def _remove_parents(self, line: str, current_spaces: int) -> t.Tuple[str, ...]:
        """Remove parents from ``self._curent_parents`` based on indent levels.

        Args:
            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 = 1
        try:
            previous_parent = self._current_parents[-1]
            previous_indent = self.get_leading_space_count(previous_parent)
            while previous_indent > current_spaces:
                deindent_level += 1
                previous_parent = self._current_parents[-deindent_level]
                previous_indent = self.get_leading_space_count(previous_parent)
        except IndexError:
            raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n")
        parents = self._current_parents[:-deindent_level] or (self._current_parents[0],)
        return parents
def _update_config_lines(self, config_line): IOSConfigParser

IOSConfigParser

    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]
        if entry in self.unique_config_lines:
            self.same_line_children.add(entry)
        self.unique_config_lines.add(entry)

BaseSpaceConfigParser

    def _update_config_lines(self, config_line: str) -> None:
        """Add a ``ConfigLine`` object to ``self.config_lines``.

        Args:
            config_line: The current config line being evaluated.

        Returns:
            None
        """
        entry = ConfigLine(config_line, self._current_parents)
        self.config_lines.append(entry)
def _update_same_line_children_configs(self): IOSConfigParser
    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] = []
        for line in self.config_lines:
            if line in self.same_line_children:
                try:
                    previous_line = new_config_lines[-1]
                except IndexError as error:
                    raise IndexError(
                        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_line
                current_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
def build_config_relationship(self): IOSConfigParser

IOSConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        r"""Parse text tree of config lines and their parents.

        Examples:
            >>> from netutils.config.parser import IOSConfigParser, ConfigLine
            >>> config = '''
            ... interface Ethernet1/1
            ...   vlan 10
            ...   no shutdown
            ... interface Ethernet1/2
            ...   shutdown'''
            >>> config_tree = IOSConfigParser(str(config))
            >>> config_tree.build_config_relationship() == \
            ... [
            ...     ConfigLine(config_line='interface Ethernet1/1', parents=()),
            ...     ConfigLine(config_line='  vlan 10', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='  no shutdown', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='interface Ethernet1/2', parents=()),
            ...     ConfigLine(config_line='  shutdown', parents=('interface Ethernet1/2',))
            ... ]
            True
        """
        super(IOSConfigParser, self).build_config_relationship()
        self._update_same_line_children_configs()
        return self.config_lines

BaseSpaceConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        r"""Parse text tree of config lines and their parents.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
            >>> config = (
            ...     "interface Ethernet1/1\n"
            ...     "  vlan 10\n"
            ...     "  no shutdown\n"
            ...     "interface Ethernet1/2\n"
            ...     "  shutdown\n"
            ... )
            >>> config_tree = BaseSpaceConfigParser(config)
            >>> config_tree.build_config_relationship() == \
            ... [
            ...     ConfigLine(config_line='interface Ethernet1/1', parents=()),
            ...     ConfigLine(config_line='  vlan 10', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='  no shutdown', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='interface Ethernet1/2', parents=(),),
            ...     ConfigLine(config_line='  shutdown', parents=('interface Ethernet1/2',))
            ... ]
            True
        """
        for index, line in enumerate(self.generator_config):
            current_spaces = self.get_leading_space_count(line) if line[0].isspace() else 0
            if index == 0 and line[0].isspace():
                self._current_parents = self._remove_parents(line, current_spaces)
            elif not line[0].isspace():
                self._current_parents = ()
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore
            else:
                previous_config = self.config_lines[-1]
                self._current_parents = (previous_config.config_line,)
                self.indent_level = self.get_leading_space_count(line)
                if not self.is_banner_start(line):
                    line = self._build_nested_config(line)  # type: ignore
                else:
                    line = self._build_banner(line)  # type: ignore
                    if line is not None and line[0].isspace():
                        line = self._build_nested_config(line)  # type: ignore
                    else:
                        self._current_parents = ()

            if line is None:
                break
            elif self.is_banner_start(line):
                line = self._build_banner(line)  # type: ignore
                # line can potentially be another banner start therefore we do a secondary check.
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore

            self._update_config_lines(line)
        return self.config_lines

BaseConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        """Parse text tree of config lines and their parents."""
        raise NotImplementedError()
def find_all_children(self, pattern, match_type=exact): BaseSpaceConfigParser
    def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]:
        """Returns configuration part for a specific pattern not including parents.

        Args:
            pattern: pattern that describes parent.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith")
            >>> print(bgp_conf)
            ['router bgp 45000', '  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if (
                parents
                and self._match_type_check(parents, pattern, match_type)
                or self._match_type_check(cfg_line.config_line, pattern, match_type)
            ):
                config.append(cfg_line.config_line)
        return config
def find_children_w_parents(self, parent_pattern, child_pattern, match_type=exact): BaseSpaceConfigParser
    def find_children_w_parents(
        self, parent_pattern: str, child_pattern: str, match_type: str = "exact"
    ) -> t.List[str]:
        """Returns configuration part for a specific pattern including parents and children.

        Args:
            parent_pattern: pattern that describes parent.
            child_pattern: pattern that describes child.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern="  address-family", match_type="regex")
            >>> print(bgp_conf)
            ['  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        potential_parents = [
            elem.parents[0]
            for elem in self.build_config_relationship()
            if self._match_type_check(elem.config_line, child_pattern, match_type)
        ]
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if parents in potential_parents and self._match_type_check(
                parents,  # type: ignore[arg-type]
                parent_pattern,
                match_type,
            ):
                config.append(cfg_line.config_line)
        return config
def get_leading_space_count(config_line): BaseSpaceConfigParser
    @staticmethod
    def get_leading_space_count(config_line: str) -> int:
        r"""Determine how many spaces the ``config_line`` is indented.

        Args:
           config_line: A line of text in the config.

        Returns:
            The number of leading spaces.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''interface GigabitEthernet1\n description link to ISP'''
            >>> config_line = " description link to ISP"
            >>> indent_level = BaseSpaceConfigParser(config).get_leading_space_count(config_line)
            >>> indent_level
            1
            >>>
        """
        return len(config_line) - len(config_line.lstrip())
def is_banner_end(self, line): BaseSpaceConfigParser
    def is_banner_end(self, line: str) -> bool:
        """Determine if line ends the banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line ends banner, else False.
        """
        if self.banner_end in line:
            return True
        return False
def is_banner_one_line(config_line): CiscoConfigParser
    @staticmethod
    def is_banner_one_line(config_line: str) -> bool:
        """Determine if all banner config is on one line."""
        _, delimeter, banner = config_line.partition("^C")
        # if the banner is the delimeter is a single line empty banner. e.g banner motd ^C^C which ios allows.
        if banner == "^C":
            return True
        # Based on NXOS configs, the banner delimeter is ignored until another char is used
        banner_config_start = banner.lstrip(delimeter)
        if delimeter not in banner_config_start:
            return False
        return True
def is_banner_start(self, line): CiscoConfigParser

CiscoConfigParser

    def is_banner_start(self, line: str) -> bool:
        """Determine if the line starts a banner config."""
        state = super(CiscoConfigParser, self).is_banner_start(line)
        if state:
            self.banner_end = line
        return state

BaseSpaceConfigParser

    def is_banner_start(self, line: str) -> bool:
        """Determine if the line starts a banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line starts banner, else False.
        """
        for banner_start in self.banner_start:
            if not line:
                return False
            if line.lstrip().startswith(banner_start):
                return True
        return False
def is_comment(self, line): BaseSpaceConfigParser
    def is_comment(self, line: str) -> bool:
        """Determine if line is a comment.

        Args:
            line: A config line from the device.

        Returns:
            True if line is a comment, else False.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> BaseSpaceConfigParser("interface Ethernet1/1").is_comment("interface Ethernet1/1")
            False
            >>> BaseSpaceConfigParser("!").is_comment("!")
            True
            >>>
        """
        for comment_char in self.comment_chars:
            if line.lstrip().startswith(comment_char):
                return True
        return False

IOSXRConfigParser

from netutils.config.parser import IOSXRConfigParser

Ancestors (MRO)

The Method Resolution Order is described below.

  1. IOSXRConfigParser
  2. CiscoConfigParser
  3. BaseSpaceConfigParser
  4. BaseConfigParser

Descendant Classes

Attributes

Key Value Defined in
banner_start ['banner', 'vacant-message'] BaseSpaceConfigParser
banner_start ['banner', 'vacant-message'] BaseConfigParser
comment_chars ['!'] BaseSpaceConfigParser
comment_chars ['!'] BaseConfigParser
regex_banner re.compile('^banner\\s+\\S+\\s+(?P<banner_delimiter>\\S)') IOSXRConfigParser
regex_banner re.compile('^(banner\\s+\\S+|\\s*vacant-message)\\s+(?P<banner_delimiter>\\^C|.)') CiscoConfigParser

Methods

def __init__(self, config): IOSXRConfigParser

IOSXRConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self.delimiter = ""
        super(IOSXRConfigParser, self).__init__(config)

CiscoConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self._banner_end: t.Optional[str] = None
        super(CiscoConfigParser, self).__init__(config)

BaseSpaceConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self._indent_level = 0
        super(BaseSpaceConfigParser, self).__init__(config)

BaseConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config: The config text to parse.
        """
        self.config = config
        self._config: t.Optional[str] = None
        self._current_parents: t.Tuple[str, ...] = ()
        self.generator_config = (line for line in self.config_lines_only.splitlines())
        self.config_lines: t.List[ConfigLine] = []
        self.build_config_relationship()
def _build_banner(self, config_line): IOSXRConfigParser

IOSXRConfigParser

    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None

        Raises:
            ValueError: When the parser is unable to identify the end of the Banner.
        """
        self._update_config_lines(config_line)
        self._current_parents += (config_line,)
        banner_config = []
        for line in self.generator_config:
            if not self.is_banner_end(line):
                banner_config.append(line)
            else:
                banner_config.append(line)
                line = "\n".join(banner_config)
                if line.endswith(self.delimiter):
                    banner, end, _ = line.rpartition(self.delimiter)
                    line = banner + end
                self._update_config_lines(line)
                self._current_parents = self._current_parents[:-1]
                try:
                    return next(self.generator_config)
                except StopIteration:
                    return None

        raise ValueError("Unable to parse banner end.")

CiscoConfigParser

    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None when banner end is the end of the config text.

        Raises:
            ValueError: When the parser is unable to identify the End of the Banner.
        """
        if self.is_banner_one_line(config_line):
            self._update_config_lines(config_line)
            try:
                return next(self.generator_config)
            except StopIteration:
                return None
        return super(CiscoConfigParser, self)._build_banner(config_line)

BaseSpaceConfigParser

    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None

        Raises:
            ValueError: When the parser is unable to identify the end of the Banner.
        """
        self._update_config_lines(config_line)
        self._current_parents += (config_line,)
        banner_config = []
        for line in self.generator_config:
            if not self.is_banner_end(line):
                banner_config.append(line)
            else:
                line = normalise_delimiter_caret_c(self.banner_end, line)
                banner_config.append(line)
                line = "\n".join(banner_config)
                if line.endswith("^C"):
                    banner, end, _ = line.rpartition("^C")
                    line = banner + end
                self._update_config_lines(line)
                self._current_parents = self._current_parents[:-1]
                try:
                    return next(self.generator_config)
                except StopIteration:
                    return None

        raise ValueError("Unable to parse banner end.")
def _build_nested_config(self, line): BaseSpaceConfigParser
    def _build_nested_config(self, line: str) -> t.Optional[str]:
        """Handle building child config sections.

        Args:
            line: A configuration line from the configuration text.

        Returns:
            The next top-level configuration line in the configuration text or None when the last line of configuration
            text is a nested configuration line.

        Raises:
            IndexError: When the number of parents does not match the expected deindent level.
        """
        self._update_config_lines(line)
        for line in self.generator_config:
            if not line[0].isspace():
                self._current_parents = ()
                self.indent_level = 0
                return line

            spaces = self.get_leading_space_count(line)
            if spaces == self.indent_level:
                pass
            elif spaces > self.indent_level:
                previous_config = self.config_lines[-1]
                self._current_parents += (previous_config.config_line,)
            else:
                self._current_parents = self._remove_parents(line, spaces)

            if spaces != self.indent_level:
                self.indent_level = spaces

            if self.is_banner_start(line):
                banner_line = self._build_banner(line)
                if banner_line is None or not banner_line[0].isspace():
                    self._current_parents = ()
                    self.indent_level = 0
                    return banner_line
                line = banner_line

            self._update_config_lines(line)
        return None
def _match_type_check(line, pattern, match_type): BaseSpaceConfigParser
    @staticmethod
    def _match_type_check(line: str, pattern: str, match_type: str) -> bool:
        """Checks pattern for exact match or regex."""
        if match_type == "exact" and line == pattern:
            return True
        if match_type == "startswith" and line.startswith(pattern):
            return True
        if match_type == "endswith" and line.endswith(pattern):
            return True
        if match_type == "regex" and re.match(pattern, line):
            return True
        return False
def _remove_parents(self, line, current_spaces): BaseSpaceConfigParser
    def _remove_parents(self, line: str, current_spaces: int) -> t.Tuple[str, ...]:
        """Remove parents from ``self._curent_parents`` based on indent levels.

        Args:
            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 = 1
        try:
            previous_parent = self._current_parents[-1]
            previous_indent = self.get_leading_space_count(previous_parent)
            while previous_indent > current_spaces:
                deindent_level += 1
                previous_parent = self._current_parents[-deindent_level]
                previous_indent = self.get_leading_space_count(previous_parent)
        except IndexError:
            raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n")
        parents = self._current_parents[:-deindent_level] or (self._current_parents[0],)
        return parents
def _update_config_lines(self, config_line): BaseSpaceConfigParser
    def _update_config_lines(self, config_line: str) -> None:
        """Add a ``ConfigLine`` object to ``self.config_lines``.

        Args:
            config_line: The current config line being evaluated.

        Returns:
            None
        """
        entry = ConfigLine(config_line, self._current_parents)
        self.config_lines.append(entry)
def build_config_relationship(self): IOSXRConfigParser

IOSXRConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        r"""Parse text tree of config lines and their parents.

        Examples:
            >>> from netutils.config.parser import IOSXRConfigParser, ConfigLine
            >>> config = (
            ...     "interface Ethernet1/1\n"
            ...     "  vlan 10\n"
            ...     "  no shutdown"
            ...     "interface Ethernet1/2\n"
            ...     "  shutdown\n"
            ... )
            >>> config_tree = IOSXRConfigParser(config)
            >>> config_tree.build_config_relationship() == \
            ... [
            ...     ConfigLine(config_line='interface Ethernet1/1', parents=()),
            ...     ConfigLine(config_line='  vlan 10', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='  no shutdowninterface Ethernet1/2', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='  shutdown', parents=('interface Ethernet1/1',))
            ... ]
            True
        """
        for index, line in enumerate(self.generator_config):
            current_spaces = self.get_leading_space_count(line) if line[0].isspace() else 0

            if index == 0 and line[0].isspace():
                self._current_parents = self._remove_parents(line, current_spaces)
            if not line[0].isspace():
                self._current_parents = ()
                if self.is_banner_start(line):
                    if not self.delimiter:
                        self.set_delimiter(line)
                    line = self._build_banner(line)  # type: ignore
            else:
                previous_config = self.config_lines[-1]
                self._current_parents = (previous_config.config_line,)
                self.indent_level = self.get_leading_space_count(line)
                line = self._build_nested_config(line)  # type: ignore

            if line is None:
                break
            elif self.is_banner_start(line):
                line = self._build_banner(line)  # type: ignore

            self._update_config_lines(line)
        return self.config_lines

BaseSpaceConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        r"""Parse text tree of config lines and their parents.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
            >>> config = (
            ...     "interface Ethernet1/1\n"
            ...     "  vlan 10\n"
            ...     "  no shutdown\n"
            ...     "interface Ethernet1/2\n"
            ...     "  shutdown\n"
            ... )
            >>> config_tree = BaseSpaceConfigParser(config)
            >>> config_tree.build_config_relationship() == \
            ... [
            ...     ConfigLine(config_line='interface Ethernet1/1', parents=()),
            ...     ConfigLine(config_line='  vlan 10', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='  no shutdown', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='interface Ethernet1/2', parents=(),),
            ...     ConfigLine(config_line='  shutdown', parents=('interface Ethernet1/2',))
            ... ]
            True
        """
        for index, line in enumerate(self.generator_config):
            current_spaces = self.get_leading_space_count(line) if line[0].isspace() else 0
            if index == 0 and line[0].isspace():
                self._current_parents = self._remove_parents(line, current_spaces)
            elif not line[0].isspace():
                self._current_parents = ()
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore
            else:
                previous_config = self.config_lines[-1]
                self._current_parents = (previous_config.config_line,)
                self.indent_level = self.get_leading_space_count(line)
                if not self.is_banner_start(line):
                    line = self._build_nested_config(line)  # type: ignore
                else:
                    line = self._build_banner(line)  # type: ignore
                    if line is not None and line[0].isspace():
                        line = self._build_nested_config(line)  # type: ignore
                    else:
                        self._current_parents = ()

            if line is None:
                break
            elif self.is_banner_start(line):
                line = self._build_banner(line)  # type: ignore
                # line can potentially be another banner start therefore we do a secondary check.
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore

            self._update_config_lines(line)
        return self.config_lines

BaseConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        """Parse text tree of config lines and their parents."""
        raise NotImplementedError()
def find_all_children(self, pattern, match_type=exact): BaseSpaceConfigParser
    def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]:
        """Returns configuration part for a specific pattern not including parents.

        Args:
            pattern: pattern that describes parent.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith")
            >>> print(bgp_conf)
            ['router bgp 45000', '  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if (
                parents
                and self._match_type_check(parents, pattern, match_type)
                or self._match_type_check(cfg_line.config_line, pattern, match_type)
            ):
                config.append(cfg_line.config_line)
        return config
def find_children_w_parents(self, parent_pattern, child_pattern, match_type=exact): BaseSpaceConfigParser
    def find_children_w_parents(
        self, parent_pattern: str, child_pattern: str, match_type: str = "exact"
    ) -> t.List[str]:
        """Returns configuration part for a specific pattern including parents and children.

        Args:
            parent_pattern: pattern that describes parent.
            child_pattern: pattern that describes child.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern="  address-family", match_type="regex")
            >>> print(bgp_conf)
            ['  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        potential_parents = [
            elem.parents[0]
            for elem in self.build_config_relationship()
            if self._match_type_check(elem.config_line, child_pattern, match_type)
        ]
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if parents in potential_parents and self._match_type_check(
                parents,  # type: ignore[arg-type]
                parent_pattern,
                match_type,
            ):
                config.append(cfg_line.config_line)
        return config
def get_leading_space_count(config_line): BaseSpaceConfigParser
    @staticmethod
    def get_leading_space_count(config_line: str) -> int:
        r"""Determine how many spaces the ``config_line`` is indented.

        Args:
           config_line: A line of text in the config.

        Returns:
            The number of leading spaces.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''interface GigabitEthernet1\n description link to ISP'''
            >>> config_line = " description link to ISP"
            >>> indent_level = BaseSpaceConfigParser(config).get_leading_space_count(config_line)
            >>> indent_level
            1
            >>>
        """
        return len(config_line) - len(config_line.lstrip())
def is_banner_end(self, line): BaseSpaceConfigParser
    def is_banner_end(self, line: str) -> bool:
        """Determine if line ends the banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line ends banner, else False.
        """
        if self.banner_end in line:
            return True
        return False
def is_banner_one_line(config_line): CiscoConfigParser
    @staticmethod
    def is_banner_one_line(config_line: str) -> bool:
        """Determine if all banner config is on one line."""
        _, delimeter, banner = config_line.partition("^C")
        # if the banner is the delimeter is a single line empty banner. e.g banner motd ^C^C which ios allows.
        if banner == "^C":
            return True
        # Based on NXOS configs, the banner delimeter is ignored until another char is used
        banner_config_start = banner.lstrip(delimeter)
        if delimeter not in banner_config_start:
            return False
        return True
def is_banner_start(self, line): CiscoConfigParser

CiscoConfigParser

    def is_banner_start(self, line: str) -> bool:
        """Determine if the line starts a banner config."""
        state = super(CiscoConfigParser, self).is_banner_start(line)
        if state:
            self.banner_end = line
        return state

BaseSpaceConfigParser

    def is_banner_start(self, line: str) -> bool:
        """Determine if the line starts a banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line starts banner, else False.
        """
        for banner_start in self.banner_start:
            if not line:
                return False
            if line.lstrip().startswith(banner_start):
                return True
        return False
def is_comment(self, line): BaseSpaceConfigParser
    def is_comment(self, line: str) -> bool:
        """Determine if line is a comment.

        Args:
            line: A config line from the device.

        Returns:
            True if line is a comment, else False.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> BaseSpaceConfigParser("interface Ethernet1/1").is_comment("interface Ethernet1/1")
            False
            >>> BaseSpaceConfigParser("!").is_comment("!")
            True
            >>>
        """
        for comment_char in self.comment_chars:
            if line.lstrip().startswith(comment_char):
                return True
        return False
def set_delimiter(self, config_line): IOSXRConfigParser
    def set_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)
        if banner_parsed and "banner_delimiter" in banner_parsed.groupdict():
            self.delimiter = banner_parsed.groupdict()["banner_delimiter"]
            return None
        raise ValueError("Unable to find banner delimiter.")

JunosConfigParser

from netutils.config.parser import JunosConfigParser

Ancestors (MRO)

The Method Resolution Order is described below.

  1. JunosConfigParser
  2. BaseSpaceConfigParser
  3. BaseConfigParser

Descendant Classes

Attributes

Key Value Defined in
banner_start ['banner', 'vacant-message'] BaseSpaceConfigParser
banner_start ['banner', 'vacant-message'] BaseConfigParser
comment_chars ['!'] BaseSpaceConfigParser
comment_chars ['!'] BaseConfigParser

Methods

def __init__(self, config): BaseSpaceConfigParser

BaseSpaceConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self._indent_level = 0
        super(BaseSpaceConfigParser, self).__init__(config)

BaseConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config: The config text to parse.
        """
        self.config = config
        self._config: t.Optional[str] = None
        self._current_parents: t.Tuple[str, ...] = ()
        self.generator_config = (line for line in self.config_lines_only.splitlines())
        self.config_lines: t.List[ConfigLine] = []
        self.build_config_relationship()
def _build_banner(self, config_line): BaseSpaceConfigParser
    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None

        Raises:
            ValueError: When the parser is unable to identify the end of the Banner.
        """
        self._update_config_lines(config_line)
        self._current_parents += (config_line,)
        banner_config = []
        for line in self.generator_config:
            if not self.is_banner_end(line):
                banner_config.append(line)
            else:
                line = normalise_delimiter_caret_c(self.banner_end, line)
                banner_config.append(line)
                line = "\n".join(banner_config)
                if line.endswith("^C"):
                    banner, end, _ = line.rpartition("^C")
                    line = banner + end
                self._update_config_lines(line)
                self._current_parents = self._current_parents[:-1]
                try:
                    return next(self.generator_config)
                except StopIteration:
                    return None

        raise ValueError("Unable to parse banner end.")
def _build_nested_config(self, line): BaseSpaceConfigParser
    def _build_nested_config(self, line: str) -> t.Optional[str]:
        """Handle building child config sections.

        Args:
            line: A configuration line from the configuration text.

        Returns:
            The next top-level configuration line in the configuration text or None when the last line of configuration
            text is a nested configuration line.

        Raises:
            IndexError: When the number of parents does not match the expected deindent level.
        """
        self._update_config_lines(line)
        for line in self.generator_config:
            if not line[0].isspace():
                self._current_parents = ()
                self.indent_level = 0
                return line

            spaces = self.get_leading_space_count(line)
            if spaces == self.indent_level:
                pass
            elif spaces > self.indent_level:
                previous_config = self.config_lines[-1]
                self._current_parents += (previous_config.config_line,)
            else:
                self._current_parents = self._remove_parents(line, spaces)

            if spaces != self.indent_level:
                self.indent_level = spaces

            if self.is_banner_start(line):
                banner_line = self._build_banner(line)
                if banner_line is None or not banner_line[0].isspace():
                    self._current_parents = ()
                    self.indent_level = 0
                    return banner_line
                line = banner_line

            self._update_config_lines(line)
        return None
def _match_type_check(line, pattern, match_type): BaseSpaceConfigParser
    @staticmethod
    def _match_type_check(line: str, pattern: str, match_type: str) -> bool:
        """Checks pattern for exact match or regex."""
        if match_type == "exact" and line == pattern:
            return True
        if match_type == "startswith" and line.startswith(pattern):
            return True
        if match_type == "endswith" and line.endswith(pattern):
            return True
        if match_type == "regex" and re.match(pattern, line):
            return True
        return False
def _remove_parents(self, line, current_spaces): BaseSpaceConfigParser
    def _remove_parents(self, line: str, current_spaces: int) -> t.Tuple[str, ...]:
        """Remove parents from ``self._curent_parents`` based on indent levels.

        Args:
            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 = 1
        try:
            previous_parent = self._current_parents[-1]
            previous_indent = self.get_leading_space_count(previous_parent)
            while previous_indent > current_spaces:
                deindent_level += 1
                previous_parent = self._current_parents[-deindent_level]
                previous_indent = self.get_leading_space_count(previous_parent)
        except IndexError:
            raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n")
        parents = self._current_parents[:-deindent_level] or (self._current_parents[0],)
        return parents
def _update_config_lines(self, config_line): BaseSpaceConfigParser
    def _update_config_lines(self, config_line: str) -> None:
        """Add a ``ConfigLine`` object to ``self.config_lines``.

        Args:
            config_line: The current config line being evaluated.

        Returns:
            None
        """
        entry = ConfigLine(config_line, self._current_parents)
        self.config_lines.append(entry)
def build_config_relationship(self): BaseSpaceConfigParser

BaseSpaceConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        r"""Parse text tree of config lines and their parents.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
            >>> config = (
            ...     "interface Ethernet1/1\n"
            ...     "  vlan 10\n"
            ...     "  no shutdown\n"
            ...     "interface Ethernet1/2\n"
            ...     "  shutdown\n"
            ... )
            >>> config_tree = BaseSpaceConfigParser(config)
            >>> config_tree.build_config_relationship() == \
            ... [
            ...     ConfigLine(config_line='interface Ethernet1/1', parents=()),
            ...     ConfigLine(config_line='  vlan 10', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='  no shutdown', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='interface Ethernet1/2', parents=(),),
            ...     ConfigLine(config_line='  shutdown', parents=('interface Ethernet1/2',))
            ... ]
            True
        """
        for index, line in enumerate(self.generator_config):
            current_spaces = self.get_leading_space_count(line) if line[0].isspace() else 0
            if index == 0 and line[0].isspace():
                self._current_parents = self._remove_parents(line, current_spaces)
            elif not line[0].isspace():
                self._current_parents = ()
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore
            else:
                previous_config = self.config_lines[-1]
                self._current_parents = (previous_config.config_line,)
                self.indent_level = self.get_leading_space_count(line)
                if not self.is_banner_start(line):
                    line = self._build_nested_config(line)  # type: ignore
                else:
                    line = self._build_banner(line)  # type: ignore
                    if line is not None and line[0].isspace():
                        line = self._build_nested_config(line)  # type: ignore
                    else:
                        self._current_parents = ()

            if line is None:
                break
            elif self.is_banner_start(line):
                line = self._build_banner(line)  # type: ignore
                # line can potentially be another banner start therefore we do a secondary check.
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore

            self._update_config_lines(line)
        return self.config_lines

BaseConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        """Parse text tree of config lines and their parents."""
        raise NotImplementedError()
def find_all_children(self, pattern, match_type=exact): BaseSpaceConfigParser
    def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]:
        """Returns configuration part for a specific pattern not including parents.

        Args:
            pattern: pattern that describes parent.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith")
            >>> print(bgp_conf)
            ['router bgp 45000', '  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if (
                parents
                and self._match_type_check(parents, pattern, match_type)
                or self._match_type_check(cfg_line.config_line, pattern, match_type)
            ):
                config.append(cfg_line.config_line)
        return config
def find_children_w_parents(self, parent_pattern, child_pattern, match_type=exact): BaseSpaceConfigParser
    def find_children_w_parents(
        self, parent_pattern: str, child_pattern: str, match_type: str = "exact"
    ) -> t.List[str]:
        """Returns configuration part for a specific pattern including parents and children.

        Args:
            parent_pattern: pattern that describes parent.
            child_pattern: pattern that describes child.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern="  address-family", match_type="regex")
            >>> print(bgp_conf)
            ['  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        potential_parents = [
            elem.parents[0]
            for elem in self.build_config_relationship()
            if self._match_type_check(elem.config_line, child_pattern, match_type)
        ]
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if parents in potential_parents and self._match_type_check(
                parents,  # type: ignore[arg-type]
                parent_pattern,
                match_type,
            ):
                config.append(cfg_line.config_line)
        return config
def get_leading_space_count(config_line): BaseSpaceConfigParser
    @staticmethod
    def get_leading_space_count(config_line: str) -> int:
        r"""Determine how many spaces the ``config_line`` is indented.

        Args:
           config_line: A line of text in the config.

        Returns:
            The number of leading spaces.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''interface GigabitEthernet1\n description link to ISP'''
            >>> config_line = " description link to ISP"
            >>> indent_level = BaseSpaceConfigParser(config).get_leading_space_count(config_line)
            >>> indent_level
            1
            >>>
        """
        return len(config_line) - len(config_line.lstrip())
def is_banner_end(self, line): BaseSpaceConfigParser
    def is_banner_end(self, line: str) -> bool:
        """Determine if line ends the banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line ends banner, else False.
        """
        if self.banner_end in line:
            return True
        return False
def is_banner_start(self, line): BaseSpaceConfigParser
    def is_banner_start(self, line: str) -> bool:
        """Determine if the line starts a banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line starts banner, else False.
        """
        for banner_start in self.banner_start:
            if not line:
                return False
            if line.lstrip().startswith(banner_start):
                return True
        return False
def is_comment(self, line): BaseSpaceConfigParser
    def is_comment(self, line: str) -> bool:
        """Determine if line is a comment.

        Args:
            line: A config line from the device.

        Returns:
            True if line is a comment, else False.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> BaseSpaceConfigParser("interface Ethernet1/1").is_comment("interface Ethernet1/1")
            False
            >>> BaseSpaceConfigParser("!").is_comment("!")
            True
            >>>
        """
        for comment_char in self.comment_chars:
            if line.lstrip().startswith(comment_char):
                return True
        return False

LINUXConfigParser

from netutils.config.parser import LINUXConfigParser

Ancestors (MRO)

The Method Resolution Order is described below.

  1. LINUXConfigParser
  2. BaseSpaceConfigParser
  3. BaseConfigParser

Descendant Classes

Attributes

Key Value Defined in
banner_start ['banner', 'vacant-message'] BaseSpaceConfigParser
banner_start ['banner', 'vacant-message'] BaseConfigParser
comment_chars ['!'] BaseSpaceConfigParser
comment_chars ['!'] BaseConfigParser

Methods

def __init__(self, config): BaseSpaceConfigParser

BaseSpaceConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self._indent_level = 0
        super(BaseSpaceConfigParser, self).__init__(config)

BaseConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config: The config text to parse.
        """
        self.config = config
        self._config: t.Optional[str] = None
        self._current_parents: t.Tuple[str, ...] = ()
        self.generator_config = (line for line in self.config_lines_only.splitlines())
        self.config_lines: t.List[ConfigLine] = []
        self.build_config_relationship()
def _build_banner(self, config_line): BaseSpaceConfigParser
    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None

        Raises:
            ValueError: When the parser is unable to identify the end of the Banner.
        """
        self._update_config_lines(config_line)
        self._current_parents += (config_line,)
        banner_config = []
        for line in self.generator_config:
            if not self.is_banner_end(line):
                banner_config.append(line)
            else:
                line = normalise_delimiter_caret_c(self.banner_end, line)
                banner_config.append(line)
                line = "\n".join(banner_config)
                if line.endswith("^C"):
                    banner, end, _ = line.rpartition("^C")
                    line = banner + end
                self._update_config_lines(line)
                self._current_parents = self._current_parents[:-1]
                try:
                    return next(self.generator_config)
                except StopIteration:
                    return None

        raise ValueError("Unable to parse banner end.")
def _build_nested_config(self, line): BaseSpaceConfigParser
    def _build_nested_config(self, line: str) -> t.Optional[str]:
        """Handle building child config sections.

        Args:
            line: A configuration line from the configuration text.

        Returns:
            The next top-level configuration line in the configuration text or None when the last line of configuration
            text is a nested configuration line.

        Raises:
            IndexError: When the number of parents does not match the expected deindent level.
        """
        self._update_config_lines(line)
        for line in self.generator_config:
            if not line[0].isspace():
                self._current_parents = ()
                self.indent_level = 0
                return line

            spaces = self.get_leading_space_count(line)
            if spaces == self.indent_level:
                pass
            elif spaces > self.indent_level:
                previous_config = self.config_lines[-1]
                self._current_parents += (previous_config.config_line,)
            else:
                self._current_parents = self._remove_parents(line, spaces)

            if spaces != self.indent_level:
                self.indent_level = spaces

            if self.is_banner_start(line):
                banner_line = self._build_banner(line)
                if banner_line is None or not banner_line[0].isspace():
                    self._current_parents = ()
                    self.indent_level = 0
                    return banner_line
                line = banner_line

            self._update_config_lines(line)
        return None
def _match_type_check(line, pattern, match_type): BaseSpaceConfigParser
    @staticmethod
    def _match_type_check(line: str, pattern: str, match_type: str) -> bool:
        """Checks pattern for exact match or regex."""
        if match_type == "exact" and line == pattern:
            return True
        if match_type == "startswith" and line.startswith(pattern):
            return True
        if match_type == "endswith" and line.endswith(pattern):
            return True
        if match_type == "regex" and re.match(pattern, line):
            return True
        return False
def _remove_parents(self, line, current_spaces): BaseSpaceConfigParser
    def _remove_parents(self, line: str, current_spaces: int) -> t.Tuple[str, ...]:
        """Remove parents from ``self._curent_parents`` based on indent levels.

        Args:
            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 = 1
        try:
            previous_parent = self._current_parents[-1]
            previous_indent = self.get_leading_space_count(previous_parent)
            while previous_indent > current_spaces:
                deindent_level += 1
                previous_parent = self._current_parents[-deindent_level]
                previous_indent = self.get_leading_space_count(previous_parent)
        except IndexError:
            raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n")
        parents = self._current_parents[:-deindent_level] or (self._current_parents[0],)
        return parents
def _update_config_lines(self, config_line): BaseSpaceConfigParser
    def _update_config_lines(self, config_line: str) -> None:
        """Add a ``ConfigLine`` object to ``self.config_lines``.

        Args:
            config_line: The current config line being evaluated.

        Returns:
            None
        """
        entry = ConfigLine(config_line, self._current_parents)
        self.config_lines.append(entry)
def build_config_relationship(self): BaseSpaceConfigParser

BaseSpaceConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        r"""Parse text tree of config lines and their parents.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
            >>> config = (
            ...     "interface Ethernet1/1\n"
            ...     "  vlan 10\n"
            ...     "  no shutdown\n"
            ...     "interface Ethernet1/2\n"
            ...     "  shutdown\n"
            ... )
            >>> config_tree = BaseSpaceConfigParser(config)
            >>> config_tree.build_config_relationship() == \
            ... [
            ...     ConfigLine(config_line='interface Ethernet1/1', parents=()),
            ...     ConfigLine(config_line='  vlan 10', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='  no shutdown', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='interface Ethernet1/2', parents=(),),
            ...     ConfigLine(config_line='  shutdown', parents=('interface Ethernet1/2',))
            ... ]
            True
        """
        for index, line in enumerate(self.generator_config):
            current_spaces = self.get_leading_space_count(line) if line[0].isspace() else 0
            if index == 0 and line[0].isspace():
                self._current_parents = self._remove_parents(line, current_spaces)
            elif not line[0].isspace():
                self._current_parents = ()
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore
            else:
                previous_config = self.config_lines[-1]
                self._current_parents = (previous_config.config_line,)
                self.indent_level = self.get_leading_space_count(line)
                if not self.is_banner_start(line):
                    line = self._build_nested_config(line)  # type: ignore
                else:
                    line = self._build_banner(line)  # type: ignore
                    if line is not None and line[0].isspace():
                        line = self._build_nested_config(line)  # type: ignore
                    else:
                        self._current_parents = ()

            if line is None:
                break
            elif self.is_banner_start(line):
                line = self._build_banner(line)  # type: ignore
                # line can potentially be another banner start therefore we do a secondary check.
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore

            self._update_config_lines(line)
        return self.config_lines

BaseConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        """Parse text tree of config lines and their parents."""
        raise NotImplementedError()
def find_all_children(self, pattern, match_type=exact): BaseSpaceConfigParser
    def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]:
        """Returns configuration part for a specific pattern not including parents.

        Args:
            pattern: pattern that describes parent.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith")
            >>> print(bgp_conf)
            ['router bgp 45000', '  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if (
                parents
                and self._match_type_check(parents, pattern, match_type)
                or self._match_type_check(cfg_line.config_line, pattern, match_type)
            ):
                config.append(cfg_line.config_line)
        return config
def find_children_w_parents(self, parent_pattern, child_pattern, match_type=exact): BaseSpaceConfigParser
    def find_children_w_parents(
        self, parent_pattern: str, child_pattern: str, match_type: str = "exact"
    ) -> t.List[str]:
        """Returns configuration part for a specific pattern including parents and children.

        Args:
            parent_pattern: pattern that describes parent.
            child_pattern: pattern that describes child.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern="  address-family", match_type="regex")
            >>> print(bgp_conf)
            ['  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        potential_parents = [
            elem.parents[0]
            for elem in self.build_config_relationship()
            if self._match_type_check(elem.config_line, child_pattern, match_type)
        ]
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if parents in potential_parents and self._match_type_check(
                parents,  # type: ignore[arg-type]
                parent_pattern,
                match_type,
            ):
                config.append(cfg_line.config_line)
        return config
def get_leading_space_count(config_line): BaseSpaceConfigParser
    @staticmethod
    def get_leading_space_count(config_line: str) -> int:
        r"""Determine how many spaces the ``config_line`` is indented.

        Args:
           config_line: A line of text in the config.

        Returns:
            The number of leading spaces.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''interface GigabitEthernet1\n description link to ISP'''
            >>> config_line = " description link to ISP"
            >>> indent_level = BaseSpaceConfigParser(config).get_leading_space_count(config_line)
            >>> indent_level
            1
            >>>
        """
        return len(config_line) - len(config_line.lstrip())
def is_banner_end(self, line): BaseSpaceConfigParser
    def is_banner_end(self, line: str) -> bool:
        """Determine if line ends the banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line ends banner, else False.
        """
        if self.banner_end in line:
            return True
        return False
def is_banner_start(self, line): BaseSpaceConfigParser
    def is_banner_start(self, line: str) -> bool:
        """Determine if the line starts a banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line starts banner, else False.
        """
        for banner_start in self.banner_start:
            if not line:
                return False
            if line.lstrip().startswith(banner_start):
                return True
        return False
def is_comment(self, line): BaseSpaceConfigParser
    def is_comment(self, line: str) -> bool:
        """Determine if line is a comment.

        Args:
            line: A config line from the device.

        Returns:
            True if line is a comment, else False.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> BaseSpaceConfigParser("interface Ethernet1/1").is_comment("interface Ethernet1/1")
            False
            >>> BaseSpaceConfigParser("!").is_comment("!")
            True
            >>>
        """
        for comment_char in self.comment_chars:
            if line.lstrip().startswith(comment_char):
                return True
        return False

NXOSConfigParser

from netutils.config.parser import NXOSConfigParser

Ancestors (MRO)

The Method Resolution Order is described below.

  1. NXOSConfigParser
  2. CiscoConfigParser
  3. BaseSpaceConfigParser
  4. BaseConfigParser

Descendant Classes

Attributes

Key Value Defined in
banner_start ['banner', 'vacant-message'] BaseSpaceConfigParser
banner_start ['banner', 'vacant-message'] BaseConfigParser
comment_chars ['!'] BaseSpaceConfigParser
comment_chars ['!'] BaseConfigParser
regex_banner re.compile('^banner\\s+\\S+\\s+(?P<banner_delimiter>\\S)') NXOSConfigParser
regex_banner re.compile('^(banner\\s+\\S+|\\s*vacant-message)\\s+(?P<banner_delimiter>\\^C|.)') CiscoConfigParser

Methods

def __init__(self, config): NXOSConfigParser

NXOSConfigParser

    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)

CiscoConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self._banner_end: t.Optional[str] = None
        super(CiscoConfigParser, self).__init__(config)

BaseSpaceConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self._indent_level = 0
        super(BaseSpaceConfigParser, self).__init__(config)

BaseConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config: The config text to parse.
        """
        self.config = config
        self._config: t.Optional[str] = None
        self._current_parents: t.Tuple[str, ...] = ()
        self.generator_config = (line for line in self.config_lines_only.splitlines())
        self.config_lines: t.List[ConfigLine] = []
        self.build_config_relationship()
def _build_banner(self, config_line): NXOSConfigParser

NXOSConfigParser

    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None when banner end is the end of the config text.

        Raises:
            ValueError: When the parser is unable to identify the end of the Banner.
        """
        config_line = normalise_delimiter_caret_c(self.banner_end, config_line)
        return super(NXOSConfigParser, self)._build_banner(config_line)

CiscoConfigParser

    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None when banner end is the end of the config text.

        Raises:
            ValueError: When the parser is unable to identify the End of the Banner.
        """
        if self.is_banner_one_line(config_line):
            self._update_config_lines(config_line)
            try:
                return next(self.generator_config)
            except StopIteration:
                return None
        return super(CiscoConfigParser, self)._build_banner(config_line)

BaseSpaceConfigParser

    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None

        Raises:
            ValueError: When the parser is unable to identify the end of the Banner.
        """
        self._update_config_lines(config_line)
        self._current_parents += (config_line,)
        banner_config = []
        for line in self.generator_config:
            if not self.is_banner_end(line):
                banner_config.append(line)
            else:
                line = normalise_delimiter_caret_c(self.banner_end, line)
                banner_config.append(line)
                line = "\n".join(banner_config)
                if line.endswith("^C"):
                    banner, end, _ = line.rpartition("^C")
                    line = banner + end
                self._update_config_lines(line)
                self._current_parents = self._current_parents[:-1]
                try:
                    return next(self.generator_config)
                except StopIteration:
                    return None

        raise ValueError("Unable to parse banner end.")
def _build_nested_config(self, line): BaseSpaceConfigParser
    def _build_nested_config(self, line: str) -> t.Optional[str]:
        """Handle building child config sections.

        Args:
            line: A configuration line from the configuration text.

        Returns:
            The next top-level configuration line in the configuration text or None when the last line of configuration
            text is a nested configuration line.

        Raises:
            IndexError: When the number of parents does not match the expected deindent level.
        """
        self._update_config_lines(line)
        for line in self.generator_config:
            if not line[0].isspace():
                self._current_parents = ()
                self.indent_level = 0
                return line

            spaces = self.get_leading_space_count(line)
            if spaces == self.indent_level:
                pass
            elif spaces > self.indent_level:
                previous_config = self.config_lines[-1]
                self._current_parents += (previous_config.config_line,)
            else:
                self._current_parents = self._remove_parents(line, spaces)

            if spaces != self.indent_level:
                self.indent_level = spaces

            if self.is_banner_start(line):
                banner_line = self._build_banner(line)
                if banner_line is None or not banner_line[0].isspace():
                    self._current_parents = ()
                    self.indent_level = 0
                    return banner_line
                line = banner_line

            self._update_config_lines(line)
        return None
def _match_type_check(line, pattern, match_type): BaseSpaceConfigParser
    @staticmethod
    def _match_type_check(line: str, pattern: str, match_type: str) -> bool:
        """Checks pattern for exact match or regex."""
        if match_type == "exact" and line == pattern:
            return True
        if match_type == "startswith" and line.startswith(pattern):
            return True
        if match_type == "endswith" and line.endswith(pattern):
            return True
        if match_type == "regex" and re.match(pattern, line):
            return True
        return False
def _remove_parents(self, line, current_spaces): BaseSpaceConfigParser
    def _remove_parents(self, line: str, current_spaces: int) -> t.Tuple[str, ...]:
        """Remove parents from ``self._curent_parents`` based on indent levels.

        Args:
            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 = 1
        try:
            previous_parent = self._current_parents[-1]
            previous_indent = self.get_leading_space_count(previous_parent)
            while previous_indent > current_spaces:
                deindent_level += 1
                previous_parent = self._current_parents[-deindent_level]
                previous_indent = self.get_leading_space_count(previous_parent)
        except IndexError:
            raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n")
        parents = self._current_parents[:-deindent_level] or (self._current_parents[0],)
        return parents
def _update_config_lines(self, config_line): BaseSpaceConfigParser
    def _update_config_lines(self, config_line: str) -> None:
        """Add a ``ConfigLine`` object to ``self.config_lines``.

        Args:
            config_line: The current config line being evaluated.

        Returns:
            None
        """
        entry = ConfigLine(config_line, self._current_parents)
        self.config_lines.append(entry)
def build_config_relationship(self): BaseSpaceConfigParser

BaseSpaceConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        r"""Parse text tree of config lines and their parents.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
            >>> config = (
            ...     "interface Ethernet1/1\n"
            ...     "  vlan 10\n"
            ...     "  no shutdown\n"
            ...     "interface Ethernet1/2\n"
            ...     "  shutdown\n"
            ... )
            >>> config_tree = BaseSpaceConfigParser(config)
            >>> config_tree.build_config_relationship() == \
            ... [
            ...     ConfigLine(config_line='interface Ethernet1/1', parents=()),
            ...     ConfigLine(config_line='  vlan 10', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='  no shutdown', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='interface Ethernet1/2', parents=(),),
            ...     ConfigLine(config_line='  shutdown', parents=('interface Ethernet1/2',))
            ... ]
            True
        """
        for index, line in enumerate(self.generator_config):
            current_spaces = self.get_leading_space_count(line) if line[0].isspace() else 0
            if index == 0 and line[0].isspace():
                self._current_parents = self._remove_parents(line, current_spaces)
            elif not line[0].isspace():
                self._current_parents = ()
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore
            else:
                previous_config = self.config_lines[-1]
                self._current_parents = (previous_config.config_line,)
                self.indent_level = self.get_leading_space_count(line)
                if not self.is_banner_start(line):
                    line = self._build_nested_config(line)  # type: ignore
                else:
                    line = self._build_banner(line)  # type: ignore
                    if line is not None and line[0].isspace():
                        line = self._build_nested_config(line)  # type: ignore
                    else:
                        self._current_parents = ()

            if line is None:
                break
            elif self.is_banner_start(line):
                line = self._build_banner(line)  # type: ignore
                # line can potentially be another banner start therefore we do a secondary check.
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore

            self._update_config_lines(line)
        return self.config_lines

BaseConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        """Parse text tree of config lines and their parents."""
        raise NotImplementedError()
def find_all_children(self, pattern, match_type=exact): BaseSpaceConfigParser
    def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]:
        """Returns configuration part for a specific pattern not including parents.

        Args:
            pattern: pattern that describes parent.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith")
            >>> print(bgp_conf)
            ['router bgp 45000', '  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if (
                parents
                and self._match_type_check(parents, pattern, match_type)
                or self._match_type_check(cfg_line.config_line, pattern, match_type)
            ):
                config.append(cfg_line.config_line)
        return config
def find_children_w_parents(self, parent_pattern, child_pattern, match_type=exact): BaseSpaceConfigParser
    def find_children_w_parents(
        self, parent_pattern: str, child_pattern: str, match_type: str = "exact"
    ) -> t.List[str]:
        """Returns configuration part for a specific pattern including parents and children.

        Args:
            parent_pattern: pattern that describes parent.
            child_pattern: pattern that describes child.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern="  address-family", match_type="regex")
            >>> print(bgp_conf)
            ['  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        potential_parents = [
            elem.parents[0]
            for elem in self.build_config_relationship()
            if self._match_type_check(elem.config_line, child_pattern, match_type)
        ]
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if parents in potential_parents and self._match_type_check(
                parents,  # type: ignore[arg-type]
                parent_pattern,
                match_type,
            ):
                config.append(cfg_line.config_line)
        return config
def get_leading_space_count(config_line): BaseSpaceConfigParser
    @staticmethod
    def get_leading_space_count(config_line: str) -> int:
        r"""Determine how many spaces the ``config_line`` is indented.

        Args:
           config_line: A line of text in the config.

        Returns:
            The number of leading spaces.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''interface GigabitEthernet1\n description link to ISP'''
            >>> config_line = " description link to ISP"
            >>> indent_level = BaseSpaceConfigParser(config).get_leading_space_count(config_line)
            >>> indent_level
            1
            >>>
        """
        return len(config_line) - len(config_line.lstrip())
def is_banner_end(self, line): BaseSpaceConfigParser
    def is_banner_end(self, line: str) -> bool:
        """Determine if line ends the banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line ends banner, else False.
        """
        if self.banner_end in line:
            return True
        return False
def is_banner_one_line(config_line): CiscoConfigParser
    @staticmethod
    def is_banner_one_line(config_line: str) -> bool:
        """Determine if all banner config is on one line."""
        _, delimeter, banner = config_line.partition("^C")
        # if the banner is the delimeter is a single line empty banner. e.g banner motd ^C^C which ios allows.
        if banner == "^C":
            return True
        # Based on NXOS configs, the banner delimeter is ignored until another char is used
        banner_config_start = banner.lstrip(delimeter)
        if delimeter not in banner_config_start:
            return False
        return True
def is_banner_start(self, line): CiscoConfigParser

CiscoConfigParser

    def is_banner_start(self, line: str) -> bool:
        """Determine if the line starts a banner config."""
        state = super(CiscoConfigParser, self).is_banner_start(line)
        if state:
            self.banner_end = line
        return state

BaseSpaceConfigParser

    def is_banner_start(self, line: str) -> bool:
        """Determine if the line starts a banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line starts banner, else False.
        """
        for banner_start in self.banner_start:
            if not line:
                return False
            if line.lstrip().startswith(banner_start):
                return True
        return False
def is_comment(self, line): BaseSpaceConfigParser
    def is_comment(self, line: str) -> bool:
        """Determine if line is a comment.

        Args:
            line: A config line from the device.

        Returns:
            True if line is a comment, else False.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> BaseSpaceConfigParser("interface Ethernet1/1").is_comment("interface Ethernet1/1")
            False
            >>> BaseSpaceConfigParser("!").is_comment("!")
            True
            >>>
        """
        for comment_char in self.comment_chars:
            if line.lstrip().startswith(comment_char):
                return True
        return False

NetironConfigParser

from netutils.config.parser import NetironConfigParser

Ancestors (MRO)

The Method Resolution Order is described below.

  1. NetironConfigParser
  2. BaseSpaceConfigParser
  3. BaseConfigParser

Descendant Classes

Attributes

Key Value Defined in
banner_start ['banner', 'vacant-message'] BaseSpaceConfigParser
banner_start ['banner', 'vacant-message'] BaseConfigParser
comment_chars ['!'] BaseSpaceConfigParser
comment_chars ['!'] BaseConfigParser

Methods

def __init__(self, config): BaseSpaceConfigParser

BaseSpaceConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self._indent_level = 0
        super(BaseSpaceConfigParser, self).__init__(config)

BaseConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config: The config text to parse.
        """
        self.config = config
        self._config: t.Optional[str] = None
        self._current_parents: t.Tuple[str, ...] = ()
        self.generator_config = (line for line in self.config_lines_only.splitlines())
        self.config_lines: t.List[ConfigLine] = []
        self.build_config_relationship()
def _build_banner(self, config_line): BaseSpaceConfigParser
    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None

        Raises:
            ValueError: When the parser is unable to identify the end of the Banner.
        """
        self._update_config_lines(config_line)
        self._current_parents += (config_line,)
        banner_config = []
        for line in self.generator_config:
            if not self.is_banner_end(line):
                banner_config.append(line)
            else:
                line = normalise_delimiter_caret_c(self.banner_end, line)
                banner_config.append(line)
                line = "\n".join(banner_config)
                if line.endswith("^C"):
                    banner, end, _ = line.rpartition("^C")
                    line = banner + end
                self._update_config_lines(line)
                self._current_parents = self._current_parents[:-1]
                try:
                    return next(self.generator_config)
                except StopIteration:
                    return None

        raise ValueError("Unable to parse banner end.")
def _build_nested_config(self, line): BaseSpaceConfigParser
    def _build_nested_config(self, line: str) -> t.Optional[str]:
        """Handle building child config sections.

        Args:
            line: A configuration line from the configuration text.

        Returns:
            The next top-level configuration line in the configuration text or None when the last line of configuration
            text is a nested configuration line.

        Raises:
            IndexError: When the number of parents does not match the expected deindent level.
        """
        self._update_config_lines(line)
        for line in self.generator_config:
            if not line[0].isspace():
                self._current_parents = ()
                self.indent_level = 0
                return line

            spaces = self.get_leading_space_count(line)
            if spaces == self.indent_level:
                pass
            elif spaces > self.indent_level:
                previous_config = self.config_lines[-1]
                self._current_parents += (previous_config.config_line,)
            else:
                self._current_parents = self._remove_parents(line, spaces)

            if spaces != self.indent_level:
                self.indent_level = spaces

            if self.is_banner_start(line):
                banner_line = self._build_banner(line)
                if banner_line is None or not banner_line[0].isspace():
                    self._current_parents = ()
                    self.indent_level = 0
                    return banner_line
                line = banner_line

            self._update_config_lines(line)
        return None
def _match_type_check(line, pattern, match_type): BaseSpaceConfigParser
    @staticmethod
    def _match_type_check(line: str, pattern: str, match_type: str) -> bool:
        """Checks pattern for exact match or regex."""
        if match_type == "exact" and line == pattern:
            return True
        if match_type == "startswith" and line.startswith(pattern):
            return True
        if match_type == "endswith" and line.endswith(pattern):
            return True
        if match_type == "regex" and re.match(pattern, line):
            return True
        return False
def _remove_parents(self, line, current_spaces): BaseSpaceConfigParser
    def _remove_parents(self, line: str, current_spaces: int) -> t.Tuple[str, ...]:
        """Remove parents from ``self._curent_parents`` based on indent levels.

        Args:
            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 = 1
        try:
            previous_parent = self._current_parents[-1]
            previous_indent = self.get_leading_space_count(previous_parent)
            while previous_indent > current_spaces:
                deindent_level += 1
                previous_parent = self._current_parents[-deindent_level]
                previous_indent = self.get_leading_space_count(previous_parent)
        except IndexError:
            raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n")
        parents = self._current_parents[:-deindent_level] or (self._current_parents[0],)
        return parents
def _update_config_lines(self, config_line): BaseSpaceConfigParser
    def _update_config_lines(self, config_line: str) -> None:
        """Add a ``ConfigLine`` object to ``self.config_lines``.

        Args:
            config_line: The current config line being evaluated.

        Returns:
            None
        """
        entry = ConfigLine(config_line, self._current_parents)
        self.config_lines.append(entry)
def build_config_relationship(self): BaseSpaceConfigParser

BaseSpaceConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        r"""Parse text tree of config lines and their parents.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
            >>> config = (
            ...     "interface Ethernet1/1\n"
            ...     "  vlan 10\n"
            ...     "  no shutdown\n"
            ...     "interface Ethernet1/2\n"
            ...     "  shutdown\n"
            ... )
            >>> config_tree = BaseSpaceConfigParser(config)
            >>> config_tree.build_config_relationship() == \
            ... [
            ...     ConfigLine(config_line='interface Ethernet1/1', parents=()),
            ...     ConfigLine(config_line='  vlan 10', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='  no shutdown', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='interface Ethernet1/2', parents=(),),
            ...     ConfigLine(config_line='  shutdown', parents=('interface Ethernet1/2',))
            ... ]
            True
        """
        for index, line in enumerate(self.generator_config):
            current_spaces = self.get_leading_space_count(line) if line[0].isspace() else 0
            if index == 0 and line[0].isspace():
                self._current_parents = self._remove_parents(line, current_spaces)
            elif not line[0].isspace():
                self._current_parents = ()
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore
            else:
                previous_config = self.config_lines[-1]
                self._current_parents = (previous_config.config_line,)
                self.indent_level = self.get_leading_space_count(line)
                if not self.is_banner_start(line):
                    line = self._build_nested_config(line)  # type: ignore
                else:
                    line = self._build_banner(line)  # type: ignore
                    if line is not None and line[0].isspace():
                        line = self._build_nested_config(line)  # type: ignore
                    else:
                        self._current_parents = ()

            if line is None:
                break
            elif self.is_banner_start(line):
                line = self._build_banner(line)  # type: ignore
                # line can potentially be another banner start therefore we do a secondary check.
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore

            self._update_config_lines(line)
        return self.config_lines

BaseConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        """Parse text tree of config lines and their parents."""
        raise NotImplementedError()
def find_all_children(self, pattern, match_type=exact): BaseSpaceConfigParser
    def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]:
        """Returns configuration part for a specific pattern not including parents.

        Args:
            pattern: pattern that describes parent.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith")
            >>> print(bgp_conf)
            ['router bgp 45000', '  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if (
                parents
                and self._match_type_check(parents, pattern, match_type)
                or self._match_type_check(cfg_line.config_line, pattern, match_type)
            ):
                config.append(cfg_line.config_line)
        return config
def find_children_w_parents(self, parent_pattern, child_pattern, match_type=exact): BaseSpaceConfigParser
    def find_children_w_parents(
        self, parent_pattern: str, child_pattern: str, match_type: str = "exact"
    ) -> t.List[str]:
        """Returns configuration part for a specific pattern including parents and children.

        Args:
            parent_pattern: pattern that describes parent.
            child_pattern: pattern that describes child.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern="  address-family", match_type="regex")
            >>> print(bgp_conf)
            ['  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        potential_parents = [
            elem.parents[0]
            for elem in self.build_config_relationship()
            if self._match_type_check(elem.config_line, child_pattern, match_type)
        ]
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if parents in potential_parents and self._match_type_check(
                parents,  # type: ignore[arg-type]
                parent_pattern,
                match_type,
            ):
                config.append(cfg_line.config_line)
        return config
def get_leading_space_count(config_line): BaseSpaceConfigParser
    @staticmethod
    def get_leading_space_count(config_line: str) -> int:
        r"""Determine how many spaces the ``config_line`` is indented.

        Args:
           config_line: A line of text in the config.

        Returns:
            The number of leading spaces.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''interface GigabitEthernet1\n description link to ISP'''
            >>> config_line = " description link to ISP"
            >>> indent_level = BaseSpaceConfigParser(config).get_leading_space_count(config_line)
            >>> indent_level
            1
            >>>
        """
        return len(config_line) - len(config_line.lstrip())
def is_banner_end(self, line): BaseSpaceConfigParser
    def is_banner_end(self, line: str) -> bool:
        """Determine if line ends the banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line ends banner, else False.
        """
        if self.banner_end in line:
            return True
        return False
def is_banner_start(self, line): BaseSpaceConfigParser
    def is_banner_start(self, line: str) -> bool:
        """Determine if the line starts a banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line starts banner, else False.
        """
        for banner_start in self.banner_start:
            if not line:
                return False
            if line.lstrip().startswith(banner_start):
                return True
        return False
def is_comment(self, line): BaseSpaceConfigParser
    def is_comment(self, line: str) -> bool:
        """Determine if line is a comment.

        Args:
            line: A config line from the device.

        Returns:
            True if line is a comment, else False.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> BaseSpaceConfigParser("interface Ethernet1/1").is_comment("interface Ethernet1/1")
            False
            >>> BaseSpaceConfigParser("!").is_comment("!")
            True
            >>>
        """
        for comment_char in self.comment_chars:
            if line.lstrip().startswith(comment_char):
                return True
        return False

NetscalerConfigParser

from netutils.config.parser import NetscalerConfigParser

Ancestors (MRO)

The Method Resolution Order is described below.

  1. NetscalerConfigParser
  2. BaseSpaceConfigParser
  3. BaseConfigParser

Descendant Classes

Attributes

Key Value Defined in
banner_start ['banner', 'vacant-message'] BaseSpaceConfigParser
banner_start ['banner', 'vacant-message'] BaseConfigParser
comment_chars ['!'] BaseSpaceConfigParser
comment_chars ['!'] BaseConfigParser

Methods

def __init__(self, config): BaseSpaceConfigParser

BaseSpaceConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self._indent_level = 0
        super(BaseSpaceConfigParser, self).__init__(config)

BaseConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config: The config text to parse.
        """
        self.config = config
        self._config: t.Optional[str] = None
        self._current_parents: t.Tuple[str, ...] = ()
        self.generator_config = (line for line in self.config_lines_only.splitlines())
        self.config_lines: t.List[ConfigLine] = []
        self.build_config_relationship()
def _build_banner(self, config_line): BaseSpaceConfigParser
    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None

        Raises:
            ValueError: When the parser is unable to identify the end of the Banner.
        """
        self._update_config_lines(config_line)
        self._current_parents += (config_line,)
        banner_config = []
        for line in self.generator_config:
            if not self.is_banner_end(line):
                banner_config.append(line)
            else:
                line = normalise_delimiter_caret_c(self.banner_end, line)
                banner_config.append(line)
                line = "\n".join(banner_config)
                if line.endswith("^C"):
                    banner, end, _ = line.rpartition("^C")
                    line = banner + end
                self._update_config_lines(line)
                self._current_parents = self._current_parents[:-1]
                try:
                    return next(self.generator_config)
                except StopIteration:
                    return None

        raise ValueError("Unable to parse banner end.")
def _build_nested_config(self, line): BaseSpaceConfigParser
    def _build_nested_config(self, line: str) -> t.Optional[str]:
        """Handle building child config sections.

        Args:
            line: A configuration line from the configuration text.

        Returns:
            The next top-level configuration line in the configuration text or None when the last line of configuration
            text is a nested configuration line.

        Raises:
            IndexError: When the number of parents does not match the expected deindent level.
        """
        self._update_config_lines(line)
        for line in self.generator_config:
            if not line[0].isspace():
                self._current_parents = ()
                self.indent_level = 0
                return line

            spaces = self.get_leading_space_count(line)
            if spaces == self.indent_level:
                pass
            elif spaces > self.indent_level:
                previous_config = self.config_lines[-1]
                self._current_parents += (previous_config.config_line,)
            else:
                self._current_parents = self._remove_parents(line, spaces)

            if spaces != self.indent_level:
                self.indent_level = spaces

            if self.is_banner_start(line):
                banner_line = self._build_banner(line)
                if banner_line is None or not banner_line[0].isspace():
                    self._current_parents = ()
                    self.indent_level = 0
                    return banner_line
                line = banner_line

            self._update_config_lines(line)
        return None
def _match_type_check(line, pattern, match_type): BaseSpaceConfigParser
    @staticmethod
    def _match_type_check(line: str, pattern: str, match_type: str) -> bool:
        """Checks pattern for exact match or regex."""
        if match_type == "exact" and line == pattern:
            return True
        if match_type == "startswith" and line.startswith(pattern):
            return True
        if match_type == "endswith" and line.endswith(pattern):
            return True
        if match_type == "regex" and re.match(pattern, line):
            return True
        return False
def _remove_parents(self, line, current_spaces): BaseSpaceConfigParser
    def _remove_parents(self, line: str, current_spaces: int) -> t.Tuple[str, ...]:
        """Remove parents from ``self._curent_parents`` based on indent levels.

        Args:
            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 = 1
        try:
            previous_parent = self._current_parents[-1]
            previous_indent = self.get_leading_space_count(previous_parent)
            while previous_indent > current_spaces:
                deindent_level += 1
                previous_parent = self._current_parents[-deindent_level]
                previous_indent = self.get_leading_space_count(previous_parent)
        except IndexError:
            raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n")
        parents = self._current_parents[:-deindent_level] or (self._current_parents[0],)
        return parents
def _update_config_lines(self, config_line): BaseSpaceConfigParser
    def _update_config_lines(self, config_line: str) -> None:
        """Add a ``ConfigLine`` object to ``self.config_lines``.

        Args:
            config_line: The current config line being evaluated.

        Returns:
            None
        """
        entry = ConfigLine(config_line, self._current_parents)
        self.config_lines.append(entry)
def build_config_relationship(self): BaseSpaceConfigParser

BaseSpaceConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        r"""Parse text tree of config lines and their parents.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
            >>> config = (
            ...     "interface Ethernet1/1\n"
            ...     "  vlan 10\n"
            ...     "  no shutdown\n"
            ...     "interface Ethernet1/2\n"
            ...     "  shutdown\n"
            ... )
            >>> config_tree = BaseSpaceConfigParser(config)
            >>> config_tree.build_config_relationship() == \
            ... [
            ...     ConfigLine(config_line='interface Ethernet1/1', parents=()),
            ...     ConfigLine(config_line='  vlan 10', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='  no shutdown', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='interface Ethernet1/2', parents=(),),
            ...     ConfigLine(config_line='  shutdown', parents=('interface Ethernet1/2',))
            ... ]
            True
        """
        for index, line in enumerate(self.generator_config):
            current_spaces = self.get_leading_space_count(line) if line[0].isspace() else 0
            if index == 0 and line[0].isspace():
                self._current_parents = self._remove_parents(line, current_spaces)
            elif not line[0].isspace():
                self._current_parents = ()
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore
            else:
                previous_config = self.config_lines[-1]
                self._current_parents = (previous_config.config_line,)
                self.indent_level = self.get_leading_space_count(line)
                if not self.is_banner_start(line):
                    line = self._build_nested_config(line)  # type: ignore
                else:
                    line = self._build_banner(line)  # type: ignore
                    if line is not None and line[0].isspace():
                        line = self._build_nested_config(line)  # type: ignore
                    else:
                        self._current_parents = ()

            if line is None:
                break
            elif self.is_banner_start(line):
                line = self._build_banner(line)  # type: ignore
                # line can potentially be another banner start therefore we do a secondary check.
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore

            self._update_config_lines(line)
        return self.config_lines

BaseConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        """Parse text tree of config lines and their parents."""
        raise NotImplementedError()
def find_all_children(self, pattern, match_type=exact): BaseSpaceConfigParser
    def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]:
        """Returns configuration part for a specific pattern not including parents.

        Args:
            pattern: pattern that describes parent.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith")
            >>> print(bgp_conf)
            ['router bgp 45000', '  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if (
                parents
                and self._match_type_check(parents, pattern, match_type)
                or self._match_type_check(cfg_line.config_line, pattern, match_type)
            ):
                config.append(cfg_line.config_line)
        return config
def find_children_w_parents(self, parent_pattern, child_pattern, match_type=exact): BaseSpaceConfigParser
    def find_children_w_parents(
        self, parent_pattern: str, child_pattern: str, match_type: str = "exact"
    ) -> t.List[str]:
        """Returns configuration part for a specific pattern including parents and children.

        Args:
            parent_pattern: pattern that describes parent.
            child_pattern: pattern that describes child.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern="  address-family", match_type="regex")
            >>> print(bgp_conf)
            ['  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        potential_parents = [
            elem.parents[0]
            for elem in self.build_config_relationship()
            if self._match_type_check(elem.config_line, child_pattern, match_type)
        ]
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if parents in potential_parents and self._match_type_check(
                parents,  # type: ignore[arg-type]
                parent_pattern,
                match_type,
            ):
                config.append(cfg_line.config_line)
        return config
def get_leading_space_count(config_line): BaseSpaceConfigParser
    @staticmethod
    def get_leading_space_count(config_line: str) -> int:
        r"""Determine how many spaces the ``config_line`` is indented.

        Args:
           config_line: A line of text in the config.

        Returns:
            The number of leading spaces.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''interface GigabitEthernet1\n description link to ISP'''
            >>> config_line = " description link to ISP"
            >>> indent_level = BaseSpaceConfigParser(config).get_leading_space_count(config_line)
            >>> indent_level
            1
            >>>
        """
        return len(config_line) - len(config_line.lstrip())
def is_banner_end(self, line): BaseSpaceConfigParser
    def is_banner_end(self, line: str) -> bool:
        """Determine if line ends the banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line ends banner, else False.
        """
        if self.banner_end in line:
            return True
        return False
def is_banner_start(self, line): BaseSpaceConfigParser
    def is_banner_start(self, line: str) -> bool:
        """Determine if the line starts a banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line starts banner, else False.
        """
        for banner_start in self.banner_start:
            if not line:
                return False
            if line.lstrip().startswith(banner_start):
                return True
        return False
def is_comment(self, line): BaseSpaceConfigParser
    def is_comment(self, line: str) -> bool:
        """Determine if line is a comment.

        Args:
            line: A config line from the device.

        Returns:
            True if line is a comment, else False.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> BaseSpaceConfigParser("interface Ethernet1/1").is_comment("interface Ethernet1/1")
            False
            >>> BaseSpaceConfigParser("!").is_comment("!")
            True
            >>>
        """
        for comment_char in self.comment_chars:
            if line.lstrip().startswith(comment_char):
                return True
        return False

NokiaConfigParser

from netutils.config.parser import NokiaConfigParser

Ancestors (MRO)

The Method Resolution Order is described below.

  1. NokiaConfigParser
  2. BaseSpaceConfigParser
  3. BaseConfigParser

Descendant Classes

Attributes

Key Value Defined in
banner_start ['banner', 'vacant-message'] BaseSpaceConfigParser
banner_start ['banner', 'vacant-message'] BaseConfigParser
comment_chars ['!'] BaseSpaceConfigParser
comment_chars ['!'] BaseConfigParser

Methods

def __init__(self, config): BaseSpaceConfigParser

BaseSpaceConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self._indent_level = 0
        super(BaseSpaceConfigParser, self).__init__(config)

BaseConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config: The config text to parse.
        """
        self.config = config
        self._config: t.Optional[str] = None
        self._current_parents: t.Tuple[str, ...] = ()
        self.generator_config = (line for line in self.config_lines_only.splitlines())
        self.config_lines: t.List[ConfigLine] = []
        self.build_config_relationship()
def _build_banner(self, config_line): BaseSpaceConfigParser
    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None

        Raises:
            ValueError: When the parser is unable to identify the end of the Banner.
        """
        self._update_config_lines(config_line)
        self._current_parents += (config_line,)
        banner_config = []
        for line in self.generator_config:
            if not self.is_banner_end(line):
                banner_config.append(line)
            else:
                line = normalise_delimiter_caret_c(self.banner_end, line)
                banner_config.append(line)
                line = "\n".join(banner_config)
                if line.endswith("^C"):
                    banner, end, _ = line.rpartition("^C")
                    line = banner + end
                self._update_config_lines(line)
                self._current_parents = self._current_parents[:-1]
                try:
                    return next(self.generator_config)
                except StopIteration:
                    return None

        raise ValueError("Unable to parse banner end.")
def _build_nested_config(self, line): BaseSpaceConfigParser
    def _build_nested_config(self, line: str) -> t.Optional[str]:
        """Handle building child config sections.

        Args:
            line: A configuration line from the configuration text.

        Returns:
            The next top-level configuration line in the configuration text or None when the last line of configuration
            text is a nested configuration line.

        Raises:
            IndexError: When the number of parents does not match the expected deindent level.
        """
        self._update_config_lines(line)
        for line in self.generator_config:
            if not line[0].isspace():
                self._current_parents = ()
                self.indent_level = 0
                return line

            spaces = self.get_leading_space_count(line)
            if spaces == self.indent_level:
                pass
            elif spaces > self.indent_level:
                previous_config = self.config_lines[-1]
                self._current_parents += (previous_config.config_line,)
            else:
                self._current_parents = self._remove_parents(line, spaces)

            if spaces != self.indent_level:
                self.indent_level = spaces

            if self.is_banner_start(line):
                banner_line = self._build_banner(line)
                if banner_line is None or not banner_line[0].isspace():
                    self._current_parents = ()
                    self.indent_level = 0
                    return banner_line
                line = banner_line

            self._update_config_lines(line)
        return None
def _get_section_title(self, line): NokiaConfigParser
    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)
        if section_title:
            return section_title.group("section_name")
        return False
def _is_section_title(self, line): NokiaConfigParser
    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.
        """
        if re.match(r"^echo\s\".+\"", string=line):
            return True
        return False
def _match_type_check(line, pattern, match_type): BaseSpaceConfigParser
    @staticmethod
    def _match_type_check(line: str, pattern: str, match_type: str) -> bool:
        """Checks pattern for exact match or regex."""
        if match_type == "exact" and line == pattern:
            return True
        if match_type == "startswith" and line.startswith(pattern):
            return True
        if match_type == "endswith" and line.endswith(pattern):
            return True
        if match_type == "regex" and re.match(pattern, line):
            return True
        return False
def _remove_parents(self, line, current_spaces): BaseSpaceConfigParser
    def _remove_parents(self, line: str, current_spaces: int) -> t.Tuple[str, ...]:
        """Remove parents from ``self._curent_parents`` based on indent levels.

        Args:
            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 = 1
        try:
            previous_parent = self._current_parents[-1]
            previous_indent = self.get_leading_space_count(previous_parent)
            while previous_indent > current_spaces:
                deindent_level += 1
                previous_parent = self._current_parents[-deindent_level]
                previous_indent = self.get_leading_space_count(previous_parent)
        except IndexError:
            raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n")
        parents = self._current_parents[:-deindent_level] or (self._current_parents[0],)
        return parents
def _update_config_lines(self, config_line): BaseSpaceConfigParser
    def _update_config_lines(self, config_line: str) -> None:
        """Add a ``ConfigLine`` object to ``self.config_lines``.

        Args:
            config_line: The current config line being evaluated.

        Returns:
            None
        """
        entry = ConfigLine(config_line, self._current_parents)
        self.config_lines.append(entry)
def build_config_relationship(self): BaseSpaceConfigParser

BaseSpaceConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        r"""Parse text tree of config lines and their parents.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
            >>> config = (
            ...     "interface Ethernet1/1\n"
            ...     "  vlan 10\n"
            ...     "  no shutdown\n"
            ...     "interface Ethernet1/2\n"
            ...     "  shutdown\n"
            ... )
            >>> config_tree = BaseSpaceConfigParser(config)
            >>> config_tree.build_config_relationship() == \
            ... [
            ...     ConfigLine(config_line='interface Ethernet1/1', parents=()),
            ...     ConfigLine(config_line='  vlan 10', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='  no shutdown', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='interface Ethernet1/2', parents=(),),
            ...     ConfigLine(config_line='  shutdown', parents=('interface Ethernet1/2',))
            ... ]
            True
        """
        for index, line in enumerate(self.generator_config):
            current_spaces = self.get_leading_space_count(line) if line[0].isspace() else 0
            if index == 0 and line[0].isspace():
                self._current_parents = self._remove_parents(line, current_spaces)
            elif not line[0].isspace():
                self._current_parents = ()
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore
            else:
                previous_config = self.config_lines[-1]
                self._current_parents = (previous_config.config_line,)
                self.indent_level = self.get_leading_space_count(line)
                if not self.is_banner_start(line):
                    line = self._build_nested_config(line)  # type: ignore
                else:
                    line = self._build_banner(line)  # type: ignore
                    if line is not None and line[0].isspace():
                        line = self._build_nested_config(line)  # type: ignore
                    else:
                        self._current_parents = ()

            if line is None:
                break
            elif self.is_banner_start(line):
                line = self._build_banner(line)  # type: ignore
                # line can potentially be another banner start therefore we do a secondary check.
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore

            self._update_config_lines(line)
        return self.config_lines

BaseConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        """Parse text tree of config lines and their parents."""
        raise NotImplementedError()
def find_all_children(self, pattern, match_type=exact): BaseSpaceConfigParser
    def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]:
        """Returns configuration part for a specific pattern not including parents.

        Args:
            pattern: pattern that describes parent.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith")
            >>> print(bgp_conf)
            ['router bgp 45000', '  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if (
                parents
                and self._match_type_check(parents, pattern, match_type)
                or self._match_type_check(cfg_line.config_line, pattern, match_type)
            ):
                config.append(cfg_line.config_line)
        return config
def find_children_w_parents(self, parent_pattern, child_pattern, match_type=exact): BaseSpaceConfigParser
    def find_children_w_parents(
        self, parent_pattern: str, child_pattern: str, match_type: str = "exact"
    ) -> t.List[str]:
        """Returns configuration part for a specific pattern including parents and children.

        Args:
            parent_pattern: pattern that describes parent.
            child_pattern: pattern that describes child.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern="  address-family", match_type="regex")
            >>> print(bgp_conf)
            ['  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        potential_parents = [
            elem.parents[0]
            for elem in self.build_config_relationship()
            if self._match_type_check(elem.config_line, child_pattern, match_type)
        ]
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if parents in potential_parents and self._match_type_check(
                parents,  # type: ignore[arg-type]
                parent_pattern,
                match_type,
            ):
                config.append(cfg_line.config_line)
        return config
def get_leading_space_count(config_line): BaseSpaceConfigParser
    @staticmethod
    def get_leading_space_count(config_line: str) -> int:
        r"""Determine how many spaces the ``config_line`` is indented.

        Args:
           config_line: A line of text in the config.

        Returns:
            The number of leading spaces.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''interface GigabitEthernet1\n description link to ISP'''
            >>> config_line = " description link to ISP"
            >>> indent_level = BaseSpaceConfigParser(config).get_leading_space_count(config_line)
            >>> indent_level
            1
            >>>
        """
        return len(config_line) - len(config_line.lstrip())
def is_banner_end(self, line): BaseSpaceConfigParser
    def is_banner_end(self, line: str) -> bool:
        """Determine if line ends the banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line ends banner, else False.
        """
        if self.banner_end in line:
            return True
        return False
def is_banner_start(self, line): BaseSpaceConfigParser
    def is_banner_start(self, line: str) -> bool:
        """Determine if the line starts a banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line starts banner, else False.
        """
        for banner_start in self.banner_start:
            if not line:
                return False
            if line.lstrip().startswith(banner_start):
                return True
        return False
def is_comment(self, line): BaseSpaceConfigParser
    def is_comment(self, line: str) -> bool:
        """Determine if line is a comment.

        Args:
            line: A config line from the device.

        Returns:
            True if line is a comment, else False.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> BaseSpaceConfigParser("interface Ethernet1/1").is_comment("interface Ethernet1/1")
            False
            >>> BaseSpaceConfigParser("!").is_comment("!")
            True
            >>>
        """
        for comment_char in self.comment_chars:
            if line.lstrip().startswith(comment_char):
                return True
        return False

NvidiaOnyxConfigParser

from netutils.config.parser import NvidiaOnyxConfigParser

Ancestors (MRO)

The Method Resolution Order is described below.

  1. NvidiaOnyxConfigParser
  2. BaseConfigParser

Descendant Classes

Attributes

Key Value Defined in
banner_start ['banner', 'vacant-message'] BaseConfigParser
comment_char '#' NvidiaOnyxConfigParser
comment_chars ['!'] BaseConfigParser
section_char '##' NvidiaOnyxConfigParser

Methods

def __init__(self, config): NvidiaOnyxConfigParser

NvidiaOnyxConfigParser

    def __init__(self, config: str):  # pylint: disable=super-init-not-called
        """Create ConfigParser Object.

        Args:
            config: The config text to parse.
        """
        self.config = config
        self._config: t.Optional[str] = None
        self._current_parents: t.Tuple[str, ...] = ()
        self.generator_config = (line for line in (self._config_lines_only()))
        self.config_lines: t.List[ConfigLine] = []
        self.build_config_relationship()

BaseConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config: The config text to parse.
        """
        self.config = config
        self._config: t.Optional[str] = None
        self._current_parents: t.Tuple[str, ...] = ()
        self.generator_config = (line for line in self.config_lines_only.splitlines())
        self.config_lines: t.List[ConfigLine] = []
        self.build_config_relationship()
def _config_lines_only(self): NvidiaOnyxConfigParser
    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 = []
        for line in self.config.splitlines():
            if line.startswith(self.section_char):
                continue
            stripped = line.strip()
            if re.match(r"banner \w+ (?!\".+\")", stripped):
                banner_text += line.lstrip()
            elif banner_text and not stripped.endswith('"'):
                banner_text += "\n" + line
            elif banner_text and stripped.endswith('"'):
                banner_text += "\n" + line
                config_lines.append(banner_text)
                banner_text = ""
            elif stripped:
                config_lines.append(stripped)

        return config_lines
def build_config_relationship(self): NvidiaOnyxConfigParser

NvidiaOnyxConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        r"""Parse text config lines and banners.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
            >>> config = '''
            ...     ##
            ...     ## VLAN configuration
            ...     ##
            ...        vlan 1
            ...        vlan 2
            ...        vlan 3
            ...
            ...     ##
            ...     ## Network management configuration
            ...     ##
            ...        banner login "
            ...
            ...        MULTILINE BANNER"'''
            >>> config_tree = NvidiaOnyxConfigParser(config)
            >>> config_tree.build_config_relationship() == \
            ... [
            ...     ConfigLine(config_line='vlan 1', parents=()),
            ...     ConfigLine(config_line='vlan 2', parents=()),
            ...     ConfigLine(config_line='vlan 3', parents=()),
            ...     ConfigLine(config_line='banner login "\n\n       MULTILINE BANNER"', parents=()),
            ... ]
            True
        """
        for line in self.generator_config:
            if not line.startswith(self.comment_char):
                self.config_lines.append(ConfigLine(line, ()))

        return self.config_lines

BaseConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        """Parse text tree of config lines and their parents."""
        raise NotImplementedError()

OptiswitchConfigParser

from netutils.config.parser import OptiswitchConfigParser

Ancestors (MRO)

The Method Resolution Order is described below.

  1. OptiswitchConfigParser
  2. BaseSpaceConfigParser
  3. BaseConfigParser

Descendant Classes

Attributes

Key Value Defined in
banner_start ['banner', 'vacant-message'] BaseSpaceConfigParser
banner_start ['banner', 'vacant-message'] BaseConfigParser
comment_chars ['!'] BaseSpaceConfigParser
comment_chars ['!'] BaseConfigParser

Methods

def __init__(self, config): BaseSpaceConfigParser

BaseSpaceConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self._indent_level = 0
        super(BaseSpaceConfigParser, self).__init__(config)

BaseConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config: The config text to parse.
        """
        self.config = config
        self._config: t.Optional[str] = None
        self._current_parents: t.Tuple[str, ...] = ()
        self.generator_config = (line for line in self.config_lines_only.splitlines())
        self.config_lines: t.List[ConfigLine] = []
        self.build_config_relationship()
def _build_banner(self, config_line): BaseSpaceConfigParser
    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None

        Raises:
            ValueError: When the parser is unable to identify the end of the Banner.
        """
        self._update_config_lines(config_line)
        self._current_parents += (config_line,)
        banner_config = []
        for line in self.generator_config:
            if not self.is_banner_end(line):
                banner_config.append(line)
            else:
                line = normalise_delimiter_caret_c(self.banner_end, line)
                banner_config.append(line)
                line = "\n".join(banner_config)
                if line.endswith("^C"):
                    banner, end, _ = line.rpartition("^C")
                    line = banner + end
                self._update_config_lines(line)
                self._current_parents = self._current_parents[:-1]
                try:
                    return next(self.generator_config)
                except StopIteration:
                    return None

        raise ValueError("Unable to parse banner end.")
def _build_nested_config(self, line): BaseSpaceConfigParser
    def _build_nested_config(self, line: str) -> t.Optional[str]:
        """Handle building child config sections.

        Args:
            line: A configuration line from the configuration text.

        Returns:
            The next top-level configuration line in the configuration text or None when the last line of configuration
            text is a nested configuration line.

        Raises:
            IndexError: When the number of parents does not match the expected deindent level.
        """
        self._update_config_lines(line)
        for line in self.generator_config:
            if not line[0].isspace():
                self._current_parents = ()
                self.indent_level = 0
                return line

            spaces = self.get_leading_space_count(line)
            if spaces == self.indent_level:
                pass
            elif spaces > self.indent_level:
                previous_config = self.config_lines[-1]
                self._current_parents += (previous_config.config_line,)
            else:
                self._current_parents = self._remove_parents(line, spaces)

            if spaces != self.indent_level:
                self.indent_level = spaces

            if self.is_banner_start(line):
                banner_line = self._build_banner(line)
                if banner_line is None or not banner_line[0].isspace():
                    self._current_parents = ()
                    self.indent_level = 0
                    return banner_line
                line = banner_line

            self._update_config_lines(line)
        return None
def _match_type_check(line, pattern, match_type): BaseSpaceConfigParser
    @staticmethod
    def _match_type_check(line: str, pattern: str, match_type: str) -> bool:
        """Checks pattern for exact match or regex."""
        if match_type == "exact" and line == pattern:
            return True
        if match_type == "startswith" and line.startswith(pattern):
            return True
        if match_type == "endswith" and line.endswith(pattern):
            return True
        if match_type == "regex" and re.match(pattern, line):
            return True
        return False
def _remove_parents(self, line, current_spaces): BaseSpaceConfigParser
    def _remove_parents(self, line: str, current_spaces: int) -> t.Tuple[str, ...]:
        """Remove parents from ``self._curent_parents`` based on indent levels.

        Args:
            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 = 1
        try:
            previous_parent = self._current_parents[-1]
            previous_indent = self.get_leading_space_count(previous_parent)
            while previous_indent > current_spaces:
                deindent_level += 1
                previous_parent = self._current_parents[-deindent_level]
                previous_indent = self.get_leading_space_count(previous_parent)
        except IndexError:
            raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n")
        parents = self._current_parents[:-deindent_level] or (self._current_parents[0],)
        return parents
def _update_config_lines(self, config_line): BaseSpaceConfigParser
    def _update_config_lines(self, config_line: str) -> None:
        """Add a ``ConfigLine`` object to ``self.config_lines``.

        Args:
            config_line: The current config line being evaluated.

        Returns:
            None
        """
        entry = ConfigLine(config_line, self._current_parents)
        self.config_lines.append(entry)
def build_config_relationship(self): BaseSpaceConfigParser

BaseSpaceConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        r"""Parse text tree of config lines and their parents.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
            >>> config = (
            ...     "interface Ethernet1/1\n"
            ...     "  vlan 10\n"
            ...     "  no shutdown\n"
            ...     "interface Ethernet1/2\n"
            ...     "  shutdown\n"
            ... )
            >>> config_tree = BaseSpaceConfigParser(config)
            >>> config_tree.build_config_relationship() == \
            ... [
            ...     ConfigLine(config_line='interface Ethernet1/1', parents=()),
            ...     ConfigLine(config_line='  vlan 10', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='  no shutdown', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='interface Ethernet1/2', parents=(),),
            ...     ConfigLine(config_line='  shutdown', parents=('interface Ethernet1/2',))
            ... ]
            True
        """
        for index, line in enumerate(self.generator_config):
            current_spaces = self.get_leading_space_count(line) if line[0].isspace() else 0
            if index == 0 and line[0].isspace():
                self._current_parents = self._remove_parents(line, current_spaces)
            elif not line[0].isspace():
                self._current_parents = ()
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore
            else:
                previous_config = self.config_lines[-1]
                self._current_parents = (previous_config.config_line,)
                self.indent_level = self.get_leading_space_count(line)
                if not self.is_banner_start(line):
                    line = self._build_nested_config(line)  # type: ignore
                else:
                    line = self._build_banner(line)  # type: ignore
                    if line is not None and line[0].isspace():
                        line = self._build_nested_config(line)  # type: ignore
                    else:
                        self._current_parents = ()

            if line is None:
                break
            elif self.is_banner_start(line):
                line = self._build_banner(line)  # type: ignore
                # line can potentially be another banner start therefore we do a secondary check.
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore

            self._update_config_lines(line)
        return self.config_lines

BaseConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        """Parse text tree of config lines and their parents."""
        raise NotImplementedError()
def find_all_children(self, pattern, match_type=exact): BaseSpaceConfigParser
    def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]:
        """Returns configuration part for a specific pattern not including parents.

        Args:
            pattern: pattern that describes parent.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith")
            >>> print(bgp_conf)
            ['router bgp 45000', '  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if (
                parents
                and self._match_type_check(parents, pattern, match_type)
                or self._match_type_check(cfg_line.config_line, pattern, match_type)
            ):
                config.append(cfg_line.config_line)
        return config
def find_children_w_parents(self, parent_pattern, child_pattern, match_type=exact): BaseSpaceConfigParser
    def find_children_w_parents(
        self, parent_pattern: str, child_pattern: str, match_type: str = "exact"
    ) -> t.List[str]:
        """Returns configuration part for a specific pattern including parents and children.

        Args:
            parent_pattern: pattern that describes parent.
            child_pattern: pattern that describes child.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern="  address-family", match_type="regex")
            >>> print(bgp_conf)
            ['  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        potential_parents = [
            elem.parents[0]
            for elem in self.build_config_relationship()
            if self._match_type_check(elem.config_line, child_pattern, match_type)
        ]
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if parents in potential_parents and self._match_type_check(
                parents,  # type: ignore[arg-type]
                parent_pattern,
                match_type,
            ):
                config.append(cfg_line.config_line)
        return config
def get_leading_space_count(config_line): BaseSpaceConfigParser
    @staticmethod
    def get_leading_space_count(config_line: str) -> int:
        r"""Determine how many spaces the ``config_line`` is indented.

        Args:
           config_line: A line of text in the config.

        Returns:
            The number of leading spaces.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''interface GigabitEthernet1\n description link to ISP'''
            >>> config_line = " description link to ISP"
            >>> indent_level = BaseSpaceConfigParser(config).get_leading_space_count(config_line)
            >>> indent_level
            1
            >>>
        """
        return len(config_line) - len(config_line.lstrip())
def is_banner_end(self, line): BaseSpaceConfigParser
    def is_banner_end(self, line: str) -> bool:
        """Determine if line ends the banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line ends banner, else False.
        """
        if self.banner_end in line:
            return True
        return False
def is_banner_start(self, line): BaseSpaceConfigParser
    def is_banner_start(self, line: str) -> bool:
        """Determine if the line starts a banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line starts banner, else False.
        """
        for banner_start in self.banner_start:
            if not line:
                return False
            if line.lstrip().startswith(banner_start):
                return True
        return False
def is_comment(self, line): BaseSpaceConfigParser
    def is_comment(self, line: str) -> bool:
        """Determine if line is a comment.

        Args:
            line: A config line from the device.

        Returns:
            True if line is a comment, else False.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> BaseSpaceConfigParser("interface Ethernet1/1").is_comment("interface Ethernet1/1")
            False
            >>> BaseSpaceConfigParser("!").is_comment("!")
            True
            >>>
        """
        for comment_char in self.comment_chars:
            if line.lstrip().startswith(comment_char):
                return True
        return False

PaloAltoNetworksConfigParser

from netutils.config.parser import PaloAltoNetworksConfigParser

Ancestors (MRO)

The Method Resolution Order is described below.

  1. PaloAltoNetworksConfigParser
  2. BaseSpaceConfigParser
  3. BaseConfigParser

Descendant Classes

Attributes

Key Value Defined in
banner_end '' PaloAltoNetworksConfigParser
banner_start ['banner', 'vacant-message'] BaseSpaceConfigParser
banner_start ['banner', 'vacant-message'] BaseConfigParser
comment_chars ['!'] BaseSpaceConfigParser
comment_chars ['!'] BaseConfigParser

Methods

def __init__(self, config): BaseSpaceConfigParser

BaseSpaceConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self._indent_level = 0
        super(BaseSpaceConfigParser, self).__init__(config)

BaseConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config: The config text to parse.
        """
        self.config = config
        self._config: t.Optional[str] = None
        self._current_parents: t.Tuple[str, ...] = ()
        self.generator_config = (line for line in self.config_lines_only.splitlines())
        self.config_lines: t.List[ConfigLine] = []
        self.build_config_relationship()
def _build_banner(self, config_line): PaloAltoNetworksConfigParser

PaloAltoNetworksConfigParser

    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] = []
        for line in self.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 ".
            if line.startswith("set "):
                # New command, save the banner and return the next line
                if banner_config:
                    banner_string = "\n".join(banner_config)
                    self._update_config_lines(banner_string)
                self._current_parents = self._current_parents[:-1]
                return line
            banner_config.append(line)

        # Edge case, the last line of the config is the banner
        banner_string = "\n".join(banner_config)
        self._update_config_lines(banner_string)
        self._current_parents = self._current_parents[:-1]
        return None

BaseSpaceConfigParser

    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None

        Raises:
            ValueError: When the parser is unable to identify the end of the Banner.
        """
        self._update_config_lines(config_line)
        self._current_parents += (config_line,)
        banner_config = []
        for line in self.generator_config:
            if not self.is_banner_end(line):
                banner_config.append(line)
            else:
                line = normalise_delimiter_caret_c(self.banner_end, line)
                banner_config.append(line)
                line = "\n".join(banner_config)
                if line.endswith("^C"):
                    banner, end, _ = line.rpartition("^C")
                    line = banner + end
                self._update_config_lines(line)
                self._current_parents = self._current_parents[:-1]
                try:
                    return next(self.generator_config)
                except StopIteration:
                    return None

        raise ValueError("Unable to parse banner end.")
def _build_nested_config(self, line): BaseSpaceConfigParser
    def _build_nested_config(self, line: str) -> t.Optional[str]:
        """Handle building child config sections.

        Args:
            line: A configuration line from the configuration text.

        Returns:
            The next top-level configuration line in the configuration text or None when the last line of configuration
            text is a nested configuration line.

        Raises:
            IndexError: When the number of parents does not match the expected deindent level.
        """
        self._update_config_lines(line)
        for line in self.generator_config:
            if not line[0].isspace():
                self._current_parents = ()
                self.indent_level = 0
                return line

            spaces = self.get_leading_space_count(line)
            if spaces == self.indent_level:
                pass
            elif spaces > self.indent_level:
                previous_config = self.config_lines[-1]
                self._current_parents += (previous_config.config_line,)
            else:
                self._current_parents = self._remove_parents(line, spaces)

            if spaces != self.indent_level:
                self.indent_level = spaces

            if self.is_banner_start(line):
                banner_line = self._build_banner(line)
                if banner_line is None or not banner_line[0].isspace():
                    self._current_parents = ()
                    self.indent_level = 0
                    return banner_line
                line = banner_line

            self._update_config_lines(line)
        return None
def _match_type_check(line, pattern, match_type): BaseSpaceConfigParser
    @staticmethod
    def _match_type_check(line: str, pattern: str, match_type: str) -> bool:
        """Checks pattern for exact match or regex."""
        if match_type == "exact" and line == pattern:
            return True
        if match_type == "startswith" and line.startswith(pattern):
            return True
        if match_type == "endswith" and line.endswith(pattern):
            return True
        if match_type == "regex" and re.match(pattern, line):
            return True
        return False
def _remove_parents(self, line, current_spaces): BaseSpaceConfigParser
    def _remove_parents(self, line: str, current_spaces: int) -> t.Tuple[str, ...]:
        """Remove parents from ``self._curent_parents`` based on indent levels.

        Args:
            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 = 1
        try:
            previous_parent = self._current_parents[-1]
            previous_indent = self.get_leading_space_count(previous_parent)
            while previous_indent > current_spaces:
                deindent_level += 1
                previous_parent = self._current_parents[-deindent_level]
                previous_indent = self.get_leading_space_count(previous_parent)
        except IndexError:
            raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n")
        parents = self._current_parents[:-deindent_level] or (self._current_parents[0],)
        return parents
def _update_config_lines(self, config_line): BaseSpaceConfigParser
    def _update_config_lines(self, config_line: str) -> None:
        """Add a ``ConfigLine`` object to ``self.config_lines``.

        Args:
            config_line: The current config line being evaluated.

        Returns:
            None
        """
        entry = ConfigLine(config_line, self._current_parents)
        self.config_lines.append(entry)
def build_config_relationship(self): PaloAltoNetworksConfigParser

PaloAltoNetworksConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:  # pylint: disable=too-many-branches
        r"""Parse text of config lines and find their parents.

        Examples:
            >>> from netutils.config.parser import PaloAltoNetworksConfigParser, ConfigLine
            >>> config = (
            ...     "set deviceconfig system hostname firewall1\n"
            ...     "set deviceconfig system panorama local-panorama panorama-server 10.0.0.1\n"
            ...     "set deviceconfig system panorama local-panorama panorama-server-2 10.0.0.2\n"
            ...     "set deviceconfig setting config rematch yes\n"
            ... )
            >>> config_tree = PaloAltoNetworksConfigParser(config)
            >>> config_tree.build_config_relationship() == \
            ... [
            ...     ConfigLine(config_line="set deviceconfig system hostname firewall1", parents=()),
            ...     ConfigLine(config_line="set deviceconfig system panorama local-panorama panorama-server 10.0.0.1", parents=()),
            ...     ConfigLine(config_line="set deviceconfig system panorama local-panorama panorama-server-2 10.0.0.2", parents=()),
            ...     ConfigLine(config_line="set deviceconfig setting config rematch yes", parents=()),
            ... ]
            True
        """
        if self.config_lines_only is None:
            return []
        config_lines = self.config_lines_only.splitlines()
        if not config_lines:
            return []

        if "@dirtyId" in self.config_lines_only:
            # We have to specifically check for JSON format because it can be confused with the brace format
            raise ValueError("Found 'json' configuration format. Please provide in 'set' or 'default' (brace) format.")
        if any(line.endswith("{") for line in config_lines):
            converted_config = paloalto_panos_brace_to_set(cfg=self.config, cfg_type="string")
            list_config = converted_config.splitlines()
            self.generator_config = (line for line in list_config)
        elif not any(line.startswith("set ") for line in config_lines):
            raise ValueError("Unexpected configuration format. Please provide in 'set' or 'default' (brace) format.")

        # build config relationships
        for line in self.generator_config:
            if self.is_banner_start(line):
                line = self._build_banner(line)  # type: ignore

            if line is None:
                break

            self._update_config_lines(line)
        return self.config_lines

BaseSpaceConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        r"""Parse text tree of config lines and their parents.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
            >>> config = (
            ...     "interface Ethernet1/1\n"
            ...     "  vlan 10\n"
            ...     "  no shutdown\n"
            ...     "interface Ethernet1/2\n"
            ...     "  shutdown\n"
            ... )
            >>> config_tree = BaseSpaceConfigParser(config)
            >>> config_tree.build_config_relationship() == \
            ... [
            ...     ConfigLine(config_line='interface Ethernet1/1', parents=()),
            ...     ConfigLine(config_line='  vlan 10', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='  no shutdown', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='interface Ethernet1/2', parents=(),),
            ...     ConfigLine(config_line='  shutdown', parents=('interface Ethernet1/2',))
            ... ]
            True
        """
        for index, line in enumerate(self.generator_config):
            current_spaces = self.get_leading_space_count(line) if line[0].isspace() else 0
            if index == 0 and line[0].isspace():
                self._current_parents = self._remove_parents(line, current_spaces)
            elif not line[0].isspace():
                self._current_parents = ()
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore
            else:
                previous_config = self.config_lines[-1]
                self._current_parents = (previous_config.config_line,)
                self.indent_level = self.get_leading_space_count(line)
                if not self.is_banner_start(line):
                    line = self._build_nested_config(line)  # type: ignore
                else:
                    line = self._build_banner(line)  # type: ignore
                    if line is not None and line[0].isspace():
                        line = self._build_nested_config(line)  # type: ignore
                    else:
                        self._current_parents = ()

            if line is None:
                break
            elif self.is_banner_start(line):
                line = self._build_banner(line)  # type: ignore
                # line can potentially be another banner start therefore we do a secondary check.
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore

            self._update_config_lines(line)
        return self.config_lines

BaseConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        """Parse text tree of config lines and their parents."""
        raise NotImplementedError()
def find_all_children(self, pattern, match_type=exact): BaseSpaceConfigParser
    def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]:
        """Returns configuration part for a specific pattern not including parents.

        Args:
            pattern: pattern that describes parent.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith")
            >>> print(bgp_conf)
            ['router bgp 45000', '  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if (
                parents
                and self._match_type_check(parents, pattern, match_type)
                or self._match_type_check(cfg_line.config_line, pattern, match_type)
            ):
                config.append(cfg_line.config_line)
        return config
def find_children_w_parents(self, parent_pattern, child_pattern, match_type=exact): BaseSpaceConfigParser
    def find_children_w_parents(
        self, parent_pattern: str, child_pattern: str, match_type: str = "exact"
    ) -> t.List[str]:
        """Returns configuration part for a specific pattern including parents and children.

        Args:
            parent_pattern: pattern that describes parent.
            child_pattern: pattern that describes child.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern="  address-family", match_type="regex")
            >>> print(bgp_conf)
            ['  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        potential_parents = [
            elem.parents[0]
            for elem in self.build_config_relationship()
            if self._match_type_check(elem.config_line, child_pattern, match_type)
        ]
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if parents in potential_parents and self._match_type_check(
                parents,  # type: ignore[arg-type]
                parent_pattern,
                match_type,
            ):
                config.append(cfg_line.config_line)
        return config
def get_leading_space_count(config_line): BaseSpaceConfigParser
    @staticmethod
    def get_leading_space_count(config_line: str) -> int:
        r"""Determine how many spaces the ``config_line`` is indented.

        Args:
           config_line: A line of text in the config.

        Returns:
            The number of leading spaces.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''interface GigabitEthernet1\n description link to ISP'''
            >>> config_line = " description link to ISP"
            >>> indent_level = BaseSpaceConfigParser(config).get_leading_space_count(config_line)
            >>> indent_level
            1
            >>>
        """
        return len(config_line) - len(config_line.lstrip())
def is_banner_end(self, line): BaseSpaceConfigParser
    def is_banner_end(self, line: str) -> bool:
        """Determine if line ends the banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line ends banner, else False.
        """
        if self.banner_end in line:
            return True
        return False
def is_banner_start(self, line): BaseSpaceConfigParser
    def is_banner_start(self, line: str) -> bool:
        """Determine if the line starts a banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line starts banner, else False.
        """
        for banner_start in self.banner_start:
            if not line:
                return False
            if line.lstrip().startswith(banner_start):
                return True
        return False
def is_comment(self, line): BaseSpaceConfigParser
    def is_comment(self, line: str) -> bool:
        """Determine if line is a comment.

        Args:
            line: A config line from the device.

        Returns:
            True if line is a comment, else False.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> BaseSpaceConfigParser("interface Ethernet1/1").is_comment("interface Ethernet1/1")
            False
            >>> BaseSpaceConfigParser("!").is_comment("!")
            True
            >>>
        """
        for comment_char in self.comment_chars:
            if line.lstrip().startswith(comment_char):
                return True
        return False

RadEtxConfigParser

from netutils.config.parser import RadEtxConfigParser

Ancestors (MRO)

The Method Resolution Order is described below.

  1. RadEtxConfigParser
  2. BaseSpaceConfigParser
  3. BaseConfigParser

Descendant Classes

Attributes

Key Value Defined in
banner_start ['banner', 'vacant-message'] BaseSpaceConfigParser
banner_start ['banner', 'vacant-message'] BaseConfigParser
comment_chars ['!'] BaseSpaceConfigParser
comment_chars ['!'] BaseConfigParser

Methods

def __init__(self, config): BaseSpaceConfigParser

BaseSpaceConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self._indent_level = 0
        super(BaseSpaceConfigParser, self).__init__(config)

BaseConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config: The config text to parse.
        """
        self.config = config
        self._config: t.Optional[str] = None
        self._current_parents: t.Tuple[str, ...] = ()
        self.generator_config = (line for line in self.config_lines_only.splitlines())
        self.config_lines: t.List[ConfigLine] = []
        self.build_config_relationship()
def _build_banner(self, config_line): BaseSpaceConfigParser
    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None

        Raises:
            ValueError: When the parser is unable to identify the end of the Banner.
        """
        self._update_config_lines(config_line)
        self._current_parents += (config_line,)
        banner_config = []
        for line in self.generator_config:
            if not self.is_banner_end(line):
                banner_config.append(line)
            else:
                line = normalise_delimiter_caret_c(self.banner_end, line)
                banner_config.append(line)
                line = "\n".join(banner_config)
                if line.endswith("^C"):
                    banner, end, _ = line.rpartition("^C")
                    line = banner + end
                self._update_config_lines(line)
                self._current_parents = self._current_parents[:-1]
                try:
                    return next(self.generator_config)
                except StopIteration:
                    return None

        raise ValueError("Unable to parse banner end.")
def _build_nested_config(self, line): BaseSpaceConfigParser
    def _build_nested_config(self, line: str) -> t.Optional[str]:
        """Handle building child config sections.

        Args:
            line: A configuration line from the configuration text.

        Returns:
            The next top-level configuration line in the configuration text or None when the last line of configuration
            text is a nested configuration line.

        Raises:
            IndexError: When the number of parents does not match the expected deindent level.
        """
        self._update_config_lines(line)
        for line in self.generator_config:
            if not line[0].isspace():
                self._current_parents = ()
                self.indent_level = 0
                return line

            spaces = self.get_leading_space_count(line)
            if spaces == self.indent_level:
                pass
            elif spaces > self.indent_level:
                previous_config = self.config_lines[-1]
                self._current_parents += (previous_config.config_line,)
            else:
                self._current_parents = self._remove_parents(line, spaces)

            if spaces != self.indent_level:
                self.indent_level = spaces

            if self.is_banner_start(line):
                banner_line = self._build_banner(line)
                if banner_line is None or not banner_line[0].isspace():
                    self._current_parents = ()
                    self.indent_level = 0
                    return banner_line
                line = banner_line

            self._update_config_lines(line)
        return None
def _match_type_check(line, pattern, match_type): BaseSpaceConfigParser
    @staticmethod
    def _match_type_check(line: str, pattern: str, match_type: str) -> bool:
        """Checks pattern for exact match or regex."""
        if match_type == "exact" and line == pattern:
            return True
        if match_type == "startswith" and line.startswith(pattern):
            return True
        if match_type == "endswith" and line.endswith(pattern):
            return True
        if match_type == "regex" and re.match(pattern, line):
            return True
        return False
def _remove_parents(self, line, current_spaces): BaseSpaceConfigParser
    def _remove_parents(self, line: str, current_spaces: int) -> t.Tuple[str, ...]:
        """Remove parents from ``self._curent_parents`` based on indent levels.

        Args:
            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 = 1
        try:
            previous_parent = self._current_parents[-1]
            previous_indent = self.get_leading_space_count(previous_parent)
            while previous_indent > current_spaces:
                deindent_level += 1
                previous_parent = self._current_parents[-deindent_level]
                previous_indent = self.get_leading_space_count(previous_parent)
        except IndexError:
            raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n")
        parents = self._current_parents[:-deindent_level] or (self._current_parents[0],)
        return parents
def _update_config_lines(self, config_line): BaseSpaceConfigParser
    def _update_config_lines(self, config_line: str) -> None:
        """Add a ``ConfigLine`` object to ``self.config_lines``.

        Args:
            config_line: The current config line being evaluated.

        Returns:
            None
        """
        entry = ConfigLine(config_line, self._current_parents)
        self.config_lines.append(entry)
def build_config_relationship(self): BaseSpaceConfigParser

BaseSpaceConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        r"""Parse text tree of config lines and their parents.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
            >>> config = (
            ...     "interface Ethernet1/1\n"
            ...     "  vlan 10\n"
            ...     "  no shutdown\n"
            ...     "interface Ethernet1/2\n"
            ...     "  shutdown\n"
            ... )
            >>> config_tree = BaseSpaceConfigParser(config)
            >>> config_tree.build_config_relationship() == \
            ... [
            ...     ConfigLine(config_line='interface Ethernet1/1', parents=()),
            ...     ConfigLine(config_line='  vlan 10', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='  no shutdown', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='interface Ethernet1/2', parents=(),),
            ...     ConfigLine(config_line='  shutdown', parents=('interface Ethernet1/2',))
            ... ]
            True
        """
        for index, line in enumerate(self.generator_config):
            current_spaces = self.get_leading_space_count(line) if line[0].isspace() else 0
            if index == 0 and line[0].isspace():
                self._current_parents = self._remove_parents(line, current_spaces)
            elif not line[0].isspace():
                self._current_parents = ()
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore
            else:
                previous_config = self.config_lines[-1]
                self._current_parents = (previous_config.config_line,)
                self.indent_level = self.get_leading_space_count(line)
                if not self.is_banner_start(line):
                    line = self._build_nested_config(line)  # type: ignore
                else:
                    line = self._build_banner(line)  # type: ignore
                    if line is not None and line[0].isspace():
                        line = self._build_nested_config(line)  # type: ignore
                    else:
                        self._current_parents = ()

            if line is None:
                break
            elif self.is_banner_start(line):
                line = self._build_banner(line)  # type: ignore
                # line can potentially be another banner start therefore we do a secondary check.
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore

            self._update_config_lines(line)
        return self.config_lines

BaseConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        """Parse text tree of config lines and their parents."""
        raise NotImplementedError()
def find_all_children(self, pattern, match_type=exact): BaseSpaceConfigParser
    def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]:
        """Returns configuration part for a specific pattern not including parents.

        Args:
            pattern: pattern that describes parent.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith")
            >>> print(bgp_conf)
            ['router bgp 45000', '  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if (
                parents
                and self._match_type_check(parents, pattern, match_type)
                or self._match_type_check(cfg_line.config_line, pattern, match_type)
            ):
                config.append(cfg_line.config_line)
        return config
def find_children_w_parents(self, parent_pattern, child_pattern, match_type=exact): BaseSpaceConfigParser
    def find_children_w_parents(
        self, parent_pattern: str, child_pattern: str, match_type: str = "exact"
    ) -> t.List[str]:
        """Returns configuration part for a specific pattern including parents and children.

        Args:
            parent_pattern: pattern that describes parent.
            child_pattern: pattern that describes child.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern="  address-family", match_type="regex")
            >>> print(bgp_conf)
            ['  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        potential_parents = [
            elem.parents[0]
            for elem in self.build_config_relationship()
            if self._match_type_check(elem.config_line, child_pattern, match_type)
        ]
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if parents in potential_parents and self._match_type_check(
                parents,  # type: ignore[arg-type]
                parent_pattern,
                match_type,
            ):
                config.append(cfg_line.config_line)
        return config
def get_leading_space_count(config_line): BaseSpaceConfigParser
    @staticmethod
    def get_leading_space_count(config_line: str) -> int:
        r"""Determine how many spaces the ``config_line`` is indented.

        Args:
           config_line: A line of text in the config.

        Returns:
            The number of leading spaces.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''interface GigabitEthernet1\n description link to ISP'''
            >>> config_line = " description link to ISP"
            >>> indent_level = BaseSpaceConfigParser(config).get_leading_space_count(config_line)
            >>> indent_level
            1
            >>>
        """
        return len(config_line) - len(config_line.lstrip())
def is_banner_end(self, line): BaseSpaceConfigParser
    def is_banner_end(self, line: str) -> bool:
        """Determine if line ends the banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line ends banner, else False.
        """
        if self.banner_end in line:
            return True
        return False
def is_banner_start(self, line): BaseSpaceConfigParser
    def is_banner_start(self, line: str) -> bool:
        """Determine if the line starts a banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line starts banner, else False.
        """
        for banner_start in self.banner_start:
            if not line:
                return False
            if line.lstrip().startswith(banner_start):
                return True
        return False
def is_comment(self, line): BaseSpaceConfigParser
    def is_comment(self, line: str) -> bool:
        """Determine if line is a comment.

        Args:
            line: A config line from the device.

        Returns:
            True if line is a comment, else False.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> BaseSpaceConfigParser("interface Ethernet1/1").is_comment("interface Ethernet1/1")
            False
            >>> BaseSpaceConfigParser("!").is_comment("!")
            True
            >>>
        """
        for comment_char in self.comment_chars:
            if line.lstrip().startswith(comment_char):
                return True
        return False

RouterOSConfigParser

from netutils.config.parser import RouterOSConfigParser

Ancestors (MRO)

The Method Resolution Order is described below.

  1. RouterOSConfigParser
  2. BaseSpaceConfigParser
  3. BaseConfigParser

Descendant Classes

Attributes

Key Value Defined in
banner_start ['banner', 'vacant-message'] BaseSpaceConfigParser
banner_start ['banner', 'vacant-message'] BaseConfigParser
comment_chars ['!'] BaseSpaceConfigParser
comment_chars ['!'] BaseConfigParser

Methods

def __init__(self, config): BaseSpaceConfigParser

BaseSpaceConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self._indent_level = 0
        super(BaseSpaceConfigParser, self).__init__(config)

BaseConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config: The config text to parse.
        """
        self.config = config
        self._config: t.Optional[str] = None
        self._current_parents: t.Tuple[str, ...] = ()
        self.generator_config = (line for line in self.config_lines_only.splitlines())
        self.config_lines: t.List[ConfigLine] = []
        self.build_config_relationship()
def _build_banner(self, config_line): RouterOSConfigParser

RouterOSConfigParser

    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]
        for line in self.generator_config:
            if not self.is_banner_end(line):
                banner_config.append(line)
            else:
                banner_config.append(line)
                line = "\n".join(banner_config)
                self._update_config_lines(line)
                try:
                    return next(self.generator_config)
                except StopIteration:
                    return None
        raise ValueError("Unable to parse banner (system note) end.")

BaseSpaceConfigParser

    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None

        Raises:
            ValueError: When the parser is unable to identify the end of the Banner.
        """
        self._update_config_lines(config_line)
        self._current_parents += (config_line,)
        banner_config = []
        for line in self.generator_config:
            if not self.is_banner_end(line):
                banner_config.append(line)
            else:
                line = normalise_delimiter_caret_c(self.banner_end, line)
                banner_config.append(line)
                line = "\n".join(banner_config)
                if line.endswith("^C"):
                    banner, end, _ = line.rpartition("^C")
                    line = banner + end
                self._update_config_lines(line)
                self._current_parents = self._current_parents[:-1]
                try:
                    return next(self.generator_config)
                except StopIteration:
                    return None

        raise ValueError("Unable to parse banner end.")
def _build_nested_config(self, line): BaseSpaceConfigParser
    def _build_nested_config(self, line: str) -> t.Optional[str]:
        """Handle building child config sections.

        Args:
            line: A configuration line from the configuration text.

        Returns:
            The next top-level configuration line in the configuration text or None when the last line of configuration
            text is a nested configuration line.

        Raises:
            IndexError: When the number of parents does not match the expected deindent level.
        """
        self._update_config_lines(line)
        for line in self.generator_config:
            if not line[0].isspace():
                self._current_parents = ()
                self.indent_level = 0
                return line

            spaces = self.get_leading_space_count(line)
            if spaces == self.indent_level:
                pass
            elif spaces > self.indent_level:
                previous_config = self.config_lines[-1]
                self._current_parents += (previous_config.config_line,)
            else:
                self._current_parents = self._remove_parents(line, spaces)

            if spaces != self.indent_level:
                self.indent_level = spaces

            if self.is_banner_start(line):
                banner_line = self._build_banner(line)
                if banner_line is None or not banner_line[0].isspace():
                    self._current_parents = ()
                    self.indent_level = 0
                    return banner_line
                line = banner_line

            self._update_config_lines(line)
        return None
def _match_type_check(line, pattern, match_type): BaseSpaceConfigParser
    @staticmethod
    def _match_type_check(line: str, pattern: str, match_type: str) -> bool:
        """Checks pattern for exact match or regex."""
        if match_type == "exact" and line == pattern:
            return True
        if match_type == "startswith" and line.startswith(pattern):
            return True
        if match_type == "endswith" and line.endswith(pattern):
            return True
        if match_type == "regex" and re.match(pattern, line):
            return True
        return False
def _remove_parents(self, line, current_spaces): BaseSpaceConfigParser
    def _remove_parents(self, line: str, current_spaces: int) -> t.Tuple[str, ...]:
        """Remove parents from ``self._curent_parents`` based on indent levels.

        Args:
            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 = 1
        try:
            previous_parent = self._current_parents[-1]
            previous_indent = self.get_leading_space_count(previous_parent)
            while previous_indent > current_spaces:
                deindent_level += 1
                previous_parent = self._current_parents[-deindent_level]
                previous_indent = self.get_leading_space_count(previous_parent)
        except IndexError:
            raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n")
        parents = self._current_parents[:-deindent_level] or (self._current_parents[0],)
        return parents
def _update_config_lines(self, config_line): BaseSpaceConfigParser
    def _update_config_lines(self, config_line: str) -> None:
        """Add a ``ConfigLine`` object to ``self.config_lines``.

        Args:
            config_line: The current config line being evaluated.

        Returns:
            None
        """
        entry = ConfigLine(config_line, self._current_parents)
        self.config_lines.append(entry)
def build_config_relationship(self): BaseSpaceConfigParser

BaseSpaceConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        r"""Parse text tree of config lines and their parents.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
            >>> config = (
            ...     "interface Ethernet1/1\n"
            ...     "  vlan 10\n"
            ...     "  no shutdown\n"
            ...     "interface Ethernet1/2\n"
            ...     "  shutdown\n"
            ... )
            >>> config_tree = BaseSpaceConfigParser(config)
            >>> config_tree.build_config_relationship() == \
            ... [
            ...     ConfigLine(config_line='interface Ethernet1/1', parents=()),
            ...     ConfigLine(config_line='  vlan 10', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='  no shutdown', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='interface Ethernet1/2', parents=(),),
            ...     ConfigLine(config_line='  shutdown', parents=('interface Ethernet1/2',))
            ... ]
            True
        """
        for index, line in enumerate(self.generator_config):
            current_spaces = self.get_leading_space_count(line) if line[0].isspace() else 0
            if index == 0 and line[0].isspace():
                self._current_parents = self._remove_parents(line, current_spaces)
            elif not line[0].isspace():
                self._current_parents = ()
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore
            else:
                previous_config = self.config_lines[-1]
                self._current_parents = (previous_config.config_line,)
                self.indent_level = self.get_leading_space_count(line)
                if not self.is_banner_start(line):
                    line = self._build_nested_config(line)  # type: ignore
                else:
                    line = self._build_banner(line)  # type: ignore
                    if line is not None and line[0].isspace():
                        line = self._build_nested_config(line)  # type: ignore
                    else:
                        self._current_parents = ()

            if line is None:
                break
            elif self.is_banner_start(line):
                line = self._build_banner(line)  # type: ignore
                # line can potentially be another banner start therefore we do a secondary check.
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore

            self._update_config_lines(line)
        return self.config_lines

BaseConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        """Parse text tree of config lines and their parents."""
        raise NotImplementedError()
def find_all_children(self, pattern, match_type=exact): BaseSpaceConfigParser
    def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]:
        """Returns configuration part for a specific pattern not including parents.

        Args:
            pattern: pattern that describes parent.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith")
            >>> print(bgp_conf)
            ['router bgp 45000', '  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if (
                parents
                and self._match_type_check(parents, pattern, match_type)
                or self._match_type_check(cfg_line.config_line, pattern, match_type)
            ):
                config.append(cfg_line.config_line)
        return config
def find_children_w_parents(self, parent_pattern, child_pattern, match_type=exact): BaseSpaceConfigParser
    def find_children_w_parents(
        self, parent_pattern: str, child_pattern: str, match_type: str = "exact"
    ) -> t.List[str]:
        """Returns configuration part for a specific pattern including parents and children.

        Args:
            parent_pattern: pattern that describes parent.
            child_pattern: pattern that describes child.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern="  address-family", match_type="regex")
            >>> print(bgp_conf)
            ['  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        potential_parents = [
            elem.parents[0]
            for elem in self.build_config_relationship()
            if self._match_type_check(elem.config_line, child_pattern, match_type)
        ]
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if parents in potential_parents and self._match_type_check(
                parents,  # type: ignore[arg-type]
                parent_pattern,
                match_type,
            ):
                config.append(cfg_line.config_line)
        return config
def get_leading_space_count(config_line): BaseSpaceConfigParser
    @staticmethod
    def get_leading_space_count(config_line: str) -> int:
        r"""Determine how many spaces the ``config_line`` is indented.

        Args:
           config_line: A line of text in the config.

        Returns:
            The number of leading spaces.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''interface GigabitEthernet1\n description link to ISP'''
            >>> config_line = " description link to ISP"
            >>> indent_level = BaseSpaceConfigParser(config).get_leading_space_count(config_line)
            >>> indent_level
            1
            >>>
        """
        return len(config_line) - len(config_line.lstrip())
def is_banner_end(self, line): RouterOSConfigParser

RouterOSConfigParser

    def is_banner_end(self, line: str) -> bool:
        """Determine if end of banner."""
        if line.endswith('"') or line.startswith("/"):
            return True
        return False

BaseSpaceConfigParser

    def is_banner_end(self, line: str) -> bool:
        """Determine if line ends the banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line ends banner, else False.
        """
        if self.banner_end in line:
            return True
        return False
def is_banner_start(self, line): BaseSpaceConfigParser
    def is_banner_start(self, line: str) -> bool:
        """Determine if the line starts a banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line starts banner, else False.
        """
        for banner_start in self.banner_start:
            if not line:
                return False
            if line.lstrip().startswith(banner_start):
                return True
        return False
def is_comment(self, line): BaseSpaceConfigParser
    def is_comment(self, line: str) -> bool:
        """Determine if line is a comment.

        Args:
            line: A config line from the device.

        Returns:
            True if line is a comment, else False.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> BaseSpaceConfigParser("interface Ethernet1/1").is_comment("interface Ethernet1/1")
            False
            >>> BaseSpaceConfigParser("!").is_comment("!")
            True
            >>>
        """
        for comment_char in self.comment_chars:
            if line.lstrip().startswith(comment_char):
                return True
        return False

UbiquitiAirOSConfigParser

from netutils.config.parser import UbiquitiAirOSConfigParser

Ancestors (MRO)

The Method Resolution Order is described below.

  1. UbiquitiAirOSConfigParser
  2. BaseSpaceConfigParser
  3. BaseConfigParser

Descendant Classes

Attributes

Key Value Defined in
banner_start ['banner', 'vacant-message'] BaseSpaceConfigParser
banner_start ['banner', 'vacant-message'] BaseConfigParser
comment_chars ['!'] BaseSpaceConfigParser
comment_chars ['!'] BaseConfigParser

Methods

def __init__(self, config): BaseSpaceConfigParser

BaseSpaceConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config (str): The config text to parse.
        """
        self._indent_level = 0
        super(BaseSpaceConfigParser, self).__init__(config)

BaseConfigParser

    def __init__(self, config: str):
        """Create ConfigParser Object.

        Args:
            config: The config text to parse.
        """
        self.config = config
        self._config: t.Optional[str] = None
        self._current_parents: t.Tuple[str, ...] = ()
        self.generator_config = (line for line in self.config_lines_only.splitlines())
        self.config_lines: t.List[ConfigLine] = []
        self.build_config_relationship()
def _build_banner(self, config_line): BaseSpaceConfigParser
    def _build_banner(self, config_line: str) -> t.Optional[str]:
        """Handle banner config lines.

        Args:
            config_line: The start of the banner config.

        Returns:
            The next configuration line in the configuration text or None

        Raises:
            ValueError: When the parser is unable to identify the end of the Banner.
        """
        self._update_config_lines(config_line)
        self._current_parents += (config_line,)
        banner_config = []
        for line in self.generator_config:
            if not self.is_banner_end(line):
                banner_config.append(line)
            else:
                line = normalise_delimiter_caret_c(self.banner_end, line)
                banner_config.append(line)
                line = "\n".join(banner_config)
                if line.endswith("^C"):
                    banner, end, _ = line.rpartition("^C")
                    line = banner + end
                self._update_config_lines(line)
                self._current_parents = self._current_parents[:-1]
                try:
                    return next(self.generator_config)
                except StopIteration:
                    return None

        raise ValueError("Unable to parse banner end.")
def _build_nested_config(self, line): BaseSpaceConfigParser
    def _build_nested_config(self, line: str) -> t.Optional[str]:
        """Handle building child config sections.

        Args:
            line: A configuration line from the configuration text.

        Returns:
            The next top-level configuration line in the configuration text or None when the last line of configuration
            text is a nested configuration line.

        Raises:
            IndexError: When the number of parents does not match the expected deindent level.
        """
        self._update_config_lines(line)
        for line in self.generator_config:
            if not line[0].isspace():
                self._current_parents = ()
                self.indent_level = 0
                return line

            spaces = self.get_leading_space_count(line)
            if spaces == self.indent_level:
                pass
            elif spaces > self.indent_level:
                previous_config = self.config_lines[-1]
                self._current_parents += (previous_config.config_line,)
            else:
                self._current_parents = self._remove_parents(line, spaces)

            if spaces != self.indent_level:
                self.indent_level = spaces

            if self.is_banner_start(line):
                banner_line = self._build_banner(line)
                if banner_line is None or not banner_line[0].isspace():
                    self._current_parents = ()
                    self.indent_level = 0
                    return banner_line
                line = banner_line

            self._update_config_lines(line)
        return None
def _match_type_check(line, pattern, match_type): BaseSpaceConfigParser
    @staticmethod
    def _match_type_check(line: str, pattern: str, match_type: str) -> bool:
        """Checks pattern for exact match or regex."""
        if match_type == "exact" and line == pattern:
            return True
        if match_type == "startswith" and line.startswith(pattern):
            return True
        if match_type == "endswith" and line.endswith(pattern):
            return True
        if match_type == "regex" and re.match(pattern, line):
            return True
        return False
def _remove_parents(self, line, current_spaces): BaseSpaceConfigParser
    def _remove_parents(self, line: str, current_spaces: int) -> t.Tuple[str, ...]:
        """Remove parents from ``self._curent_parents`` based on indent levels.

        Args:
            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 = 1
        try:
            previous_parent = self._current_parents[-1]
            previous_indent = self.get_leading_space_count(previous_parent)
            while previous_indent > current_spaces:
                deindent_level += 1
                previous_parent = self._current_parents[-deindent_level]
                previous_indent = self.get_leading_space_count(previous_parent)
        except IndexError:
            raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n")
        parents = self._current_parents[:-deindent_level] or (self._current_parents[0],)
        return parents
def _update_config_lines(self, config_line): BaseSpaceConfigParser
    def _update_config_lines(self, config_line: str) -> None:
        """Add a ``ConfigLine`` object to ``self.config_lines``.

        Args:
            config_line: The current config line being evaluated.

        Returns:
            None
        """
        entry = ConfigLine(config_line, self._current_parents)
        self.config_lines.append(entry)
def build_config_relationship(self): BaseSpaceConfigParser

BaseSpaceConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        r"""Parse text tree of config lines and their parents.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser, ConfigLine
            >>> config = (
            ...     "interface Ethernet1/1\n"
            ...     "  vlan 10\n"
            ...     "  no shutdown\n"
            ...     "interface Ethernet1/2\n"
            ...     "  shutdown\n"
            ... )
            >>> config_tree = BaseSpaceConfigParser(config)
            >>> config_tree.build_config_relationship() == \
            ... [
            ...     ConfigLine(config_line='interface Ethernet1/1', parents=()),
            ...     ConfigLine(config_line='  vlan 10', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='  no shutdown', parents=('interface Ethernet1/1',)),
            ...     ConfigLine(config_line='interface Ethernet1/2', parents=(),),
            ...     ConfigLine(config_line='  shutdown', parents=('interface Ethernet1/2',))
            ... ]
            True
        """
        for index, line in enumerate(self.generator_config):
            current_spaces = self.get_leading_space_count(line) if line[0].isspace() else 0
            if index == 0 and line[0].isspace():
                self._current_parents = self._remove_parents(line, current_spaces)
            elif not line[0].isspace():
                self._current_parents = ()
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore
            else:
                previous_config = self.config_lines[-1]
                self._current_parents = (previous_config.config_line,)
                self.indent_level = self.get_leading_space_count(line)
                if not self.is_banner_start(line):
                    line = self._build_nested_config(line)  # type: ignore
                else:
                    line = self._build_banner(line)  # type: ignore
                    if line is not None and line[0].isspace():
                        line = self._build_nested_config(line)  # type: ignore
                    else:
                        self._current_parents = ()

            if line is None:
                break
            elif self.is_banner_start(line):
                line = self._build_banner(line)  # type: ignore
                # line can potentially be another banner start therefore we do a secondary check.
                if self.is_banner_start(line):
                    line = self._build_banner(line)  # type: ignore

            self._update_config_lines(line)
        return self.config_lines

BaseConfigParser

    def build_config_relationship(self) -> t.List[ConfigLine]:
        """Parse text tree of config lines and their parents."""
        raise NotImplementedError()
def find_all_children(self, pattern, match_type=exact): BaseSpaceConfigParser
    def find_all_children(self, pattern: str, match_type: str = "exact") -> t.List[str]:
        """Returns configuration part for a specific pattern not including parents.

        Args:
            pattern: pattern that describes parent.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_all_children(pattern="router bgp", match_type="startswith")
            >>> print(bgp_conf)
            ['router bgp 45000', '  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if (
                parents
                and self._match_type_check(parents, pattern, match_type)
                or self._match_type_check(cfg_line.config_line, pattern, match_type)
            ):
                config.append(cfg_line.config_line)
        return config
def find_children_w_parents(self, parent_pattern, child_pattern, match_type=exact): BaseSpaceConfigParser
    def find_children_w_parents(
        self, parent_pattern: str, child_pattern: str, match_type: str = "exact"
    ) -> t.List[str]:
        """Returns configuration part for a specific pattern including parents and children.

        Args:
            parent_pattern: pattern that describes parent.
            child_pattern: pattern that describes child.
            match_type (optional): Exact or regex. Defaults to "exact".

        Returns:
            configuration under that parent pattern.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''
            ... router bgp 45000
            ...   address-family ipv4 unicast
            ...    neighbor 192.168.1.2 activate
            ...    network 172.17.1.0 mask'''
            >>> bgp_conf = BaseSpaceConfigParser(str(config)).find_children_w_parents(parent_pattern="router bgp", child_pattern="  address-family", match_type="regex")
            >>> print(bgp_conf)
            ['  address-family ipv4 unicast', '   neighbor 192.168.1.2 activate', '   network 172.17.1.0 mask']
        """
        config = []
        potential_parents = [
            elem.parents[0]
            for elem in self.build_config_relationship()
            if self._match_type_check(elem.config_line, child_pattern, match_type)
        ]
        for cfg_line in self.build_config_relationship():
            parents = cfg_line.parents[0] if cfg_line.parents else None
            if parents in potential_parents and self._match_type_check(
                parents,  # type: ignore[arg-type]
                parent_pattern,
                match_type,
            ):
                config.append(cfg_line.config_line)
        return config
def get_leading_space_count(config_line): BaseSpaceConfigParser
    @staticmethod
    def get_leading_space_count(config_line: str) -> int:
        r"""Determine how many spaces the ``config_line`` is indented.

        Args:
           config_line: A line of text in the config.

        Returns:
            The number of leading spaces.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> config = '''interface GigabitEthernet1\n description link to ISP'''
            >>> config_line = " description link to ISP"
            >>> indent_level = BaseSpaceConfigParser(config).get_leading_space_count(config_line)
            >>> indent_level
            1
            >>>
        """
        return len(config_line) - len(config_line.lstrip())
def is_banner_end(self, line): BaseSpaceConfigParser
    def is_banner_end(self, line: str) -> bool:
        """Determine if line ends the banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line ends banner, else False.
        """
        if self.banner_end in line:
            return True
        return False
def is_banner_start(self, line): BaseSpaceConfigParser
    def is_banner_start(self, line: str) -> bool:
        """Determine if the line starts a banner config.

        Args:
            line: The current config line in iteration.

        Returns:
            True if line starts banner, else False.
        """
        for banner_start in self.banner_start:
            if not line:
                return False
            if line.lstrip().startswith(banner_start):
                return True
        return False
def is_comment(self, line): BaseSpaceConfigParser
    def is_comment(self, line: str) -> bool:
        """Determine if line is a comment.

        Args:
            line: A config line from the device.

        Returns:
            True if line is a comment, else False.

        Examples:
            >>> from netutils.config.parser import BaseSpaceConfigParser
            >>> BaseSpaceConfigParser("interface Ethernet1/1").is_comment("interface Ethernet1/1")
            False
            >>> BaseSpaceConfigParser("!").is_comment("!")
            True
            >>>
        """
        for comment_char in self.comment_chars:
            if line.lstrip().startswith(comment_char):
                return True
        return False