diff --git a/ciscoconfparse/ciscoconfparse.py b/ciscoconfparse/ciscoconfparse.py index c191055..998817f 100644 --- a/ciscoconfparse/ciscoconfparse.py +++ b/ciscoconfparse/ciscoconfparse.py @@ -41,6 +41,13 @@ import re import os + +from deprecated import deprecated +from loguru import logger +import hier_config +import yaml +import toml + from ciscoconfparse.models_cisco import IOSHostnameLine, IOSRouteLine from ciscoconfparse.models_cisco import IOSIntfLine from ciscoconfparse.models_cisco import IOSAccessLine, IOSIntfGlobal @@ -91,16 +98,9 @@ from ciscoconfparse.errors import InvalidParameters from ciscoconfparse.errors import RequirementFailure - # Not using ccp_re yet... still a work in progress # from ciscoconfparse.ccp_util import ccp_re -from deprecated import deprecated -from loguru import logger -import hier_config -import yaml -import toml - ALL_IOS_FACTORY_CLASSES = [ IOSIntfLine, IOSRouteLine, @@ -207,11 +207,21 @@ def get_version_number(): return version +ENCODING = None +ACTIVE_LOGURU_HANDLERS = None +__author_email__ = r"mike /at\ pennington [dot] net" +__author__ = f"David Michael Pennington <{__author_email__}>" +__copyright__ = f'2007-{time.strftime("%Y")}, {__author__}' +__license__ = "GPLv3" +__status__ = "Production" +__version__ = None + + @logger.catch(reraise=True) def initialize_globals(): """Initialize ciscoconfparse global dunder-variables and a couple others.""" global ENCODING - global ACTIVE_LOGURU_HANDLERS + #global ACTIVE_LOGURU_HANDLERS global __author_email__ global __author__ global __copyright__ @@ -1126,8 +1136,8 @@ def list_matching_children( allow_none=True, debug=0, ): - ## I'm not using parent_obj.re_search_children() because - ## re_search_children() doesn't return None for no match... + # I'm not using parent_obj.re_search_children() because + # re_search_children() doesn't return None for no match... # FIXME: Insert debugging here... # print("PARENT "+str(parent_obj)) @@ -1327,7 +1337,7 @@ def find_interface_objects(self, intfspec, exactmatch=True): err_text = "This method requires exactmatch set True" logger.error(err_text) raise NotImplementedError(err_text) - ## TODO: implement ASAConfigLine.abbvs and others + # TODO: implement ASAConfigLine.abbvs and others else: err_text = "This method requires exactmatch set True" logger.error(err_text) @@ -1779,7 +1789,7 @@ def find_objects_w_child(self, parentspec, childspec, ignore_ws=False, recurse=F def find_parent_objects( self, parentspec, - childspec, + childspec=None, ignore_ws=False, recurse=False, escape_chars=False, @@ -1792,7 +1802,7 @@ def find_parent_objects( Parameters ---------- - parentspec : str + parentspec : str or list Text regular expression for the :class:`~models_cisco.IOSCfgLine` object to be matched; this must match the parent's line childspec : str Text regular expression for the line to be matched; this must match the child's line @@ -1870,9 +1880,42 @@ def find_parent_objects( [, ] >>> """ - if isinstance(parentspec, BaseCfgLine): parentspec = parentspec.text + elif isinstance(parentspec, str): + pass + elif isinstance(parentspec, (list, tuple)): + if len(parentspec) > 1: + _results = set() + _parentspec = parentspec[0] + for _childspec in parentspec[1:]: + _values = self.find_parent_objects( + _parentspec, + _childspec, + ignore_ws=ignore_ws, + recurse=recurse, + escape_chars=escape_chars + ) + if len(_values) == 0: + ###################################################### + # If any _childspec fails to match, we will hit this + # condition when that failure happens. + ###################################################### + return [] + else: + # Add the parent of this set of values + _ = [_results.add(ii) for ii in _values] + # Sort the de-duplicated results + return sorted(_results) + else: + error = f"`parentspec` {type(parentspec)} must be longer than one element." + logger.error(error) + raise InvalidParameters(error) + else: + error = f"Received unexpected `parentspec` {type(parentspec)}" + logger.error(error) + raise InvalidParameters(error) + if isinstance(childspec, BaseCfgLine): parentspec = childspec.text @@ -1895,7 +1938,7 @@ def find_parent_objects( ) # This method is on CiscoConfParse() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def find_objects_w_all_children( self, parentspec, @@ -2018,7 +2061,7 @@ def find_objects_w_all_children( return retval # This method is on CiscoConfParse() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def find_objects_w_missing_children( self, parentspec, @@ -2072,7 +2115,7 @@ def find_objects_w_missing_children( return retval # This method is on CiscoConfParse() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def find_parents_w_child(self, parentspec, childspec, ignore_ws=False): """Parse through all children matching childspec, and return a list of parents that matched the parentspec. Only the parent lines will be @@ -2160,7 +2203,7 @@ def find_parents_w_child(self, parentspec, childspec, ignore_ws=False): return [ii.text for ii in tmp] # This method is on CiscoConfParse() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def find_objects_wo_child(self, parentspec, childspec, ignore_ws=False, recurse=False): r"""Return a list of parent :class:`~models_cisco.IOSCfgLine` objects, which matched the ``parentspec`` and whose children did not match ``childspec``. Only the parent :class:`~models_cisco.IOSCfgLine` objects will be returned. For simplicity, this method only finds oldest_ancestors without immediate children that match. @@ -2181,7 +2224,7 @@ def find_objects_wo_child(self, parentspec, childspec, ignore_ws=False, recurse= return self.find_parent_objects_wo_child(parentspec, childspec, ignore_ws=ignore_ws, recurse=recurse) # This method is on CiscoConfParse() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def find_parent_objects_wo_child(self, parentspec, childspec, ignore_ws=False, recurse=False, escape_chars=False): r"""Return a list of parent :class:`~models_cisco.IOSCfgLine` objects, which matched the ``parentspec`` and whose children did not match ``childspec``. Only the parent :class:`~models_cisco.IOSCfgLine` objects will be returned. For simplicity, this method only finds oldest_ancestors without immediate children that match. @@ -2265,6 +2308,13 @@ def find_parent_objects_wo_child(self, parentspec, childspec, ignore_ws=False, r if isinstance(parentspec, BaseCfgLine): parentspec = parentspec.text + elif isinstance(parentspec, (list, tuple)): + ################################################################## + # Catch unsupported parentspec type here + ################################################################## + error = f"find_parents_objects_wo_child() `parentspec` does not support a {type(parentspec)}" + logger.error(error) + raise InvalidParameters(error) if isinstance(childspec, BaseCfgLine): parentspec = childspec.text @@ -2286,7 +2336,7 @@ def find_parent_objects_wo_child(self, parentspec, childspec, ignore_ws=False, r ] # This method is on CiscoConfParse() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def find_parents_wo_child(self, parentspec, childspec, ignore_ws=False): r"""Parse through all parents matching parentspec, and return a list of parents that did NOT have children match the childspec. For simplicity, this method only finds oldest_ancestors without immediate children that match. @@ -2373,7 +2423,7 @@ def find_parents_wo_child(self, parentspec, childspec, ignore_ws=False): return [ii.text for ii in tmp] # This method is on CiscoConfParse() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def find_children_w_parents(self, parentspec, childspec, ignore_ws=False): r"""Parse through the children of all parents matching parentspec, and return a list of children that matched the childspec. @@ -2484,7 +2534,7 @@ def find_children_w_parents(self, parentspec, childspec, ignore_ws=False): return [ii.text for ii in sorted(retval)] # This method is on CiscoConfParse() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def find_objects_w_parents(self, parentspec, childspec, ignore_ws=False): r"""Parse through the children of all parents matching parentspec, and return a list of child objects, which matched the childspec. @@ -2508,14 +2558,21 @@ def find_objects_w_parents(self, parentspec, childspec, ignore_ws=False): return self.find_child_objects(parentspec, childspec, ignore_ws=ignore_ws, recurse=False) # This method is on CiscoConfParse() - @logger.catch(reraise=True) - def find_child_objects(self, parentspec, childspec, ignore_ws=False, recurse=False, escape_chars=False): + @ logger.catch(reraise=True) + def find_child_objects( + self, + parentspec, + childspec=None, + ignore_ws=False, + recurse=False, + escape_chars=False + ): r"""Parse through the children of all parents matching parentspec, and return a list of child objects, which matched the childspec. Parameters ---------- - parentspec : str + parentspec : str or list Text regular expression for the line to be matched; this must match the parent's line childspec : str Text regular expression for the line to be matched; this must match the child's line @@ -2596,9 +2653,46 @@ def find_child_objects(self, parentspec, childspec, ignore_ws=False, recurse=Fal >>> """ - if isinstance(parentspec, BaseCfgLine): parentspec = parentspec.text + elif isinstance(parentspec, str): + pass + elif isinstance(parentspec, (list, tuple)): + _parentspec_len = len(parentspec) + if _parentspec_len > 1: + _results = set() + _parentspec = parentspec[0] + for _idx, _childspec in enumerate(parentspec[1:]): + _values = self.find_child_objects( + _parentspec, + _childspec, + ignore_ws=ignore_ws, + recurse=recurse, + escape_chars=escape_chars + ) + if len(_values) == 0: + ###################################################### + # If any _childspec fails to match, we will hit this + # condition when that failure happens. + ###################################################### + return [] + elif _idx == _parentspec_len - 2: + ###################################################### + # Add the matching last child of this set of values + # '_parentspec_len - 2' was used intentionally + ###################################################### + _ = [_results.add(ii) for ii in _values] + # Sort the de-duplicated results + return sorted(_results) + else: + error = f"`parentspec` {type(parentspec)} must be longer than one element." + logger.error(error) + raise InvalidParameters(error) + else: + error = f"Received unexpected `parentspec` {type(parentspec)}" + logger.error(error) + raise InvalidParameters(error) + if isinstance(childspec, BaseCfgLine): parentspec = childspec.text @@ -2636,7 +2730,7 @@ def find_child_objects(self, parentspec, childspec, ignore_ws=False, recurse=Fal return sorted(retval) # This method is on CiscoConfParse() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def find_lineage(self, linespec, exactmatch=False): """ Iterate through to the oldest ancestor of this object, and return @@ -2665,7 +2759,7 @@ def find_lineage(self, linespec, exactmatch=False): return [obj.text for obj in tmp[0].lineage] # This method is on CiscoConfParse() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def has_line_with(self, linespec): """Return True if `linespec` is contained in the configuration.""" # https://stackoverflow.com/a/16097112/667301 @@ -2678,7 +2772,7 @@ def has_line_with(self, linespec): return bool(matching_conftext) # This method is on CiscoConfParse() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def insert_before( self, exist_val=None, @@ -2800,7 +2894,7 @@ def insert_before( return [ii.text for ii in sorted(objs)] # This method is on CiscoConfParse() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def insert_after( self, exist_val=None, @@ -2918,7 +3012,7 @@ def insert_after( return [ii.text for ii in sorted(objs)] # This method is on CiscoConfParse() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def insert_after_child( self, parentspec, @@ -2962,7 +3056,7 @@ def insert_after_child( return retval # This method is on CiscoConfParse() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def delete_lines(self, linespec, exactmatch=False, ignore_ws=False): """Find all :class:`~models_cisco.IOSCfgLine` objects whose text matches linespec, and delete the object""" @@ -2974,7 +3068,7 @@ def delete_lines(self, linespec, exactmatch=False, ignore_ws=False): obj.delete() # This method is on CiscoConfParse() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def prepend_line(self, linespec): """Unconditionally insert an :class:`~models_cisco.IOSCfgLine` object for ``linespec`` (a text line) at the top of the configuration""" @@ -2982,7 +3076,7 @@ def prepend_line(self, linespec): return self.ConfigObjs[0] # This method is on CiscoConfParse() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def append_line(self, linespec): """Unconditionally insert ``linespec`` (a text line) at the end of the configuration @@ -3013,7 +3107,7 @@ def append_line(self, linespec): return self.ConfigObjs[-1] # This method is on CiscoConfParse() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def replace_lines( self, linespec, @@ -3129,7 +3223,7 @@ class ALL return retval # This method is on CiscoConfParse() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def replace_children( self, parentspec, @@ -3216,7 +3310,7 @@ def replace_children( return retval # This method is on CiscoConfParse() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def replace_all_children( self, parentspec, @@ -3251,7 +3345,7 @@ def replace_all_children( return retval # This method is on CiscoConfParse() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def re_search_children(self, regexspec, recurse=False): """Use ``regexspec`` to search for root parents in the config with text matching regex. If `recurse` is False, only root parent objects are returned. A list of matching objects is returned. @@ -3279,7 +3373,7 @@ def re_search_children(self, regexspec, recurse=False): return [obj for obj in self.find_objects(regexspec)] # This method is on CiscoConfParse() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def re_match_iter_typed( self, regexspec, @@ -3384,8 +3478,8 @@ def re_match_iter_typed( return result_type(default) # This method is on CiscoConfParse() - @logger.catch(reraise=True) - @deprecated( + @ logger.catch(reraise=True) + @ deprecated( reason="req_cfgspec_all_diff() is obsolete; use Diff() instead. req_cfgspec_all_diff() will be removed", version="1.7.0", ) @@ -3456,8 +3550,8 @@ def req_cfgspec_all_diff(self, cfgspec, ignore_ws=False): return retval # This method is on CiscoConfParse() - @logger.catch(reraise=True) - @deprecated( + @ logger.catch(reraise=True) + @ deprecated( reason="req_cfgspec_excl_diff() is obsolete; use Diff() instead. req_cfgspec_excl_diff() will be removed", version="1.7.0", ) @@ -3538,7 +3632,7 @@ def req_cfgspec_excl_diff(self, linespec, uncfgspec, cfgspec): return retval # This method is on CiscoConfParse() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def _sequence_nonparent_lines(self, a_nonparent_objs, b_nonparent_objs): """Assume a_nonparent_objs is the existing config sequence, and b_nonparent_objs is the *desired* config sequence @@ -3582,7 +3676,7 @@ def _sequence_nonparent_lines(self, a_nonparent_objs, b_nonparent_objs): return a_parse, a_lines, a_linenums # This method is on CiscoConfParse() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def _sequence_parent_lines(self, a_parent_objs, b_parent_objs): """Assume a_parent_objs is the existing config sequence, and b_parent_objs is the *desired* config sequence @@ -3647,8 +3741,8 @@ def _sequence_parent_lines(self, a_parent_objs, b_parent_objs): return a_parse, a_lines, a_linenums # This method is on CiscoConfParse() - @logger.catch(reraise=True) - @deprecated( + @ logger.catch(reraise=True) + @ deprecated( reason="sync_diff() is obsolete; use Diff() instead. sync_diff() will be removed", version="1.9.40", ) @@ -3805,7 +3899,7 @@ def sync_diff( return retval # This method is on CiscoConfParse() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def save_as(self, filepath): """Save a text copy of the configuration at ``filepath``; this method uses the OperatingSystem's native line separators (such as @@ -3823,7 +3917,7 @@ def save_as(self, filepath): ### or iterable of objects instead of the configuration text itself. # This method is on CiscoConfParse() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def _find_line_OBJ(self, linespec, exactmatch=False): """SEMI-PRIVATE: Find objects whose text matches the linespec""" @@ -3850,7 +3944,7 @@ def _find_line_OBJ(self, linespec, exactmatch=False): ) # This method is on CiscoConfParse() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def _find_sibling_OBJ(self, lineobject): """SEMI-PRIVATE: Takes a singe object and returns a list of sibling objects""" @@ -3858,7 +3952,7 @@ def _find_sibling_OBJ(self, lineobject): return siblings # This method is on CiscoConfParse() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def _find_all_child_OBJ(self, lineobject): """SEMI-PRIVATE: Takes a single object and returns a list of decendants in all 'children' / 'grandchildren' / etc... after it. @@ -3873,7 +3967,7 @@ def _find_all_child_OBJ(self, lineobject): return retval # This method is on CiscoConfParse() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def _unique_OBJ(self, objectlist): """SEMI-PRIVATE: Returns a list of unique objects (i.e. with no duplicates). @@ -3885,7 +3979,7 @@ def _unique_OBJ(self, objectlist): return sorted(retval) # This method is on CiscoConfParse() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def _objects_to_uncfg(self, objectlist, unconflist): # Used by req_cfgspec_excl_diff() retval = [] @@ -3902,7 +3996,7 @@ def _objects_to_uncfg(self, objectlist, unconflist): class Diff(object): - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def __init__(self, hostname=None, old_config=None, new_config=None, syntax='ios'): """ Initialize Diff(). @@ -3990,7 +4084,7 @@ def __init__(self, hostname=None, old_config=None, new_config=None, syntax='ios' # New configuration self.host.load_generated_config(new_config) - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def diff(self): """ diff() returns the list of required configuration statements to go from the old_config to the new_config @@ -4001,7 +4095,7 @@ def diff(self): retval.append(obj.cisco_style_text()) return retval - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def rollback(self): """ rollback() returns the list of required configuration statements to rollback from the new_config to the old_config @@ -4017,7 +4111,7 @@ class HDiff(object): """An object to implement diffs against configs or config templates. By default, the output diffs are ordered roughly as before_config, then after_config. HDiff() is intended as an internal CiscoConfParse building-block to implement methods such as :meth:`ciscoconfparse.CiscoConfParse.sync_diff`""" # This method is on HDiff() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def __init__( self, before_config=None, @@ -4179,7 +4273,7 @@ def __init__( # FIXME - I think nxos is not going to work as-expected while using `build_ios_diffs()` self.build_ios_diffs() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def parse_hdiff_configs(self): if self.debug > 1: @@ -4200,7 +4294,7 @@ def parse_hdiff_configs(self): ignore_blank_lines=self.ignore_blank_lines, ) - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def build_diff_obj_lists(self): """Assign the `diff_side` attribute to parse_before and parse_after *CfgLine() instances""" @@ -4228,7 +4322,7 @@ def build_diff_obj_lists(self): ) # This method is on HDiff() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def build_diff_obj_list( self, parse=None, default_diff_word=None, ): @@ -4255,7 +4349,7 @@ def build_diff_obj_list( return retval # This method is on HDiff() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def build_ios_diffs(self): # Handle add / move / change. change is diff_word: remove + diff_word: add for after_obj in self.after_obj_list: @@ -4295,7 +4389,7 @@ def build_ios_diffs(self): self.sort_lines() # This method is on HDiff() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def unified_diff_header(self): """ Return a unified diff header similar to this... @@ -4350,7 +4444,7 @@ def unified_diff_header(self): return unified_diff_header # This method is on HDiff() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def unified_diffs(self, header=True): """ Return a python list of text which contains the unified diff of the @@ -4369,7 +4463,7 @@ def unified_diffs(self, header=True): return unified_diff_list # This method is on HDiff() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def unified_diffs_contents(self, header=True): """ Return a python list of unified diff contents which contain the unified diff of the @@ -4390,7 +4484,7 @@ def unified_diffs_contents(self, header=True): return unified_diff_list # This method is on HDiff() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def sort_lines(self, after_lines=None): """ Typical output line dict-format... @@ -4413,7 +4507,7 @@ def sort_lines(self, after_lines=None): raise NotImplementedError # This method is on HDiff() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def dict_diffs(self, before_obj_list, after_obj_list): ############################################ # Render diffs @@ -4451,7 +4545,7 @@ def dict_diffs(self, before_obj_list, after_obj_list): return all_dict_lines # This method is on HDiff() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def find_in_before_obj_list( self, before_obj_list, after_obj, consider_whitespace=False, debug=0 ): @@ -4547,7 +4641,7 @@ def find_in_before_obj_list( return before_obj_list, after_obj # This method is on HDiff() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def render_after_obj_diffs(self, aobj=None): """ Print after_obj (aobj) diffs to stdout. before_obj should not be @@ -4591,7 +4685,7 @@ def render_after_obj_diffs(self, aobj=None): return output # This method is on HDiff() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def compress_dict_diffs(self, all_lines=None, debug=0): """ Summary @@ -4782,7 +4876,7 @@ class ConfigList(MutableSequence): debug = None _list = [] - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def __init__( self, initlist=None, @@ -4896,7 +4990,7 @@ def __init__( self._network_cache = {} # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def __repr__(self): return """""".format( self.syntax, @@ -4904,47 +4998,47 @@ def __repr__(self): self._list, ) - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def __iter__(self): return iter(self._list) # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def __lt__(self, other): return self._list < self.__cast(other) # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def __le__(self, other): return self._list < self.__cast(other) # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def __eq__(self, other): return self._list == self.__cast(other) # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def __gt__(self, other): return self._list > self.__cast(other) # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def __ge__(self, other): return self._list >= self.__cast(other) # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def __cast(self, other): return other._list if isinstance(other, ConfigList) else other # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def __len__(self): return len(self._list) # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def __getitem__(self, ii): if isinstance(ii, slice): return self.__class__(self._list[ii]) @@ -4952,19 +5046,19 @@ def __getitem__(self, ii): return self._list[ii] # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def __setitem__(self, ii, val): self._list[ii] = val # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def __delitem__(self, ii): del self._list[ii] #self._bootstrap_from_text() self._list = self.bootstrap_obj_init_ng(self.ioscfg, debug=self.debug) # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def __add__(self, other): if isinstance(other, ConfigList): return self.__class__(self._list + other._list) @@ -4973,7 +5067,7 @@ def __add__(self, other): return self.__class__(self._list + list(other)) # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def __radd__(self, other): if isinstance(other, ConfigList): return self.__class__(other._list + self._list) @@ -4982,7 +5076,7 @@ def __radd__(self, other): return self.__class__(list(other) + self._list) # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def __iadd__(self, other): if isinstance(other, ConfigList): self._list += other._list @@ -4993,19 +5087,19 @@ def __iadd__(self, other): return self # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def __mul__(self, val): return self.__class__(self._list * val) __rmul__ = __mul__ - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def __imul__(self, val): self._list *= val return self # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def __copy__(self): inst = self.__class__.__new__(self.__class__) inst.__dict__.update(self.__dict__) @@ -5014,25 +5108,25 @@ def __copy__(self): return inst # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def __str__(self): return self.__repr__() # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def __enter__(self): # Add support for with statements... # FIXME: *with* statements dont work yield from self._list # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def __exit__(self, *args, **kwargs): # FIXME: *with* statements dont work self._list[0].confobj.CiscoConfParse.atomic() # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def __getattribute__(self, arg): """Call arg on ConfigList() object, and if that fails, call arg from the ccp_ref attribute""" # Try a method call on ASAConfigList() @@ -5056,8 +5150,8 @@ def __getattribute__(self, arg): return ccp_method # This method is on ConfigList() - @junos_unsupported - @logger.catch(reraise=True) + @ junos_unsupported + @ logger.catch(reraise=True) def append(self, val): if self.debug >= 1: logger.debug(" ConfigList().append(val={}) was called.".format(val)) @@ -5065,47 +5159,47 @@ def append(self, val): self._list.append(val) # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def pop(self, ii=-1): return self._list.pop(ii) # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def remove(self, val): self._list.remove(val) # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def clear(self): self._list.clear() # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def copy(self): return self.__class__(self) # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def count(self, val): return self._list.count(val) # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def index(self, val, *args): return self._list.index(val, *args) # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def reverse(self): self._list.reverse() # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def sort(self, _unknown_arg, *args, **kwds): self._list.sort(*args, **kwds) # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def extend(self, other): if isinstance(other, ConfigList): self._list.extend(other._list) @@ -5113,7 +5207,7 @@ def extend(self, other): self._list.extend(other) # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def has_line_with(self, linespec): # https://stackoverflow.com/a/16097112/667301 matching_conftext = list( @@ -5125,8 +5219,8 @@ def has_line_with(self, linespec): return bool(matching_conftext) # This method is on ConfigList() - @junos_unsupported - @logger.catch(reraise=True) + @ junos_unsupported + @ logger.catch(reraise=True) def insert_before_deprecated(self, exist_val, new_val, atomic=False): """ Insert new_val before all occurances of exist_val. @@ -5161,7 +5255,7 @@ def insert_before_deprecated(self, exist_val, new_val, atomic=False): ############################################################################## # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def insert_before(self, exist_val=None, new_val=None, atomic=False): """ Insert new_val before all occurances of exist_val. @@ -5272,8 +5366,8 @@ def insert_before(self, exist_val=None, new_val=None, atomic=False): self.reassign_linenums() # This method is on ConfigList() - @junos_unsupported - @logger.catch(reraise=True) + @ junos_unsupported + @ logger.catch(reraise=True) def insert_after(self, exist_val=None, new_val=None, atomic=False, new_val_indent=-1): """ Insert new_val after all occurances of exist_val. @@ -5392,8 +5486,8 @@ def insert_after(self, exist_val=None, new_val=None, atomic=False, new_val_inden self.reassign_linenums() # This method is on ConfigList() - @junos_unsupported - @logger.catch(reraise=True) + @ junos_unsupported + @ logger.catch(reraise=True) def insert(self, ii, val): if not isinstance(ii, int): raise ValueError @@ -5429,7 +5523,7 @@ def insert(self, ii, val): self.reassign_linenums() # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def config_hierarchy(self): """Walk this configuration and return the following tuple at each parent 'level': (list_of_parent_sibling_objs, list_of_nonparent_sibling_objs) @@ -5449,7 +5543,7 @@ def config_hierarchy(self): return parent_siblings, nonparent_siblings # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def _banner_mark_regex(self, regex): """ Use the regex input parameter to identify all banner parent @@ -5550,7 +5644,7 @@ def _banner_mark_regex(self, regex): break # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def _macro_mark_children(self, macro_parent_idx_list): """ Set the blank_line_keep attribute for all banner parent / child objs. @@ -5578,7 +5672,7 @@ def _macro_mark_children(self, macro_parent_idx_list): finished = True # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def _maintain_bootstrap_parent_cache(self, parents_cache, indent, max_indent, is_config_line): ## Parent cache: ## Maintain indent vs max_indent in a family and @@ -5602,7 +5696,7 @@ def _maintain_bootstrap_parent_cache(self, parents_cache, indent, max_indent, is return parents_cache, parent - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def _build_bootstrap_parent_child(self, retval, parents_cache, parent, idx, indent, obj, debug,): candidate_parent = None candidate_parent_idx = None @@ -5639,7 +5733,7 @@ def _build_bootstrap_parent_child(self, retval, parents_cache, parent, idx, inde return retval, parents_cache, parent # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def bootstrap_obj_init_ng(self, text_list=None, debug=0): """ Accept a text list, and format into a list of *CfgLine() objects. @@ -5737,7 +5831,7 @@ def bootstrap_obj_init_ng(self, text_list=None, debug=0): return retval # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def _build_banner_re_ios(self): """Return a banner regexp for IOS (and at this point, NXOS).""" banner_str = { @@ -5757,7 +5851,7 @@ def _build_banner_re_ios(self): return banner_re # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def _add_child_to_parent(self, _list, idx, indent, parentobj, childobj): ## parentobj could be None when trying to add a child that should not ## have a parent @@ -5796,35 +5890,35 @@ def _add_child_to_parent(self, _list, idx, indent, parentobj, childobj): pass # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def iter_with_comments(self, begin_index=0): for idx, obj in enumerate(self._list): if idx >= begin_index: yield obj # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def iter_no_comments(self, begin_index=0): for idx, obj in enumerate(self._list): if (idx >= begin_index) and (not obj.is_comment): yield obj # This method is on ConfigList() - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def reassign_linenums(self): # Call this after any insertion or deletion for idx, obj in enumerate(self._list): obj.linenum = idx # This method is on ConfigList() - @property - @logger.catch(reraise=True) + @ property + @ logger.catch(reraise=True) def all_parents(self): return [obj for obj in self._list if obj.has_children] # This method is on ConfigList() - @property - @logger.catch(reraise=True) + @ property + @ logger.catch(reraise=True) def last_index(self): return self.__len__() - 1 @@ -5833,8 +5927,8 @@ def last_index(self): ########################################################################## # This method was on ASAConfigList(); now tentatively on ConfigList() - @property - @logger.catch(reraise=True) + @ property + @ logger.catch(reraise=True) def names(self): """Return a dictionary of name to address mappings""" if self.syntax != "asa": @@ -5849,8 +5943,8 @@ def names(self): return retval # This method was on ASAConfigList(); now tentatively on ConfigList() - @property - @logger.catch(reraise=True) + @ property + @ logger.catch(reraise=True) def object_group_network(self): """Return a dictionary of name to object-group network mappings""" if self.syntax != "asa": @@ -5864,8 +5958,8 @@ def object_group_network(self): return retval # This method was on ASAConfigList(); now tentatively on ConfigList() - @property - @logger.catch(reraise=True) + @ property + @ logger.catch(reraise=True) def access_list(self): """Return a dictionary of ACL name to ACE (list) mappings""" if self.syntax != "asa": @@ -5890,23 +5984,23 @@ def access_list(self): class DiffObject(object): """This object should be used at every level of hierarchy""" - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def __init__(self, level, nonparents, parents): self.level = level self.nonparents = nonparents self.parents = parents - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def __repr__(self): return "".format(self.level) class CiscoPassword(object): - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def __init__(self, ep=""): self.ep = ep - @logger.catch(reraise=True) + @ logger.catch(reraise=True) def decrypt(self, ep=""): """Cisco Type 7 password decryption. Converted from perl code that was written by jbash [~at~] cisco.com; enhancements suggested by @@ -5992,7 +6086,7 @@ def decrypt(self, ep=""): return dp -@logger.catch(reraise=True) +@ logger.catch(reraise=True) def config_line_factory(all_lines=None, line=None, comment_delimiter="!", syntax="ios", debug=0): """A factory method to assign a custom BaseCfgLine() subclass based on `all_lines`, `line`, `comment_delimiter`, and `syntax` parameters.""" # Complicted & Buggy @@ -6072,8 +6166,7 @@ def config_line_factory(all_lines=None, line=None, comment_delimiter="!", syntax return IOSCfgLine(all_lines=all_lines, line=line, comment_delimiter=comment_delimiter) -### TODO: Add unit tests below -if __name__ == "__main__": +def parse_global_options(): import optparse pp = optparse.OptionParser() @@ -6163,6 +6256,11 @@ def config_line_factory(all_lines=None, line=None, comment_delimiter="!", syntax for line in diff: print(line) else: - error = "ciscoconfparse was called with unknown parameters" - logger.error(error) - raise RuntimeError(error) + opt_error = "ciscoconfparse was called with unknown parameters" + logger.error(opt_error) + raise RuntimeError(opt_error) + + +# TODO: Add unit tests below +if __name__ == "__main__": + parse_global_options()