Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-ciscoconfparse for openSUSE:Factory checked in at 2022-10-29 20:16:49 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-ciscoconfparse (Old) and /work/SRC/openSUSE:Factory/.python-ciscoconfparse.new.2275 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-ciscoconfparse" Sat Oct 29 20:16:49 2022 rev:25 rq:1032073 version:1.6.50 Changes: -------- --- /work/SRC/openSUSE:Factory/python-ciscoconfparse/python-ciscoconfparse.changes 2022-10-21 16:20:14.742251416 +0200 +++ /work/SRC/openSUSE:Factory/.python-ciscoconfparse.new.2275/python-ciscoconfparse.changes 2022-10-29 20:18:05.490525468 +0200 @@ -1,0 +2,22 @@ +Thu Oct 27 22:29:08 UTC 2022 - Yogalakshmi Arunachalam <yarunacha...@suse.com> + +- Update to version 1.6.50: + Released: 2022-10-21 + Summary: + * Modify .github/workflows/tests.yml with improved yml + * Enhance CiscoConfParse().repr() string output + * Minor package documentation tweaks + +- Update to version 1.6.49: + * Adjust string strip() conditions on config lines in assign_parent_to_closing_braces() + +- Update to version 1.6.48: + * Add code to catch misconfigurations such as parsing a string instead of a list in ciscoconfparse.ConfigList(``) + * Avoid problems with reading empty lines (see Github Issue #251) + +- Update to version 1.6.47: + * Add repo version management into the Makefile ('make bump-version-patch' / 'make bump-version-minor') + * Add repo version management to 'dev_tools/git_helper.py' + * Revise README_git_workflow.md to include more rebase and merge details + +------------------------------------------------------------------- Old: ---- ciscoconfparse-1.6.41.tar.gz New: ---- ciscoconfparse-1.6.50.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-ciscoconfparse.spec ++++++ --- /var/tmp/diff_new_pack.dKY79w/_old 2022-10-29 20:18:05.986528111 +0200 +++ /var/tmp/diff_new_pack.dKY79w/_new 2022-10-29 20:18:05.994528153 +0200 @@ -19,7 +19,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} %bcond_without python2 Name: python-ciscoconfparse -Version: 1.6.41 +Version: 1.6.50 Release: 0 Summary: Library for parsing, querying and modifying Cisco IOS-style configurations License: GPL-3.0-or-later ++++++ ciscoconfparse-1.6.41.tar.gz -> ciscoconfparse-1.6.50.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ciscoconfparse-1.6.41/PKG-INFO new/ciscoconfparse-1.6.50/PKG-INFO --- old/ciscoconfparse-1.6.41/PKG-INFO 2022-09-10 00:29:24.075619200 +0200 +++ new/ciscoconfparse-1.6.50/PKG-INFO 1970-01-01 01:00:00.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: ciscoconfparse -Version: 1.6.41 +Version: 1.6.50 Summary: Parse, Audit, Query, Build, and Modify Cisco IOS-style and JunOS-style configurations Home-page: https://github.com/mpenning/ciscoconfparse License: GPLv3 @@ -21,11 +21,12 @@ Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 Classifier: Topic :: Communications Classifier: Topic :: Internet Classifier: Topic :: Software Development :: Libraries :: Python Modules @@ -152,7 +153,7 @@ Pre-requisites -------------- -[The ciscoconfparse python package][3] requires Python versions 3.6+ (note: Python version 3.7.0 has a bug - ref [Github issue \#117][18], but version 3.7.1 works); the OS should not matter. +[The ciscoconfparse python package][3] requires Python versions 3.7+ (note: Python version 3.7.0 has a bug - ref [Github issue \#117][18], but version 3.7.1 works); the OS should not matter. Installation and Downloads -------------------------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ciscoconfparse-1.6.41/README.md new/ciscoconfparse-1.6.50/README.md --- old/ciscoconfparse-1.6.41/README.md 2022-05-26 11:47:08.097129000 +0200 +++ new/ciscoconfparse-1.6.50/README.md 2022-10-20 13:10:54.100284000 +0200 @@ -112,7 +112,7 @@ Pre-requisites -------------- -[The ciscoconfparse python package][3] requires Python versions 3.6+ (note: Python version 3.7.0 has a bug - ref [Github issue \#117][18], but version 3.7.1 works); the OS should not matter. +[The ciscoconfparse python package][3] requires Python versions 3.7+ (note: Python version 3.7.0 has a bug - ref [Github issue \#117][18], but version 3.7.1 works); the OS should not matter. Installation and Downloads -------------------------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ciscoconfparse-1.6.41/ciscoconfparse/ccp_abc.py new/ciscoconfparse-1.6.50/ciscoconfparse/ccp_abc.py --- old/ciscoconfparse-1.6.41/ciscoconfparse/ccp_abc.py 2022-07-20 02:06:20.741461800 +0200 +++ new/ciscoconfparse-1.6.50/ciscoconfparse/ccp_abc.py 2022-10-20 13:10:54.100284000 +0200 @@ -18,7 +18,6 @@ """ from difflib import get_close_matches -from operator import methodcaller from abc import ABCMeta import warnings import inspect @@ -538,10 +537,8 @@ ! >>> """ - cobjs = filter(methodcaller("re_search", linespec), self.children) - retval = [ii.text for ii in cobjs] - # Delete the children - map(methodcaller("delete"), cobjs) + # if / else in a list comprehension... ref ---> https://stackoverflow.com/a/9442777/667301 + retval = [(obj.delete() if obj.re_search(linespec) else obj) for obj in self.children] return retval # On BaseCfgLine() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ciscoconfparse-1.6.41/ciscoconfparse/ccp_util.py new/ciscoconfparse-1.6.50/ciscoconfparse/ccp_util.py --- old/ciscoconfparse-1.6.41/ciscoconfparse/ccp_util.py 2022-09-10 00:25:02.491401000 +0200 +++ new/ciscoconfparse-1.6.50/ciscoconfparse/ccp_util.py 2022-10-20 13:10:54.100284000 +0200 @@ -143,7 +143,7 @@ """ - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def __init__(self): self.PYTHONOPTIMIZE_env_value = os.environ.get("PYTHONOPTIMIZE", None) @@ -178,8 +178,136 @@ if error != "__no_error__": raise PythonOptimizeException(error) +@logger.catch(reraise=True) +def ccp_logger_control( + sink=sys.stderr, + action="", + handler_id=None, + allow_enqueue=True, + # rotation="00:00", + # retention="1 month", + # compression="zip", + level="DEBUG", + colorize=True, + debug=0, +): + """ + A simple function to handle logging... Enable / Disable all + ciscoconfparse logging here... also see Github issue #211. + + Example + ------- + """ + + msg = "ccp_logger_control() was called with sink='{}', action='{}', handler_id='{}', allow_enqueue={}, level='{}', colorize={}, debug={}".format( + sink, + action, + handler_id, + allow_enqueue, + # rotation, + # retention, + # compression, + level, + colorize, + debug, + ) + if debug > 0: + logger.info(msg) + + assert isinstance(action, str) + assert action in ("remove", "add", "disable", "enable", "",) + + package_name = "ciscoconfparse" + + if action == "remove": + # Require an explicit loguru handler_id to remove... + assert isinstance(handler_id, int) + + logger.remove(handler_id) + return True + + elif action == "disable": + # Administratively disable this loguru logger + logger.disable(package_name) + return True + + elif action == "enable": + # Administratively enable this loguru logger + logger.enable(package_name) + return True + + elif action == "add": + + logger.add( + sink=sink, + diagnose=True, + backtrace=True, + # https://github.com/mpenning/ciscoconfparse/issues/215 + enqueue=allow_enqueue, + serialize=False, + catch=True, + # rotation="00:00", + # retention="1 day", + # compression="zip", + colorize=True, + level="DEBUG", + ) + logger.enable(package_name) + return True + + elif action == "": + raise ValueError( + "action='' is not supported. Please use a valid action keyword" + ) + + else: + raise NotImplementedError( + "action='%s' is an unsupported logger action" % action + ) + + +@logger.catch(reraise=True) +def configure_loguru( + sink=sys.stderr, + action="", + # rotation="midnight", + # retention="1 month", + # compression="zip", + level="DEBUG", + colorize=True, + debug=0, +): + """ + configure_loguru() + """ + assert isinstance(action, str) + assert action in ('remove', 'add', 'enable', 'disable', '',) + # assert isinstance(rotation, str) + # assert isinstance(retention, str) + # assert isinstance(compression, str) + # assert compression == "zip" + assert isinstance(level, str) + assert isinstance(colorize, bool) + assert isinstance(debug, int) and (0 <= debug <= 5) + + # logger_control() was imported above... + # Remove the default loguru logger to stderr (handler_id==0)... + ccp_logger_control(action="remove", handler_id=0) + + # Add log to STDOUT + ccp_logger_control( + sink=sys.stdout, + action="add", + level="DEBUG", + # rotation='midnight', # ALE barks about the rotation keyword... + # retention="1 month", + # compression=compression, + colorize=colorize + ) + ccp_logger_control(action="enable") + -@logger.catch(default=True, onerror=lambda _: sys.exit(1)) +@logger.catch(reraise=True) def as_text_list(object_list): """ This is a helper-function to convert a list of configuration objects into @@ -217,11 +345,11 @@ return list(map(attrgetter("text"), object_list)) -@logger.catch(default=True, onerror=lambda _: sys.exit(1)) +@logger.catch(reraise=True) def junos_unsupported(func): """A function wrapper to warn junos users of unsupported features""" - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def wrapper(*args, **kwargs): warn = "syntax='junos' does not fully support config modifications such as .{}(); see Github Issue #185. https://github.com/mpenning/ciscoconfparse/issues/185".format( func.__name__ @@ -240,7 +368,7 @@ return wrapper -@logger.catch(default=True, onerror=lambda _: sys.exit(1)) +@logger.catch(reraise=True) def log_function_call(function=None, *args, **kwargs): """A wrapper; this decorator uses loguru to log function calls. @@ -253,7 +381,7 @@ """ - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def logging_decorator(ff): @wraps(ff) def wrapped_logging(*args, **kwargs): @@ -283,93 +411,6 @@ return logging_decorator -@logger.catch(default=True, onerror=lambda _: sys.exit(1)) -def ccp_logger_control( - sink=sys.stderr, - action="", - handler_id=None, - allow_enqueue=True, - # rotation="00:00", - # retention="1 month", - # compression="zip", - level="DEBUG", - colorize=True, - debug=0, -): - """ - A simple function to handle logging... Enable / Disable all - ciscoconfparse logging here... also see Github issue #211. - - Example - ------- - """ - - msg = "ccp_logger_control() was called with sink='{}', action='{}', handler_id='{}', allow_enqueue={}, level='{}', colorize={}, debug={}".format( - sink, - action, - handler_id, - allow_enqueue, - # rotation, - # retention, - # compression, - level, - colorize, - debug, - ) - if debug > 0: - logger.info(msg) - - assert isinstance(action, str) - assert action in ("remove", "add", "disable", "enable", "",) - - package_name = "ciscoconfparse" - - if action == "remove": - # Require an explicit loguru handler_id to remove... - assert isinstance(handler_id, int) - - logger.remove(handler_id) - return True - - elif action == "disable": - # Administratively disable this loguru logger - logger.disable(package_name) - return True - - elif action == "enable": - # Administratively enable this loguru logger - logger.enable(package_name) - return True - - elif action == "add": - - logger.add( - sink=sink, - diagnose=True, - backtrace=True, - # https://github.com/mpenning/ciscoconfparse/issues/215 - enqueue=allow_enqueue, - serialize=False, - catch=True, - # rotation="00:00", - # retention="1 day", - # compression="zip", - colorize=True, - level="DEBUG", - ) - logger.enable(package_name) - return True - - elif action == "": - raise ValueError( - "action='' is not supported. Please use a valid action keyword" - ) - - else: - raise NotImplementedError( - "action='%s' is an unsupported logger action" % action - ) - class __ccp_re__(object): """ @@ -402,7 +443,7 @@ """ - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def __init__(self, regex_str=r"", target_str=None, groups=None, flags=0, debug=0): assert isinstance(regex_str, str) @@ -628,7 +669,7 @@ error_str = "Cannot parse '%s' as ipv4 or ipv6" % val raise AddressValueError(error_str) -@logger.catch(default=True, onerror=lambda _: sys.exit(1)) +@logger.catch(reraise=True) def collapse_addresses(network_list): """ This is a ciscoconfparse proxy for ipaddress.collapse_addresses() @@ -642,7 +683,7 @@ """ assert isinstance(network_list, list) or isinstance(network_list, tuple) - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def ip_net(arg): if isinstance(arg, IPv4Obj): return arg.network @@ -2242,7 +2283,7 @@ ) -@logger.catch(default=True, onerror=lambda _: sys.exit(1)) +@logger.catch(reraise=True) def dns_query(input_str="", query_type="", server="", timeout=2.0): """A unified IPv4 & IPv6 DNS lookup interface; this is essentially just a wrapper around dnspython's API. When you query a PTR record, you can use an IPv4 or IPv6 address (which will automatically be converted into an in-addr.arpa name. This wrapper only supports a subset of DNS records: 'A', 'AAAA', 'CNAME', 'MX', 'NS', 'PTR', and 'TXT' @@ -2435,7 +2476,7 @@ return retval -@logger.catch(default=True, onerror=lambda _: sys.exit(1)) +@logger.catch(reraise=True) def dns_lookup(input_str, timeout=3, server="", record_type="A"): """Perform a simple DNS lookup, return results in a dictionary""" rr = Resolver() @@ -2475,7 +2516,7 @@ } -@logger.catch(default=True, onerror=lambda _: sys.exit(1)) +@logger.catch(reraise=True) def dns6_lookup(input_str, timeout=3, server=""): """Perform a simple DNS lookup, return results in a dictionary""" rr = Resolver() @@ -2500,7 +2541,7 @@ _REVERSE_DNS_REGEX = re.compile(r"^\s*\d+\.\d+\.\d+\.\d+\s*$") -@logger.catch(default=True, onerror=lambda _: sys.exit(1)) +@logger.catch(reraise=True) def check_valid_ipaddress(input_addr=None): """ Accept an input string with an IPv4 or IPv6 address. If the address is @@ -2529,13 +2570,14 @@ assert (ipaddr_family == 4 or ipaddr_family == 6), error return (input_addr, ipaddr_family) -@logger.catch(default=True, onerror=lambda _: sys.exit(1)) -def reverse_dns_lookup(input_str, timeout=3.0, server="", proto="udp"): +@logger.catch(reraise=True) +def reverse_dns_lookup(input_str, timeout=3.0, server="4.2.2.2", proto="udp"): """Perform a simple reverse DNS lookup on an IPv4 or IPv6 address; return results in a python dictionary""" assert isinstance(proto, str) and (proto=="udp" or proto=="tcp") assert isinstance(float(timeout), float) and float(timeout) > 0.0 - input_str, ipaddr_family = check_valid_ipaddress(input_str) + addr, addr_family = check_valid_ipaddress(input_str) + assert addr_family==4 or addr_family==6 tcp_flag = None if proto=="tcp": @@ -2546,12 +2588,16 @@ raise ValueError() rr = Resolver() + rr.nameservers = [] + + # Append valid addresses to rr.nameservers... + for candidate_server_addr in server.split(","): + addr, addr_family = check_valid_ipaddress(input_addr=candidate_server_addr) + rr.nameservers.append(addr) + #rr = rr.resolve_address(ipaddr=input_str, tcp=tcp_flag, rdtype="PTR", lifetime=float(timeout)) #rr = rr.resolve_address(ipaddr=input_str, tcp=tcp_flag, lifetime=float(timeout)) - if server != "": - rr.nameservers = [server] - records = [] retval = {} try: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ciscoconfparse-1.6.41/ciscoconfparse/ciscoconfparse.py new/ciscoconfparse-1.6.50/ciscoconfparse/ciscoconfparse.py --- old/ciscoconfparse-1.6.41/ciscoconfparse/ciscoconfparse.py 2022-09-10 00:25:02.491401000 +0200 +++ new/ciscoconfparse-1.6.50/ciscoconfparse/ciscoconfparse.py 2022-10-22 04:32:00.204709500 +0200 @@ -38,9 +38,6 @@ import re import os -from loguru import logger -import toml - from ciscoconfparse.models_cisco import IOSHostnameLine, IOSRouteLine from ciscoconfparse.models_cisco import IOSIntfLine from ciscoconfparse.models_cisco import IOSAccessLine, IOSIntfGlobal @@ -81,97 +78,110 @@ from ciscoconfparse.ccp_abc import BaseCfgLine from ciscoconfparse.ccp_util import junos_unsupported -from ciscoconfparse.ccp_util import ccp_logger_control +from ciscoconfparse.ccp_util import configure_loguru # Not using ccp_re yet... still a work in progress # from ciscoconfparse.ccp_util import ccp_re +from loguru import logger +import toml -@logger.catch(default=True, onerror=lambda _: sys.exit(1)) -def configure_loguru( - sink=sys.stderr, - action="", - # rotation="midnight", - # retention="1 month", - # compression="zip", - level="DEBUG", - colorize=True, - debug=0, -): +@logger.catch(reraise=True) +def get_version_number(): """ - configure_loguru() + Read the version number from 'pyproject.toml', or use version 0.0.0 in odd + circumstances. """ - assert isinstance(action, str) - assert action in ('remove', 'add', 'enable', 'disable', '',) - # assert isinstance(rotation, str) - # assert isinstance(retention, str) - # assert isinstance(compression, str) - # assert compression == "zip" - assert isinstance(level, str) - assert isinstance(colorize, bool) - assert isinstance(debug, int) and (0 <= debug <= 5) - - # logger_control() was imported above... - # Remove the default loguru logger to stderr (handler_id==0)... - ccp_logger_control(action="remove", handler_id=0) - -# _logger_config = logger.configure( -# sys.stdout, -# level="DEBUG", -# rotation='midnight', -# retention="1 month", -# compression=compression, -# colorize=colorize, -# ) -# logger.add(_logger_config) - - ccp_logger_control( - sink=sys.stdout, - action="add", - level="DEBUG", - # rotation='midnight', # ALE barks about the rotation keyword... - # retention="1 month", - # compression=compression, - colorize=colorize + # Docstring props: http://stackoverflow.com/a/1523456/667301 + # version: if-else below fixes Github issue #123 + + version = "0.0.0" # version read failed + + pyproject_toml_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "../pyproject.toml", ) - ccp_logger_control(action="enable") + if os.path.isfile(pyproject_toml_path): + # Retrieve the version number from pyproject.toml... + toml_values = dict() + with open(pyproject_toml_path, encoding=ENCODING) as fh: + toml_values = toml.loads(fh.read()) + version = toml_values["tool"]["poetry"].get("version", -1.0) + assert isinstance(version, str) -configure_loguru() + else: + # This is required for importing from a zipfile... Github issue #123 + version = "0.0.0" # __version__ read failed -ENCODING = locale.getpreferredencoding() -ALL_VALID_SYNTAX = ( - 'ios', - 'nxos', - 'asa', - 'junos', - 'terraform', -) - -# Docstring props: http://stackoverflow.com/a/1523456/667301 -# __version__ if-else below fixes Github issue #123 -pyproject_path = os.path.join( - os.path.dirname(os.path.abspath(__file__)), - "../pyproject.toml", -) -if os.path.isfile(pyproject_path): - # Retrieve the version number from pyproject.toml... - toml_values = dict() - with open(pyproject_path, encoding=ENCODING) as fh: - toml_values = toml.loads(fh.read()) - __version__ = toml_values.get("version") - -else: - # This case is required for importing from a zipfile... Github issue #123 - __version__ = "0.0.0" # __version__ read failed - -__author_email__ = r"mike /at\ pennington [dot] net" -__author__ = "David Michael Pennington <{}>".format(__author_email__) -__copyright__ = "2007-{}, {}".format(time.strftime("%Y"), __author__) -__license__ = "GPLv3" -__status__ = "Production" + return version +@logger.catch(reraise=True) +def initialize_globals(): + """ + Initialize ciscoconfparse global dunder variables and a couple others. + """ + global ALL_VALID_SYNTAX + global ENCODING + global __author_email__ + global __author__ + global __copyright__ + global __license__ + global __status__ + global __version__ + + ENCODING = locale.getpreferredencoding() + ALL_VALID_SYNTAX = ( + 'ios', + 'nxos', + 'asa', + 'junos', + 'terraform', + ) -@logger.catch(default=True, onerror=lambda _: sys.exit(1)) + try: + __author_email__ = r"mike /at\ pennington [dot] net" + __author__ = "David Michael Pennington <{}>".format(__author_email__) + __copyright__ = "2007-{}, {}".format(time.strftime("%Y"), __author__) + __license__ = "GPLv3" + __status__ = "Production" + __version__ = get_version_number() + + except: + raise ValueError() + + finally: + # These are all the 'dunder variables' required... + globals_dict = { + '__author_email__': __author_email__, + '__author__': __author__, + '__copyright__': __copyright__, + '__license__': __license__, + '__status__': __status__, + '__version__': __version__, + } + return globals_dict + +@logger.catch(reraise=True) +def initialize_ciscoconfparse(): + """ + Initialize ciscoconfparse global variables and configure logging + """ + configure_loguru() + + globals_dict = initialize_globals() + for key, value in globals_dict.items(): + # Example, this will set __version__ to content of 'value' + # from -> https://stackoverflow.com/a/3972978/667301 + globals()[key] = value + + return globals_dict + + +# ALL ciscoconfparse global variables initizalization happens here... +initialize_ciscoconfparse() + + +@logger.catch(reraise=True) def _parse_line_braces(line_txt=None, comment_delimiter=None) -> tuple: """ """ @@ -281,7 +291,7 @@ raise ValueError('Cannot parse {}:"{}"'.format(syntax, line_txt)) -@logger.catch(default=True, onerror=lambda _: sys.exit(1)) +@logger.catch(reraise=True) def build_space_tolerant_regex(linespec): r"""SEMI-PRIVATE: Accept a string, and return a string with all spaces replaced with '\s+'""" @@ -302,7 +312,7 @@ return linespec -@logger.catch(default=True, onerror=lambda _: sys.exit(1)) +@logger.catch(reraise=True) def assign_parent_to_closing_braces(input_list=None): """ Accept a list of brace-delimited BaseCfgLine() objects; these objects @@ -335,16 +345,25 @@ for obj in input_list: assert isinstance(obj, BaseCfgLine) assert isinstance(obj.text, str) - if len(obj.text)>=1 and obj.text.rstrip()[-1] == '{': + + # These rstrip() are one of two fixes, intended to catch user error such as + # the problems that the submitter of Github issue #251 had. + # CiscoConfParse() could not read his configuration because he submitted + # a multi-line string... + # + # This check will explicitly catch some problems like that... + if len(obj.text.rstrip())>=1 and obj.text.rstrip()[-1] == '{': opening_brace_objs.append(obj) - elif len(obj.text)>=1 and obj.text.lstrip()[0]=='}': + + elif len(obj.text.strip())>=1 and obj.text.strip()[0]=='}': assert len(opening_brace_objs) >= 1 obj.parent = opening_brace_objs.pop() + return input_list # This method was copied from the same method in git commit below... # https://raw.githubusercontent.com/mpenning/ciscoconfparse/bb3f77436023873da344377d3c839387f5131e7f/ciscoconfparse/ciscoconfparse.py -@logger.catch(default=True, onerror=lambda _: sys.exit(1)) +@logger.catch(reraise=True) def convert_junos_to_ios(input_list=None, stop_width=4, comment_delimiter="!", debug=0): """ @@ -383,7 +402,7 @@ # IMPORTANT: do NOT decorate CiscoConfParse().__init__() # # Something breaks in CiscoConfParse() if using @logger.catch, below... - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def __init__( self, config="", @@ -469,6 +488,7 @@ self.factory = factory self.ConfigObjs = None self.syntax = syntax + self.ignore_blank_lines = ignore_blank_lines self.encoding = encoding or ENCODING self.debug = debug @@ -550,22 +570,22 @@ raise ValueError(error) # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def __repr__(self): if isinstance(self.ConfigObjs, (list, tuple, MutableSequence)): num_lines = str(len(self.ConfigObjs)) elif self.ConfigObjs is None: num_lines = "None" return ( - "<CiscoConfParse: %s lines / syntax: %s / comment delimiter: '%s' / factory: %s / encoding: '%s'>" + "<CiscoConfParse: %s lines / syntax: %s / comment delimiter: '%s' / factory: %s / ignore_blank_lines: %s / encoding: '%s'>" % ( num_lines, self.syntax, self.comment_delimiter, - self.factory, self.encoding, + self.factory, self.ignore_blank_lines, self.encoding, ) ) # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def get_config_lines(self, config=None, logger=None, linesplit_rgx=r"\r*\n+"): """ Enforce rules - If config is a str, assume it's a filepath. If config is a list, assume it's a router config. @@ -601,8 +621,10 @@ else: raise ValueError("config='%s' is an unexpected type()" % config) + ######################################################################### # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + # do NOT wrap this method in logger.catch() - github issue #249 + ######################################################################### @property def openargs(self): """Fix for Py3.5 deprecation of universal newlines - Ref Github #114 @@ -636,7 +658,7 @@ return self.ConfigObjs # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def atomic(self): """Call :func:`~ciscoconfparse.CiscoConfParse.atomic` to manually fix up ``ConfigObjs`` relationships @@ -665,7 +687,7 @@ self.ConfigObjs._bootstrap_from_text() # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def commit(self): """ Alias for calling the :func:`~ciscoconfparse.CiscoConfParse.atomic` @@ -798,7 +820,7 @@ # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def convert_terraform_to_ios(self, input_list, stop_width=4, quotes=False, comment_delimiter="#"): """ This method accepts `input_list` (it should be a list of @@ -829,7 +851,7 @@ return lines # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def find_object_branches( self, branchspec=(), @@ -1110,7 +1132,7 @@ return branches # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def find_interface_objects(self, intfspec, exactmatch=True): """Find all :class:`~cisco.IOSCfgLine` or :class:`~models_cisco.NXOSCfgLine` objects whose text @@ -1179,7 +1201,7 @@ return retval # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def find_objects_dna(self, dnaspec, exactmatch=False): """Find all :class:`~models_cisco.IOSCfgLine` objects whose text matches ``dnaspec`` and return the :class:`~models_cisco.IOSCfgLine` @@ -1253,7 +1275,7 @@ ) # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def find_objects(self, linespec, exactmatch=False, ignore_ws=False): """Find all :class:`~models_cisco.IOSCfgLine` objects whose text matches ``linespec`` and return the :class:`~models_cisco.IOSCfgLine` @@ -1317,7 +1339,7 @@ return self._find_line_OBJ(linespec, exactmatch) # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def find_lines(self, linespec, exactmatch=False, ignore_ws=False): """This method is the equivalent of a simple configuration grep (Case-sensitive). @@ -1349,7 +1371,7 @@ ) # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def find_children(self, linespec, exactmatch=False, ignore_ws=False): """Returns the parents matching the linespec, and their immediate children. This method is different than :meth:`find_all_children`, @@ -1404,7 +1426,7 @@ return [ii.text for ii in sorted(allobjs)] # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def find_all_children(self, linespec, exactmatch=False, ignore_ws=False): """Returns the parents matching the linespec, and all their children. This method is different than :meth:`find_children`, because @@ -1482,7 +1504,7 @@ return [ii.text for ii in sorted(allobjs)] # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def find_blocks(self, linespec, exactmatch=False, ignore_ws=False): """Find all siblings matching the linespec, then find all parents of those siblings. Return a list of config lines sorted by line number, @@ -1605,7 +1627,7 @@ return [ii.text for ii in sorted(tmp)] # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def find_objects_w_child( self, parentspec, @@ -1710,7 +1732,7 @@ ) # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def find_objects_w_all_children( self, parentspec, @@ -1828,7 +1850,7 @@ return retval # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def find_objects_w_missing_children( self, parentspec, @@ -1877,7 +1899,7 @@ return retval # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @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 @@ -1965,7 +1987,7 @@ return [ii.text for ii in tmp] # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def find_objects_wo_child(self, parentspec, childspec, ignore_ws=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. @@ -2053,7 +2075,7 @@ ] # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @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. @@ -2140,7 +2162,7 @@ return [ii.text for ii in tmp] # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @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. @@ -2251,7 +2273,7 @@ return [ii.text for ii in sorted(retval)] # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @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. @@ -2352,7 +2374,7 @@ return sorted(retval) # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def find_lineage(self, linespec, exactmatch=False): """ Iterate through to the oldest ancestor of this object, and return @@ -2381,7 +2403,7 @@ return [obj.text for obj in tmp[0].lineage] # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def has_line_with(self, linespec): # https://stackoverflow.com/a/16097112/667301 matching_conftext = list( @@ -2393,7 +2415,7 @@ return bool(matching_conftext) # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def insert_before( self, exist_val="", @@ -2495,7 +2517,7 @@ return [ii.text for ii in sorted(objs)] # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def insert_after( self, exist_val="", @@ -2602,7 +2624,7 @@ return [ii.text for ii in sorted(objs)] # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def insert_after_child( self, parentspec, @@ -2643,7 +2665,7 @@ return retval # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @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""" @@ -2655,7 +2677,7 @@ obj.delete() # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @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""" @@ -2663,7 +2685,7 @@ return self.ConfigObjs[0] # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def append_line(self, linespec): """Unconditionally insert ``linespec`` (a text line) at the end of the configuration @@ -2682,7 +2704,7 @@ return self.ConfigObjs[-1] # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def replace_lines( self, linespec, @@ -2799,7 +2821,7 @@ return retval # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def replace_children( self, parentspec, @@ -2887,7 +2909,7 @@ return retval # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def replace_all_children( self, parentspec, @@ -2923,7 +2945,7 @@ return retval # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def re_search_children(self, regex, recurse=False): """Use ``regex`` 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. @@ -2953,7 +2975,7 @@ return [obj for obj in self.find_objects(regex)] # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def re_match_iter_typed( self, regex, @@ -3059,7 +3081,7 @@ return result_type(default) # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def req_cfgspec_all_diff(self, cfgspec, ignore_ws=False): """ req_cfgspec_all_diff takes a list of required configuration lines, @@ -3127,7 +3149,7 @@ return retval # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def req_cfgspec_excl_diff(self, linespec, uncfgspec, cfgspec): r""" req_cfgspec_excl_diff accepts a linespec, an unconfig spec, and @@ -3205,7 +3227,7 @@ return retval # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @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 @@ -3249,7 +3271,7 @@ return a_parse, a_lines, a_linenums # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @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 @@ -3315,7 +3337,7 @@ return a_parse, a_lines, a_linenums # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def sync_diff( self, cfgspec, @@ -3695,7 +3717,7 @@ return retval # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @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 @@ -3713,7 +3735,7 @@ ### or iterable of objects instead of the configuration text itself. # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def _find_line_OBJ(self, linespec, exactmatch=False): """SEMI-PRIVATE: Find objects whose text matches the linespec""" @@ -3741,7 +3763,7 @@ ) # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def _find_sibling_OBJ(self, lineobject): """SEMI-PRIVATE: Takes a singe object and returns a list of sibling objects""" @@ -3749,7 +3771,7 @@ return siblings # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @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. @@ -3764,7 +3786,7 @@ return retval # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def _unique_OBJ(self, objectlist): """SEMI-PRIVATE: Returns a list of unique objects (i.e. with no duplicates). @@ -3776,7 +3798,7 @@ return sorted(retval) # This method is on CiscoConfParse() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def _objects_to_uncfg(self, objectlist, unconflist): # Used by req_cfgspec_excl_diff() retval = list() @@ -3797,7 +3819,7 @@ """ # This method is on HDiff() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def __init__( self, before_config=None, @@ -3891,12 +3913,12 @@ self.sort_lines(parse_after, self.all_output_dicts) # This method is on HDiff() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def raw_diff_dicts(self): return self.all_output_dicts # This method is on HDiff() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def unified_diff_header(self): """ Return a unified diff header similar to this... @@ -3946,7 +3968,7 @@ return unified_diff_header # This method is on HDiff() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def unified_diffs(self, header=True): """ Return a python list of text which contains the unified diff of the @@ -3974,7 +3996,7 @@ return unified_diff_list # This method is on HDiff() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def sort_lines(self, after_lines, all_output_dicts): """ Typical output line dict-format... @@ -3989,7 +4011,7 @@ pass # This method is on HDiff() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def dict_diffs(self, before_obj_list, after_obj_list): ############################################ # Render diffs @@ -4040,7 +4062,7 @@ return all_dict_lines # This method is on HDiff() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def build_diff_obj_list( self, parse=None, default_diff_word=None, consider_whitespace=False ): @@ -4068,7 +4090,7 @@ return retval # This method is on HDiff() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def find_in_before_obj_list( self, before_obj_list, after_obj, consider_whitespace=False, debug=0 ): @@ -4161,7 +4183,7 @@ return before_obj_list, after_obj # This method is on HDiff() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def render_after_obj_diffs(self, aobj=None): """ Print after_obj (aobj) diffs to stdout. before_obj should not be @@ -4215,7 +4237,7 @@ return output # This method is on HDiff() - @logger.catch(default=True, onerror=lambda _: sys.exit(1)) + @logger.catch(reraise=True) def compress_dict_diffs(self, all_lines=None): """ Summary @@ -4403,6 +4425,15 @@ # FIXME the CiscoConfParse attribute / parameter should go away # use self.ccp_ref instead of self.CiscoConfParse ####################################################################### + + # This assert is one of two fixes, intended to catch user error such as + # the problems that the submitter of Github issue #251 had. + # CiscoConfParse() could not read his configuration because he submitted + # a multi-line string... + # + # This check will explicitly catch some problems like that... + assert isinstance(initlist, (list, tuple, MutableSequence)) + ciscoconfparse_kwarg_val = kwargs.get("CiscoConfParse", None) ccp_ref_kwarg_val = kwargs.get("ccp_ref", None) if ciscoconfparse_kwarg_val is not None: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ciscoconfparse-1.6.41/pyproject.toml new/ciscoconfparse-1.6.50/pyproject.toml --- old/ciscoconfparse-1.6.41/pyproject.toml 2022-09-10 00:27:51.814184000 +0200 +++ new/ciscoconfparse-1.6.50/pyproject.toml 2022-10-22 04:32:00.204709500 +0200 @@ -1,6 +1,6 @@ [tool.poetry] name = "ciscoconfparse" -version = "1.6.41" +version = "1.6.50" description = "Parse, Audit, Query, Build, and Modify Cisco IOS-style and JunOS-style configurations" authors = [ "Mike Pennington <m...@pennington.net>", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ciscoconfparse-1.6.41/setup.py new/ciscoconfparse-1.6.50/setup.py --- old/ciscoconfparse-1.6.41/setup.py 2022-09-10 00:29:24.075051000 +0200 +++ new/ciscoconfparse-1.6.50/setup.py 1970-01-01 01:00:00.000000000 +0100 @@ -15,13 +15,13 @@ setup_kwargs = { 'name': 'ciscoconfparse', - 'version': '1.6.41', + 'version': '1.6.50', 'description': 'Parse, Audit, Query, Build, and Modify Cisco IOS-style and JunOS-style configurations', - 'long_description': 'ciscoconfparse\n==============\n\n[![Github unittest workflow][4]][5] [![Code Health][37]][38] [![Version][2]][3] [![Downloads][6]][7] [![License][8]][9]\n\n\nIntroduction: What is ciscoconfparse?\n-------------------------------------\n\nShort answer: ciscoconfparse is a [Python][10] library\nthat helps you quickly answer questions like these about your\nconfigurations:\n\n- What interfaces are shutdown?\n- Which interfaces are in trunk mode?\n- What address and subnet mask is assigned to each interface?\n- Which interfaces are missing a critical command?\n- Is this configuration missing a standard config line?\n\nIt can help you:\n\n- Audit existing router / switch / firewall / wlc configurations\n- Modify existing configurations\n- Build new configurations\n\nSpeaking generally, the library examines an IOS-style config and breaks\nit into a set of linked parent / child relationships. You can perform\ncomplex queries about these relationships.\n\n[![Cisco IOS config: Parent / child][11]][11]\n\nUsage\n-----\n\nThe following code will parse a configuration stored in\n\\\'exampleswitch.conf\\\' and select interfaces that are shutdown.\n\n```python\nfrom ciscoconfparse import CiscoConfParse\n\nparse = CiscoConfParse(\'exampleswitch.conf\', syntax=\'ios\')\n\nfor intf_obj in parse.find_objects_w_child(\'^interface\', \'^\\s+shutdown\'):\n print("Shutdown: " + intf_obj.text)\n```\n\nThe next example will find the IP address assigned to interfaces.\n\n```python\nfrom ciscoconfparse import CiscoConfParse\n\nparse = CiscoConfParse(\'exampleswitch.conf\', syntax=\'ios\')\n\nfor intf_obj in parse.find_objects(\'^interface\'):\n\n intf_name = intf_obj.re_match_typed(\'^interface\\s+(\\S.+?)$\')\n\n # Search children of all interfaces for a regex match and return\n # the value matched in regex match group 1. If there is no match,\n # return a default value: \'\'\n intf_ip_addr = intf_obj.re_match_iter_typed(\n r\'ip\\sa ddress\\s(\\d+\\.\\d+\\.\\d+\\.\\d+)\\s\', result_type=str,\n group=1, default=\'\')\n print("{0}: {1}".format(intf_name, intf_ip_addr))\n```\n\nWhat if we don\\\'t use Cisco?\n----------------------------\n\nDon\\\'t let that stop you.\n\nAs of CiscoConfParse 1.2.4, you can parse [brace-delimited configurations][13] into a Cisco IOS style (see [Github Issue \\#17][14]), which means that CiscoConfParse can parse these configurations:\n\n- Juniper Networks Junos\n- Palo Alto Networks Firewall configurations\n- F5 Networks configurations\n\nCiscoConfParse also handles anything that has a Cisco IOS style of configuration, which includes:\n\n- Cisco IOS, Cisco Nexus, Cisco IOS-XR, Cisco IOS-XE, Aironet OS, Cisco ASA, Cisco CatOS\n- Arista EOS\n- Brocade\n- HP Switches\n- Force 10 Switches\n- Dell PowerConnect Switches\n- Extreme Networks\n- Enterasys\n- Screenos\n\nDocs\n----\n\n- The latest copy of the docs are [archived on the web][15]\n- There is also a [CiscoConfParse Tuto rial][16]\n\nEditing the Package\n-------------------\n\n- `git clone https://github.com/mpenning/ciscoconfparse`\n- `cd ciscoconfparse`\n- `git checkout -b develop`\n- Add / modify / delete on the `develop` branch\n- `make test`\n- If tests run clean, `git commit` all the pending changes on the `develop` branch\n- (as required) Edit the version number in [pyproject.toml][12]\n- `git checkout main`\n- `git merge develop`\n- `make test`\n- `make repo-push`\n- `make pypi`\n\nPre-requisites\n--------------\n\n[The ciscoconfparse python package][3] requires Python versions 3.6+ (note: Python version 3.7.0 has a bug - ref [Github issue \\#117][18], but version 3.7.1 works); the OS should not matter.\n\nInstallation and Downloads\n--------------------------\n\n- Use `poetry` for Python3.x\\... :\n\n python -m pip install ciscoconfparse\n\nIf you\\\'re interested in the source, you can always pull from the [github repo][17]:\n\n- Download from [the github r epo][17]: :\n\n git clone git://github.com/mpenning/ciscoconfparse\n cd ciscoconfparse/\n python -m pip install .\n\nOther Resources\n---------------\n\n- [Dive into Python3](http://www.diveintopython3.net/) is a good way to learn Python\n- [Team CYMRU][30] has a [Secure IOS Template][29], which is especially useful for external-facing routers / switches\n- [Cisco\\\'s Guide to hardening IOS devices][31]\n- [Center for Internet Security Benchmarks][32] (An email address, cookies, and javascript are required)\n\nBug Tracker and Support\n-----------------------\n\n- Please report any suggestions, bug reports, or annoyances with a [github bug report][24].\n- If you\\\'re having problems with general python issues, consider searching for a solution on [Stack Overflow][33]. If you can\\\'t find a solution for your problem or need more help, you can [ask on Stack Overflow][34] or [reddit/r/Python][39].\n- If you\\\'re having problems with your Cisco devices, you can contact:\n - [Cisco TAC][28]\n - [reddit/r/Cisco][35]\n - [reddit/r/networking][36]\n - [NetworkEngineering.se][23]\n\nUnit-Tests\n----------\n\nThe project\\\'s [test workflow][1] checks ciscoconfparse on Python versions 3.6 and higher, as well as a [pypy JIT][22] executable.\n\nClick the image below for details; the current build status is: [![Github unittest status][4]][5]\n\nLicense and Copyright\n---------------------\n\n[ciscoconfparse][3] is licensed [GPLv3][21]\n\n- Copyright (C) 2021-2022 David Michael Pennington\n- Copyright (C) 2020-2021 David Michael Pennington at Cisco Systems (post-acquisition: Cisco acquired ThousandEyes)\n- Copyright (C) 2019 David Michael Pennington at ThousandEyes\n- Copyright (C) 2012-2019 David Michael Pennington at Samsung Data Services\n- Copyright (C) 2011-2012 David Michael Pennington at Dell Computer Corp\n- Copyright (C) 2007-2011 David Michael Pennington\n\nThe word \\"Cisco\\" is a registered trademark of [Cisco Systems][27].\n\nAutho r\n------\n\n[ciscoconfparse][3] was written by [David Michael Pennington][25] (mike \\[\\~at\\~\\] pennington \\[.dot.\\] net).\n\n\n [1]: https://github.com/mpenning/ciscoconfparse/tree/master/.github/workflows\n [2]: https://img.shields.io/pypi/v/ciscoconfparse.svg\n [3]: https://pypi.python.org/pypi/ciscoconfparse/\n [4]: https://github.com/mpenning/ciscoconfparse/actions/workflows/tests.yml/badge.svg\n [5]: https://github.com/mpenning/ciscoconfparse/actions/workflows/tests.yml\n [6]: https://pepy.tech/badge/ciscoconfparse\n [7]: https://pepy.tech/project/ciscoconfparse\n [8]: http://img.shields.io/badge/license-GPLv3-blue.svg\n [9]: https://www.gnu.org/copyleft/gpl.html\n [10]: https://www.python.org\n [11]: https://raw.githubusercontent.com/mpenning/ciscoconfparse/master/sphinx-doc/_static/ciscoconfparse_overview_75pct.png\n [12]: https://github.com/mpenning/ciscoconfparse/blob/main/pyproject.toml\n [13]: https://github.com/mpenning/ciscoconfparse/blob/master/conf igs/sample_01.junos\n [14]: https://github.com/mpenning/ciscoconfparse/issues/17\n [15]: http://www.pennington.net/py/ciscoconfparse/\n [16]: http://pennington.net/tutorial/ciscoconfparse/ccp_tutorial.html\n [17]: https://github.com/mpenning/ciscoconfparse\n [18]: https://github.com/mpenning/ciscoconfparse/issues/117\n [19]: https://github.com/mpenning/ciscoconfparse/issues/13\n [20]: https://github.com/CrackerJackMack/\n [21]: http://www.gnu.org/licenses/gpl-3.0.html\n [22]: https://pypy.org\n [23]: https://networkengineering.stackexchange.com/\n [24]: https://github.com/mpenning/ciscoconfparse/issues/new/choose\n [25]: https://github.com/mpenning\n [26]: https://github.com/muir\n [27]: https://www.cisco.com/\n [28]: https://www.cisco.com/go/support\n [29]: https://www.cymru.com/Documents/secure-ios-template.html\n [30]: https://team-cymru.com/company/\n [31]: http://www.cisco.com/c/en/us/support/docs/ip/access-lists/13608-21.html\n [32]: https://learn.cisecurity .org/benchmarks\n [33]: https://stackoverflow.com\n [34]: http://stackoverflow.com/questions/ask\n [35]: https://www.reddit.com/r/Cisco/\n [36]: https://www.reddit.com/r/networking\n [37]: https://snyk.io/advisor/python/ciscoconfparse/badge.svg\n [38]: https://snyk.io/advisor/python/ciscoconfparse\n [39]: https://www.reddit.com/r/Python/\n', + 'long_description': 'ciscoconfparse\n==============\n\n[![Github unittest workflow][4]][5] [![Code Health][37]][38] [![Version][2]][3] [![Downloads][6]][7] [![License][8]][9]\n\n\nIntroduction: What is ciscoconfparse?\n-------------------------------------\n\nShort answer: ciscoconfparse is a [Python][10] library\nthat helps you quickly answer questions like these about your\nconfigurations:\n\n- What interfaces are shutdown?\n- Which interfaces are in trunk mode?\n- What address and subnet mask is assigned to each interface?\n- Which interfaces are missing a critical command?\n- Is this configuration missing a standard config line?\n\nIt can help you:\n\n- Audit existing router / switch / firewall / wlc configurations\n- Modify existing configurations\n- Build new configurations\n\nSpeaking generally, the library examines an IOS-style config and breaks\nit into a set of linked parent / child relationships. You can perform\ncomplex queries about these relationships.\n\n[![Cisco IOS config: Parent / child][11]][11]\n\nUsage\n-----\n\nThe following code will parse a configuration stored in\n\\\'exampleswitch.conf\\\' and select interfaces that are shutdown.\n\n```python\nfrom ciscoconfparse import CiscoConfParse\n\nparse = CiscoConfParse(\'exampleswitch.conf\', syntax=\'ios\')\n\nfor intf_obj in parse.find_objects_w_child(\'^interface\', \'^\\s+shutdown\'):\n print("Shutdown: " + intf_obj.text)\n```\n\nThe next example will find the IP address assigned to interfaces.\n\n```python\nfrom ciscoconfparse import CiscoConfParse\n\nparse = CiscoConfParse(\'exampleswitch.conf\', syntax=\'ios\')\n\nfor intf_obj in parse.find_objects(\'^interface\'):\n\n intf_name = intf_obj.re_match_typed(\'^interface\\s+(\\S.+?)$\')\n\n # Search children of all interfaces for a regex match and return\n # the value matched in regex match group 1. If there is no match,\n # return a default value: \'\'\n intf_ip_addr = intf_obj.re_match_iter_typed(\n r\'ip\\sa ddress\\s(\\d+\\.\\d+\\.\\d+\\.\\d+)\\s\', result_type=str,\n group=1, default=\'\')\n print("{0}: {1}".format(intf_name, intf_ip_addr))\n```\n\nWhat if we don\\\'t use Cisco?\n----------------------------\n\nDon\\\'t let that stop you.\n\nAs of CiscoConfParse 1.2.4, you can parse [brace-delimited configurations][13] into a Cisco IOS style (see [Github Issue \\#17][14]), which means that CiscoConfParse can parse these configurations:\n\n- Juniper Networks Junos\n- Palo Alto Networks Firewall configurations\n- F5 Networks configurations\n\nCiscoConfParse also handles anything that has a Cisco IOS style of configuration, which includes:\n\n- Cisco IOS, Cisco Nexus, Cisco IOS-XR, Cisco IOS-XE, Aironet OS, Cisco ASA, Cisco CatOS\n- Arista EOS\n- Brocade\n- HP Switches\n- Force 10 Switches\n- Dell PowerConnect Switches\n- Extreme Networks\n- Enterasys\n- Screenos\n\nDocs\n----\n\n- The latest copy of the docs are [archived on the web][15]\n- There is also a [CiscoConfParse Tuto rial][16]\n\nEditing the Package\n-------------------\n\n- `git clone https://github.com/mpenning/ciscoconfparse`\n- `cd ciscoconfparse`\n- `git checkout -b develop`\n- Add / modify / delete on the `develop` branch\n- `make test`\n- If tests run clean, `git commit` all the pending changes on the `develop` branch\n- (as required) Edit the version number in [pyproject.toml][12]\n- `git checkout main`\n- `git merge develop`\n- `make test`\n- `make repo-push`\n- `make pypi`\n\nPre-requisites\n--------------\n\n[The ciscoconfparse python package][3] requires Python versions 3.7+ (note: Python version 3.7.0 has a bug - ref [Github issue \\#117][18], but version 3.7.1 works); the OS should not matter.\n\nInstallation and Downloads\n--------------------------\n\n- Use `poetry` for Python3.x\\... :\n\n python -m pip install ciscoconfparse\n\nIf you\\\'re interested in the source, you can always pull from the [github repo][17]:\n\n- Download from [the github r epo][17]: :\n\n git clone git://github.com/mpenning/ciscoconfparse\n cd ciscoconfparse/\n python -m pip install .\n\nOther Resources\n---------------\n\n- [Dive into Python3](http://www.diveintopython3.net/) is a good way to learn Python\n- [Team CYMRU][30] has a [Secure IOS Template][29], which is especially useful for external-facing routers / switches\n- [Cisco\\\'s Guide to hardening IOS devices][31]\n- [Center for Internet Security Benchmarks][32] (An email address, cookies, and javascript are required)\n\nBug Tracker and Support\n-----------------------\n\n- Please report any suggestions, bug reports, or annoyances with a [github bug report][24].\n- If you\\\'re having problems with general python issues, consider searching for a solution on [Stack Overflow][33]. If you can\\\'t find a solution for your problem or need more help, you can [ask on Stack Overflow][34] or [reddit/r/Python][39].\n- If you\\\'re having problems with your Cisco devices, you can contact:\n - [Cisco TAC][28]\n - [reddit/r/Cisco][35]\n - [reddit/r/networking][36]\n - [NetworkEngineering.se][23]\n\nUnit-Tests\n----------\n\nThe project\\\'s [test workflow][1] checks ciscoconfparse on Python versions 3.6 and higher, as well as a [pypy JIT][22] executable.\n\nClick the image below for details; the current build status is: [![Github unittest status][4]][5]\n\nLicense and Copyright\n---------------------\n\n[ciscoconfparse][3] is licensed [GPLv3][21]\n\n- Copyright (C) 2021-2022 David Michael Pennington\n- Copyright (C) 2020-2021 David Michael Pennington at Cisco Systems (post-acquisition: Cisco acquired ThousandEyes)\n- Copyright (C) 2019 David Michael Pennington at ThousandEyes\n- Copyright (C) 2012-2019 David Michael Pennington at Samsung Data Services\n- Copyright (C) 2011-2012 David Michael Pennington at Dell Computer Corp\n- Copyright (C) 2007-2011 David Michael Pennington\n\nThe word \\"Cisco\\" is a registered trademark of [Cisco Systems][27].\n\nAutho r\n------\n\n[ciscoconfparse][3] was written by [David Michael Pennington][25] (mike \\[\\~at\\~\\] pennington \\[.dot.\\] net).\n\n\n [1]: https://github.com/mpenning/ciscoconfparse/tree/master/.github/workflows\n [2]: https://img.shields.io/pypi/v/ciscoconfparse.svg\n [3]: https://pypi.python.org/pypi/ciscoconfparse/\n [4]: https://github.com/mpenning/ciscoconfparse/actions/workflows/tests.yml/badge.svg\n [5]: https://github.com/mpenning/ciscoconfparse/actions/workflows/tests.yml\n [6]: https://pepy.tech/badge/ciscoconfparse\n [7]: https://pepy.tech/project/ciscoconfparse\n [8]: http://img.shields.io/badge/license-GPLv3-blue.svg\n [9]: https://www.gnu.org/copyleft/gpl.html\n [10]: https://www.python.org\n [11]: https://raw.githubusercontent.com/mpenning/ciscoconfparse/master/sphinx-doc/_static/ciscoconfparse_overview_75pct.png\n [12]: https://github.com/mpenning/ciscoconfparse/blob/main/pyproject.toml\n [13]: https://github.com/mpenning/ciscoconfparse/blob/master/conf igs/sample_01.junos\n [14]: https://github.com/mpenning/ciscoconfparse/issues/17\n [15]: http://www.pennington.net/py/ciscoconfparse/\n [16]: http://pennington.net/tutorial/ciscoconfparse/ccp_tutorial.html\n [17]: https://github.com/mpenning/ciscoconfparse\n [18]: https://github.com/mpenning/ciscoconfparse/issues/117\n [19]: https://github.com/mpenning/ciscoconfparse/issues/13\n [20]: https://github.com/CrackerJackMack/\n [21]: http://www.gnu.org/licenses/gpl-3.0.html\n [22]: https://pypy.org\n [23]: https://networkengineering.stackexchange.com/\n [24]: https://github.com/mpenning/ciscoconfparse/issues/new/choose\n [25]: https://github.com/mpenning\n [26]: https://github.com/muir\n [27]: https://www.cisco.com/\n [28]: https://www.cisco.com/go/support\n [29]: https://www.cymru.com/Documents/secure-ios-template.html\n [30]: https://team-cymru.com/company/\n [31]: http://www.cisco.com/c/en/us/support/docs/ip/access-lists/13608-21.html\n [32]: https://learn.cisecurity .org/benchmarks\n [33]: https://stackoverflow.com\n [34]: http://stackoverflow.com/questions/ask\n [35]: https://www.reddit.com/r/Cisco/\n [36]: https://www.reddit.com/r/networking\n [37]: https://snyk.io/advisor/python/ciscoconfparse/badge.svg\n [38]: https://snyk.io/advisor/python/ciscoconfparse\n [39]: https://www.reddit.com/r/Python/\n', 'author': 'Mike Pennington', 'author_email': 'm...@pennington.net', - 'maintainer': None, - 'maintainer_email': None, + 'maintainer': 'None', + 'maintainer_email': 'None', 'url': 'https://github.com/mpenning/ciscoconfparse', 'packages': packages, 'package_data': package_data,