Hello community,

here is the log from the commit of package python-ciscoconfparse for 
openSUSE:Factory checked in at 2019-03-28 22:48:21
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-ciscoconfparse (Old)
 and      /work/SRC/openSUSE:Factory/.python-ciscoconfparse.new.25356 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-ciscoconfparse"

Thu Mar 28 22:48:21 2019 rev:6 rq:688723 version:1.3.32

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/python-ciscoconfparse/python-ciscoconfparse.changes  
    2018-08-10 09:49:44.298266105 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-ciscoconfparse.new.25356/python-ciscoconfparse.changes
   2019-03-28 22:48:26.995057314 +0100
@@ -1,0 +2,27 @@
+Tue Mar 26 13:28:11 UTC 2019 - Tomáš Chvátal <[email protected]>
+
+- Update to 1.3.32:
+  * No good changelog
+- Remove unused patch ciscoconfparse-1.2.40-fix-tests.patch
+
+-------------------------------------------------------------------
+Wed Mar  6 19:57:31 UTC 2019 - Tomáš Chvátal <[email protected]>
+
+- Update to 1.3.30:
+  * Fix bugs related to Python3 (Github issue #117)
+  * Add IP helper-address parsing in models_cisco.py
+  * Revert universal wheel packages (universal=0)
+  * Build universal wheel packages
+  * Build improvements - ref Github issue #127, #128
+  * Another swing at Github issue #127
+  * Rollback fix for Github issue #127
+  * Attempt to fix Github issue #127
+  * Fix Github issue #124-126 and Github issue #110
+  * Fix Github issue #121 and Github issue #123
+  * Fix Github issue #114 (Py3.5 requires different open() syntax)
+  * Fix Github issue #111 (banner parsing broken in some cases)
+  * Add * to MANIFEST.in
+  * Attempt to resolve Github issue #106
+  * Add dns_query() zone transfer as text
+
+-------------------------------------------------------------------

Old:
----
  ciscoconfparse-1.2.40-fix-tests.patch
  ciscoconfparse-1.3.15.tar.gz

New:
----
  ciscoconfparse-1.3.32.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-ciscoconfparse.spec ++++++
--- /var/tmp/diff_new_pack.Gh7tzQ/_old  2019-03-28 22:48:27.823057165 +0100
+++ /var/tmp/diff_new_pack.Gh7tzQ/_new  2019-03-28 22:48:27.827057164 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package python-ciscoconfparse
 #
-# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany.
+# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany.
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -12,30 +12,27 @@
 # license that conforms to the Open Source Definition (Version 1.9)
 # published by the Open Source Initiative.
 
-# Please submit bugfixes or comments via http://bugs.opensuse.org/
+# Please submit bugfixes or comments via https://bugs.opensuse.org/
 #
 
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-ciscoconfparse
-Version:        1.3.15
+Version:        1.3.32
 Release:        0
-Summary:        Python library for parsing, querying and modifying Cisco 
IOS-style configurations
+Summary:        Library for parsing, querying and modifying Cisco IOS-style 
configurations
 License:        GPL-3.0-or-later
 Group:          Development/Languages/Python
 URL:            https://github.com/mpenning/ciscoconfparse
 Source:         
https://files.pythonhosted.org/packages/source/c/ciscoconfparse/ciscoconfparse-%{version}.tar.gz
-# PATCH fix tests
-Patch0:         ciscoconfparse-1.2.40-fix-tests.patch
 BuildRequires:  %{python_module colorama}
 BuildRequires:  %{python_module dnspython}
-BuildRequires:  %{python_module setuptools}
-BuildRequires:  python2-ipaddress
-BuildRequires:  python2-mock
-# For tests
 BuildRequires:  %{python_module pytest}
+BuildRequires:  %{python_module setuptools}
 BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros
+BuildRequires:  python2-ipaddress
+BuildRequires:  python2-mock
 Requires:       python-colorama
 Requires:       python-dnspython
 Requires:       python-ipaddr
@@ -57,7 +54,6 @@
 
 %prep
 %setup -q -n ciscoconfparse-%{version}
-%autopatch -p1
 
 %build
 %python_build
@@ -67,9 +63,8 @@
 %python_expand %fdupes %{buildroot}%{$python_sitelib}
 
 %check
-# Upstream has broken tests.
-# https://github.com/mpenning/ciscoconfparse/issues/106
-# %%python_expand PYTHONPATH=%%{buildroot}%%{$python_sitelib} 
py.test-%%{$python_version}
+# Upstream stubbornly refuses PRs and does not distribute tests
+#%%pytest
 
 %files %{python_files}
 %license LICENSE

++++++ ciscoconfparse-1.3.15.tar.gz -> ciscoconfparse-1.3.32.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ciscoconfparse-1.3.15/CHANGES 
new/ciscoconfparse-1.3.32/CHANGES
--- old/ciscoconfparse-1.3.15/CHANGES   2018-04-21 16:08:55.000000000 +0200
+++ new/ciscoconfparse-1.3.32/CHANGES   2019-03-17 13:46:40.000000000 +0100
@@ -1,3 +1,20 @@
+1.3.32  20190317 Fix Github issue #135
+1.3.31  20190316 Fix Github issues #131, 132, 133, 134
+1.3.30  20190218 Fix bugs related to Python3 (Github issue #117)
+1.3.29  20190207 Add IP helper-address parsing in models_cisco.py
+1.3.28  20190206 Revert universal wheel packages (universal=0)
+1.3.27  20190126 Build universal wheel packages
+1.3.26  20190126 Build improvements - ref Github issue #127, #128
+1.3.25  20190123 Another swing at Github issue #127
+1.3.24  20190123 Rollback fix for Github issue #127
+1.3.23  20190123 Attempt to fix Github issue #127
+1.3.22  20181216 Fix Github issue #124-126 and Github issue #110
+1.3.21  20181216 Fix Github issue #121 and Github issue #123
+1.3.20  20180702 Fix Github issue #114 (Py3.5 requires different open() syntax)
+1.3.19  20180623 Fix Github issue #111 (banner parsing broken in some cases)
+1.3.18  20180609 Add * to MANIFEST.in
+1.3.17  20180608 Attempt to resolve Github issue #106
+1.3.16  20180601 Add dns_query() zone transfer as text
 1.3.15  20180421 Distrbution change
 1.3.14  20180421 Attempt to fix unit tests
 1.3.13  20180421 Fix Github issue #103, Python3 ccp_util imports 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ciscoconfparse-1.3.15/MANIFEST.in 
new/ciscoconfparse-1.3.32/MANIFEST.in
--- old/ciscoconfparse-1.3.15/MANIFEST.in       2018-01-20 13:00:09.000000000 
+0100
+++ new/ciscoconfparse-1.3.32/MANIFEST.in       2019-02-19 03:02:10.000000000 
+0100
@@ -5,5 +5,6 @@
 recursive-exclude * *.orig
 recursive-exclude * BUILD.ME
 recursive-exclude * BITBUCKET_HG
+recursive-include tests*
 prune sphinx-doc/_static*
 prune sphinx-doc/_build*
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ciscoconfparse-1.3.15/PKG-INFO 
new/ciscoconfparse-1.3.32/PKG-INFO
--- old/ciscoconfparse-1.3.15/PKG-INFO  2018-04-21 16:09:21.000000000 +0200
+++ new/ciscoconfparse-1.3.32/PKG-INFO  2019-03-17 13:53:07.000000000 +0100
@@ -1,12 +1,11 @@
 Metadata-Version: 1.1
 Name: ciscoconfparse
-Version: 1.3.15
+Version: 1.3.32
 Summary: Parse, Audit, Query, Build, and Modify Cisco IOS-style configurations
 Home-page: http://www.pennington.net/py/ciscoconfparse/
 Author: David Michael Pennington
 Author-email: [email protected]
 License: GPLv3
-Description-Content-Type: UNKNOWN
 Description: ==============
         ciscoconfparse
         ==============
@@ -83,9 +82,9 @@
         Pre-requisites
         ==============
         
-        ciscoconfparse_ requires Python versions 2.6, 2.7 or 3.2+; the OS 
should not
-        matter. If you want to run it under a Python virtualenv_, it's been 
heavily 
-        tested in that environment as well.
+        As of version 1.3.32, ciscoconfparse_ requires Python versions 2.7 or 
3.4+ 
+        (note: version 3.7.0 has a bug - ref Github issue #117, but version 
3.7.1 
+        works); the OS should not matter.
         
         .. _Installation:
         
@@ -98,7 +97,7 @@
         ::
         
               # Substitute whatever ciscoconfparse version you like...
-              easy_install -U ciscoconfparse==1.3.14
+              easy_install -U ciscoconfparse==1.3.32
         
         Alternatively you can install into Python2.x with pip_:
         
@@ -133,7 +132,7 @@
         FAQ
         ===
         
-        #) *QUESTION*: I want to use ciscoconfparse_ with Python3; is that 
safe?  *ANSWER*: As long as you're using Python 3.3 or higher, it's safe. I 
test every release against Python 3.2+; however, Python 3.2 is currently 
exposed to a small bug for some configurations (see `Github Issue #14`_).
+        #) *QUESTION*: I want to use ciscoconfparse_ with Python3; is that 
safe?  *ANSWER*: *ANSWER*: Yes.
         
         #) *QUESTION*: Some of the code in the documentation looks different 
than what I'm used to seeing.  Did you change something?  *ANSWER*: Yes, 
starting around ciscoconfparse_ v0.9.10 I introducted more methods directly on 
``IOSConfigLine()`` objects; going forward, these methods are the preferred way 
to use ciscoconfparse_.  Please start using the new methods shown in the 
example, since they're faster, and you type much less code this way.
         
@@ -167,7 +166,7 @@
         Unit-Tests
         ==========
         
-        `Travis CI project <https://travis-ci.org>`_ tests ciscoconfparse on 
Python versions 2.6 through 3.6, as well as a `pypy JIT`_ executable.
+        `Travis CI project <https://travis-ci.org>`_ tests ciscoconfparse on 
Python versions 2.7 through 3.7, as well as a `pypy JIT`_ executable.
         
         Click the image below for details; the current build status is:
         
@@ -182,7 +181,7 @@
         =====================
         
         ciscoconfparse_ is licensed GPLv3_; Copyright `David Michael 
Pennington`_, 
-        2007-2018.
+        2007-2019.
         
         ciscoconfparse_ is not affiliated with Cisco Systems in any way; the 
word "Cisco" is a registered trademark of Cisco Systems
         
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ciscoconfparse-1.3.15/README.rst 
new/ciscoconfparse-1.3.32/README.rst
--- old/ciscoconfparse-1.3.15/README.rst        2018-04-21 16:03:24.000000000 
+0200
+++ new/ciscoconfparse-1.3.32/README.rst        2019-03-17 13:45:26.000000000 
+0100
@@ -74,9 +74,9 @@
 Pre-requisites
 ==============
 
-ciscoconfparse_ requires Python versions 2.6, 2.7 or 3.2+; the OS should not
-matter. If you want to run it under a Python virtualenv_, it's been heavily 
-tested in that environment as well.
+As of version 1.3.32, ciscoconfparse_ requires Python versions 2.7 or 3.4+ 
+(note: version 3.7.0 has a bug - ref Github issue #117, but version 3.7.1 
+works); the OS should not matter.
 
 .. _Installation:
 
@@ -89,7 +89,7 @@
 ::
 
       # Substitute whatever ciscoconfparse version you like...
-      easy_install -U ciscoconfparse==1.3.14
+      easy_install -U ciscoconfparse==1.3.32
 
 Alternatively you can install into Python2.x with pip_:
 
@@ -124,7 +124,7 @@
 FAQ
 ===
 
-#) *QUESTION*: I want to use ciscoconfparse_ with Python3; is that safe?  
*ANSWER*: As long as you're using Python 3.3 or higher, it's safe. I test every 
release against Python 3.2+; however, Python 3.2 is currently exposed to a 
small bug for some configurations (see `Github Issue #14`_).
+#) *QUESTION*: I want to use ciscoconfparse_ with Python3; is that safe?  
*ANSWER*: *ANSWER*: Yes.
 
 #) *QUESTION*: Some of the code in the documentation looks different than what 
I'm used to seeing.  Did you change something?  *ANSWER*: Yes, starting around 
ciscoconfparse_ v0.9.10 I introducted more methods directly on 
``IOSConfigLine()`` objects; going forward, these methods are the preferred way 
to use ciscoconfparse_.  Please start using the new methods shown in the 
example, since they're faster, and you type much less code this way.
 
@@ -158,7 +158,7 @@
 Unit-Tests
 ==========
 
-`Travis CI project <https://travis-ci.org>`_ tests ciscoconfparse on Python 
versions 2.6 through 3.6, as well as a `pypy JIT`_ executable.
+`Travis CI project <https://travis-ci.org>`_ tests ciscoconfparse on Python 
versions 2.7 through 3.7, as well as a `pypy JIT`_ executable.
 
 Click the image below for details; the current build status is:
 
@@ -173,7 +173,7 @@
 =====================
 
 ciscoconfparse_ is licensed GPLv3_; Copyright `David Michael Pennington`_, 
-2007-2018.
+2007-2019.
 
 ciscoconfparse_ is not affiliated with Cisco Systems in any way; the word 
"Cisco" is a registered trademark of Cisco Systems
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ciscoconfparse-1.3.15/ciscoconfparse/ccp_abc.py 
new/ciscoconfparse-1.3.32/ciscoconfparse/ccp_abc.py
--- old/ciscoconfparse-1.3.15/ciscoconfparse/ccp_abc.py 2018-04-21 
15:41:44.000000000 +0200
+++ new/ciscoconfparse-1.3.32/ciscoconfparse/ccp_abc.py 2019-03-16 
18:45:59.000000000 +0100
@@ -6,8 +6,8 @@
 
 from ccp_util import IPv4Obj
 
-""" ccp_abc.py - Parse, Query, Build, and Modify IOS-style configurations
-     Copyright (C) 2014-2015 David Michael Pennington
+r""" ccp_abc.py - Parse, Query, Build, and Modify IOS-style configurations
+     Copyright (C) 2014-2015, 2019 David Michael Pennington
 
      This program is free software: you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published by
@@ -197,7 +197,7 @@
         """unconftext is defined during special method calls.  Do not assume it
         is automatically populated."""
         ## remove any preceeding "no "
-        conftext = re.sub("\s*no\s+", "", unconftext)
+        conftext = re.sub(r"\s*no\s+", "", unconftext)
         myindent = self.parent.child_indent
         self.uncfgtext = myindent * " " + "no " + conftext
 
@@ -457,7 +457,7 @@
         return retval
 
     def re_match(self, regex, group=1, default=""):
-        """Use ``regex`` to search the :class:`~models_cisco.IOSCfgLine` text 
and return the regular expression group, at the integer index.
+        r"""Use ``regex`` to search the :class:`~models_cisco.IOSCfgLine` text 
and return the regular expression group, at the integer index.
 
         Args:
             - regex (str): A string or python regular expression, which should 
be matched.  This regular expression should contain parenthesis, which bound a 
match group.
@@ -530,8 +530,9 @@
         """
         return [cobj for cobj in self.children if cobj.re_search(regex)]
 
-    def re_match_typed(self, regex, group=1, result_type=str, default=''):
-        """Use ``regex`` to search the :class:`~models_cisco.IOSCfgLine` text 
+    def re_match_typed(self, regex, group=1, untyped_default=False, 
+        result_type=str, default=''):
+        r"""Use ``regex`` to search the :class:`~models_cisco.IOSCfgLine` text 
         and return the contents of the regular expression group, at the 
         integer ``group`` index, cast as ``result_type``; if there is no 
match, 
         ``default`` is returned.
@@ -542,6 +543,7 @@
             - group (int): An integer which specifies the desired regex group 
to be returned.  ``group`` defaults to 1.
             - result_type (type): A type (typically one of: ``str``, ``int``, 
``float``, or ``IPv4Obj``).  All returned values are cast as ``result_type``, 
which defaults to ``str``.
             - default (any): The default value to be returned, if there is no 
match.
+            - untyped_default (bool): Set True if you don't want the default 
value to be typed
 
         Returns:
             - ``result_type``.  The text matched by the regular expression 
group; if there is no match, ``default`` is returned.  All values are cast as 
``result_type``.
@@ -584,13 +586,15 @@
         if not (mm is None):
             if not (mm.group(group) is None):
                 return result_type(mm.group(group))
-            else:
-                return result_type(default)
-        return result_type(default)
+
+        if untyped_default:
+            return default
+        else:
+            return result_type(default)
 
     def re_match_iter_typed(self, regex, group=1, result_type=str, default='', 
-        all_children=False):
-        """Use ``regex`` to search the children of 
+        untyped_default=False, all_children=False):
+        r"""Use ``regex`` to search the children of 
         :class:`~models_cisco.IOSCfgLine` text and return the contents of 
         the regular expression group, at the integer ``group`` index, cast as 
         ``result_type``; if there is no match, ``default`` is returned.
@@ -602,6 +606,7 @@
             - result_type (type): A type (typically one of: ``str``, ``int``, 
``float``, or :class:`~ccp_util.IPv4Obj`).         All returned values are cast 
as ``result_type``, which defaults to ``str``.
             - default (any): The default value to be returned, if there is no 
match.
             - all_children (bool): Set True if you want to search all children 
(children, grand children, great grand children, etc...)
+            - untyped_default (bool): Set True if you don't want the default 
value to be typed
 
         Returns:
             - ``result_type``.  The text matched by the regular expression 
group; if there is no match, ``default`` is returned.  All values are cast as 
``result_type``.
@@ -647,13 +652,21 @@
                 mm = re.search(regex, cobj.text)
                 if not (mm is None):
                     return result_type(mm.group(group))
-            return result_type(default)
+            ## Ref Github issue #121
+            if untyped_default:
+                return default
+            else:
+                return result_type(default)
         else:
             for cobj in self.all_children:
                 mm = re.search(regex, cobj.text)
                 if not (mm is None):
                     return result_type(mm.group(group))
-            return result_type(default)
+            ## Ref Github issue #121
+            if untyped_default:
+                return default
+            else:
+                return result_type(default)
 
     def reset(self):
         # For subclass APIs
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ciscoconfparse-1.3.15/ciscoconfparse/ccp_util.py 
new/ciscoconfparse-1.3.32/ciscoconfparse/ccp_util.py
--- old/ciscoconfparse-1.3.15/ciscoconfparse/ccp_util.py        2018-04-21 
16:01:00.000000000 +0200
+++ new/ciscoconfparse-1.3.32/ciscoconfparse/ccp_util.py        2019-03-17 
13:42:33.000000000 +0100
@@ -1,4 +1,3 @@
-from collections import MutableSequence
 import itertools
 import socket
 import time
@@ -6,17 +5,23 @@
 import re
 import os
 
+if (sys.version_info>=(3, 0, 0,)):
+    from collections.abc import MutableSequence
+else:
+    ## This syntax is not supported in Python 3...
+    from collections import MutableSequence
+
 from protocol_values import ASA_TCP_PORTS, ASA_UDP_PORTS
 from dns.exception import DNSException
 from dns.resolver import Resolver
-from dns import reversename, query
+from dns import reversename, query, zone
 
 if sys.version_info[0] < 3:
     from ipaddr import IPv4Network, IPv6Network, IPv4Address, IPv6Address
 else:
     from ipaddress import IPv4Network, IPv6Network, IPv4Address, IPv6Address
 """ ccp_util.py - Parse, Query, Build, and Modify IOS-style configurations
-     Copyright (C) 2014-2015, 2018 David Michael Pennington
+     Copyright (C) 2014-2015, 2018-2019 David Michael Pennington
 
      This program is free software: you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published by
@@ -132,7 +137,7 @@
 >>> net.network_object
 IPv4Network('172.16.1.0/24')
 >>> str(net.network_object)
-'172.16.1.0'
+'172.16.1.0/24'
 >>> net.prefixlen
 24
 >>> net.network_object.iterhosts()
@@ -777,15 +782,16 @@
         A set() of :class:`~ccp_util.DNSResponse` instances
 
 >>> from ciscoconfparse.ccp_util import dns_query
->>> dns_query('www.pennington.net', 'A', '4.2.2.2')
-set([<DNSResponse 'A' result_str='65.19.187.2'>])
+>>> dns_query('www.pennington.net', "A", "4.2.2.2")
+set([<DNSResponse "A" result_str="65.19.187.2">])
 >>> answer = dns_query('www.pennington.net', 'A', '4.2.2.2')
 >>> str(answer.pop())
 '65.19.187.2'
 >>>
     """
-        
-    valid_records = set(['A', 'AAAA', 'CNAME', 'MX', 'NS', 'PTR', 'TXT'])
+
+    valid_records = set(['A', 'AAAA', 'AXFR', 'CNAME', 'MX', 'NS', 'PTR', 
+        'TXT'])
     query_type = query_type.upper()
     assert query_type in valid_records
     assert server!=""
@@ -814,6 +820,10 @@
                 response.has_error = True
                 response.error_str = e
                 retval.add(response)
+    elif query_type=="AXFR":
+        """This is a hack: return text of zone transfer, instead of axfr 
objs"""
+        _zone = zone.from_xfr(query.xfr(server, input, lifetime=timeout))
+        return [_zone[node].to_text(node) for node in _zone.nodes.keys()]
     elif query_type=="CNAME":
         try:
             answer = resolver.query(input, query_type)
@@ -984,23 +994,29 @@
 
 >>> from ciscoconfparse.ccp_util import CiscoRange
 >>> CiscoRange('1-3,5,9-11,13')
-<CiscoRange ['1', '2', '3', '5', '9', '10', '11', '13']>
+<CiscoRange 1-3,5,9-11,13>
 >>> CiscoRange('Eth1/1-3,7')
-<CiscoRange ['Eth1/1', 'Eth1/2', 'Eth1/3', 'Eth1/7']>
+<CiscoRange Eth1/1-3,7>
+>>> CiscoRange()
+<CiscoRange none>
     """
-    def __init__(self, text, result_type=str):
+    def __init__(self, text="", result_type=str):
         super(CiscoRange, self).__init__()
         self.text = text
         self.result_type = result_type
         if text:
-            self.line_prefix, self.slot_prefix, self.range_text = 
self._parse_range_text(
-            )
+            self.line_prefix, self.slot_prefix, self.range_text = 
self._parse_range_text()
             self._list = self._range()
         else:
+            self.line_prefix = ""
+            self.slot_prefix = ""
             self._list = list()
 
     def __repr__(self):
-        return """<CiscoRange {0}>""".format(self._list)
+        if len(self._list)==0:
+            return """<CiscoRange none>"""
+        else:
+            return """<CiscoRange {0}>""".format(self.compressed_str)
 
     def __len__(self):
         return len(self._list)
@@ -1017,12 +1033,24 @@
     def __str__(self):
         return self.__repr__()
 
+    # Github issue #124
+    def __eq__(self, other):
+        assert hasattr(other, 'line_prefix')
+        self_prefix_str = self.line_prefix + self.slot_prefix
+        other_prefix_str = other.line_prefix + other.slot_prefix
+        cmp1 = self_prefix_str.lower()==other_prefix_str.lower()
+        cmp2 = sorted(self._list)==sorted(other._list)
+        return cmp1 and cmp2
+
     def insert(self, ii, val):
         ## Insert something at index ii
         for idx, obj in enumerate(
                 CiscoRange(
                     val, result_type=self.result_type)):
             self._list.insert(ii + idx, obj)
+
+        # Prune out any duplicate entries, and sort...
+        self._list = sorted(map(self.result_type, set(self._list)))
         return self
 
     def append(self, val):
@@ -1048,7 +1076,8 @@
             range_text = mm_result['range_text']
         return line_prefix, slot_prefix, range_text
 
-    def _dash_range(self, text):
+    def _parse_dash_range(self, text):
+        """Parse a dash Cisco range into a discrete list of items"""
         retval = list()
         for range_atom in text.split(','):
             try:
@@ -1068,14 +1097,16 @@
         def combine(arg):
             return self.line_prefix + self.slot_prefix + str(arg)
 
-        return [self.result_type(ii) for ii in map(combine, 
self._dash_range(self.range_text))]
+        return [self.result_type(ii) for ii in map(combine, 
self._parse_dash_range(self.range_text))]
 
     def remove(self, arg):
         remove_obj = CiscoRange(arg)
         for ii in remove_obj:
             try:
-                index = self.index(self.result_type(ii))
-                self.pop(index)
+                ## Remove arg, even if duplicated... Ref Github issue #126
+                while True:
+                    index = self.index(self.result_type(ii))
+                    self.pop(index)
             except ValueError:
                 pass
         return self
@@ -1083,3 +1114,55 @@
     @property
     def as_list(self):
         return self._list
+
+    ## Github issue #125
+    @property
+    def compressed_str(self):
+        """Return a text string with a compressed csv of values
+
+>>> from ciscoconfparse.ccp_util import CiscoRange
+>>> range_obj = CiscoRange('1,3,5,6,7')
+>>> range_obj.compressed_str
+'1,3,5-7'
+>>>
+        """
+        retval = list()
+        prefix_str = self.line_prefix + self.slot_prefix
+
+        # Build a list of integers (without prefix_str)
+        input = list()
+        for ii in self._list:
+            try:
+                unicode_ii = str(ii, 'utf-8') # Python2.7...
+            except:
+                unicode_ii = str(ii)
+            ii = re.sub(r'^{0}(\d+)$'.format(prefix_str), '\g<1>', unicode_ii)
+            input.append(int(ii))
+
+        if len(input)==0: # Special case, handle empty list
+            return ''
+
+        # source - https://stackoverflow.com/a/51227915/667301
+        input = sorted(list(set(input)))
+        range_list = [input[0]]
+        for ii in range(len(input)):
+            if ii+1<len(input) and ii-1>-1:
+                if (input[ii]-input[ii-1]==1) and (input[ii+1]-input[ii]==1):
+                    if  range_list[-1]!='-':
+                        range_list += ['-']
+                    else:
+                        range_list = range_list
+                else:
+                        range_list+=[input[ii]]
+        if len(input)>1:
+            range_list+=[input[len(input)-1]]
+
+        # Build the return value from range_list...
+        retval = prefix_str + str(range_list[0])
+        for ii in range(1,len(range_list)):
+            if str(type(range_list[ii]))!=str(type(range_list[ii-1])):
+                retval += str(range_list[ii])
+            else:
+                retval += ',' + str(range_list[ii])
+
+        return retval
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ciscoconfparse-1.3.15/ciscoconfparse/ciscoconfparse.py 
new/ciscoconfparse-1.3.32/ciscoconfparse/ciscoconfparse.py
--- old/ciscoconfparse-1.3.15/ciscoconfparse/ciscoconfparse.py  2018-04-21 
15:43:19.000000000 +0200
+++ new/ciscoconfparse-1.3.32/ciscoconfparse/ciscoconfparse.py  2019-03-17 
13:50:43.000000000 +0100
@@ -1,4 +1,3 @@
-from collections import MutableSequence, Iterator
 from operator import methodcaller, attrgetter
 from colorama import Fore, Back, Style
 from difflib import SequenceMatcher
@@ -8,6 +7,13 @@
 import re
 import os
 
+if (sys.version_info>=(3, 0, 0,)):
+    from collections.abc import MutableSequence, Iterator
+else:
+    ## This syntax is not supported in Python 3...
+    from collections import MutableSequence, Iterator
+
+
 from models_cisco import IOSHostnameLine, IOSRouteLine, IOSIntfLine
 from models_cisco import IOSAccessLine, IOSIntfGlobal
 from models_cisco import IOSAaaLoginAuthenticationLine
@@ -41,8 +47,8 @@
 
 from models_junos import JunosCfgLine
 
-""" ciscoconfparse.py - Parse, Query, Build, and Modify IOS-style 
configurations
-     Copyright (C) 2007-2018 David Michael Pennington
+r""" ciscoconfparse.py - Parse, Query, Build, and Modify IOS-style configs
+     Copyright (C) 2007-2019 David Michael Pennington
 
      This program is free software: you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published by
@@ -62,9 +68,15 @@
 """
 
 ## Docstring props: http://stackoverflow.com/a/1523456/667301
-__version__ = open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 
-    'version')).read().strip()
-__email__ = "mike /at\ pennington [dot] net"
+versionfilepath = os.path.join(os.path.dirname(os.path.abspath(__file__)),
+    'version')
+# __version__ if-else below fixes Github issue #123
+if os.path.isfile(versionfilepath):
+    __version__ = open(versionfilepath).read().strip()
+else:
+    # This case is required for importing from a zipfile... Github issue #123
+    __version__ = "0.0.0"  # __version__ read failed
+__email__ = r"mike /at\ pennington [dot] net"
 __author__ = "David Michael Pennington <{0}>".format(__email__)
 __copyright__ = "2007-{0}, {1}".format(time.strftime('%Y'), __author__)
 __license__ = "GPLv3"
@@ -209,7 +221,7 @@
                     if self.debug:
                         _log.debug("parsing from '{0}' with ios syntax".format(
                             config))
-                    f = open(config, mode="rU")
+                    f = open(config, **self.openargs)
                     text = f.read()
                     rgx = re.compile(linesplit_rgx)
                     self.ConfigObjs = IOSConfigList(
@@ -225,7 +237,7 @@
                     if self.debug:
                         _log.debug("parsing from '{0}' with nxos 
syntax".format(
                             config))
-                    f = open(config, mode="rU")
+                    f = open(config, **self.openargs)
                     text = f.read()
                     rgx = re.compile(linesplit_rgx)
                     self.ConfigObjs = NXOSConfigList(
@@ -241,7 +253,7 @@
                     if self.debug:
                         _log.debug("parsing from '{0}' with asa syntax".format(
                             config))
-                    f = open(config, mode="rU")
+                    f = open(config, **self.openargs)
                     text = f.read()
                     rgx = re.compile(linesplit_rgx)
                     self.ConfigObjs = ASAConfigList(
@@ -258,7 +270,7 @@
                     if self.debug:
                         _log.debug("parsing from '{0}' with junos syntax".
                                    format(config))
-                    f = open(config, mode="rU")
+                    f = open(config, **self.openargs)
                     text = f.read()
                     rgx = re.compile(linesplit_rgx)
 
@@ -290,6 +302,17 @@
             self.factory)
 
     @property
+    def openargs(self):
+        """Fix for Py3.5 deprecation of universal newlines - Ref Github #114
+        also see https://softwareengineering.stackexchange.com/q/298677/23144
+        """
+        if (sys.version_info>=(3, 5, 0,)):
+            retval = {'mode': 'r', 'newline': None}
+        else:
+            retval = {'mode': 'rU'}
+        return retval
+
+    @property
     def ioscfg(self):
         """A list containing all text configuration statements"""
         ## I keep this here to emulate the legacy ciscoconfparse behavior
@@ -367,7 +390,7 @@
                 line = results.get('line', '')
 
                 ## Hack to fix Github issue #49 (empty double braces at end)
-                nn = re.search('^(.+?)\{\s*\}\s*$', input)
+                nn = re.search(r'^(.+?)\{\s*\}\s*$', input)
                 if nn is not None:
                     # Detect double braces at the end of a line and strip them
                     line = nn.group(1)
@@ -1120,7 +1143,7 @@
         return list(map(attrgetter('text'), tmp))
 
     def find_objects_wo_child(self, parentspec, childspec, ignore_ws=False):
-        """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.
+        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.
 
         Args:
             - parentspec (str): Text regular expression for the 
:class:`~models_cisco.IOSCfgLine` object to be matched; this must match the 
parent's line
@@ -1198,7 +1221,7 @@
         ]
 
     def find_parents_wo_child(self, parentspec, childspec, ignore_ws=False):
-        """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.
+        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.
 
         Args:
             - parentspec (str): Text regular expression for the line to be 
matched; this must match the parent's line
@@ -1272,7 +1295,7 @@
         return list(map(attrgetter('text'), tmp))
 
     def find_children_w_parents(self, parentspec, childspec, ignore_ws=False):
-        """Parse through the children of all parents matching parentspec, 
+        r"""Parse through the children of all parents matching parentspec, 
         and return a list of children that matched the childspec.
 
         Args:
@@ -1353,8 +1376,8 @@
            ...           '!',
            ...     ]
            >>> p = CiscoConfParse(config)
-           >>> p.find_children_w_parents('^interface\sFastEthernet0/1', \
-           'port-security')
+           >>> p.find_children_w_parents('^interface\sFastEthernet0/1', 
+           ... 'port-security')
            [' switchport port-security', ' switchport port-security violation 
protect', ' switchport port-security aging time 5', ' switchport port-security 
aging type inactivity']
            >>>
 
@@ -1374,7 +1397,7 @@
         return list(map(attrgetter('text'), sorted(retval)))
 
     def find_objects_w_parents(self, parentspec, childspec, ignore_ws=False):
-        """Parse through the children of all parents matching parentspec, 
+        r"""Parse through the children of all parents matching parentspec, 
         and return a list of child objects, which matched the childspec.
 
         Args:
@@ -1445,8 +1468,8 @@
            ...           '                address 172.16.15.5/22',
            ...     ]
            >>> p = CiscoConfParse(config)
-           >>> p.find_objects_w_parents('^\s*interfaces', \
-           r'\s+ge-0/0/1')
+           >>> p.find_objects_w_parents('^\s*interfaces', 
+           ... r'\s+ge-0/0/1')
            [<IOSCfgLine # 7 '    ge-0/0/1' (parent is # 0)>]
            >>>
 
@@ -1690,7 +1713,7 @@
                          excludespec=None,
                          exactmatch=False,
                          atomic=False):
-        """Replace lines matching `childspec` within the `parentspec`'s 
+        r"""Replace lines matching `childspec` within the `parentspec`'s 
         immediate children.
 
         Args:
@@ -1846,7 +1869,7 @@
         return retval
 
     def req_cfgspec_excl_diff(self, linespec, uncfgspec, cfgspec):
-        """
+        r"""
         req_cfgspec_excl_diff accepts a linespec, an unconfig spec, and
         a list of required configuration elements.  Return a list of
         configuration diffs to make the configuration comply.  **All** other
@@ -2025,7 +2048,7 @@
                   ignore_order=True,
                   remove_lines=True,
                   debug=False):
-        """
+        r"""
         ``sync_diff()`` accepts a list of required configuration elements, 
         a linespec, and an unconfig spec.  This method return a list of
         configuration diffs to make the configuration comply with cfgspec.
@@ -2077,22 +2100,22 @@
         b = CiscoConfParse(cfgspec, factory=False)
         b_lines = b.ioscfg
 
-        a_heirarchy = list()
-        b_heirarchy = list()
+        a_hierarchy = list()
+        b_hierarchy = list()
 
         ## Build heirarchical, equal-length lists of parents / non-parents
-        a_parents, a_nonparents = a.ConfigObjs.config_heirarchy()
-        b_parents, b_nonparents = b.ConfigObjs.config_heirarchy()
+        a_parents, a_nonparents = a.ConfigObjs.config_hierarchy()
+        b_parents, b_nonparents = b.ConfigObjs.config_hierarchy()
 
         obj = DiffObject(0, a_nonparents, a_parents)
-        a_heirarchy.append(obj)
+        a_hierarchy.append(obj)
 
         obj = DiffObject(0, b_nonparents, b_parents)
-        b_heirarchy.append(obj)
+        b_hierarchy.append(obj)
 
         retval = list()
         ## Assign config_this and unconfig_this attributes by "diff level"
-        for adiff_level, bdiff_level in zip(a_heirarchy, b_heirarchy):
+        for adiff_level, bdiff_level in zip(a_hierarchy, b_hierarchy):
             for attr in ['parents', 'nonparents']:
                 if attr == 'parents':
                     if ignore_order:
@@ -2341,7 +2364,7 @@
 
         ## Strip out 'double negatives' (i.e. 'no no ')
         for idx in range(0, len(retval)):
-            retval[idx] = re.sub(r'(\s+)no\s+no\s+(\S+.+?)$', '\g<1>\g<2>',
+            retval[idx] = re.sub(r'(\s+)no\s+no\s+(\S+.+?)$', r'\g<1>\g<2>',
                                  retval[idx])
 
         if debug:
@@ -2365,22 +2388,27 @@
     ### The methods below are marked SEMI-PRIVATE because they return an object
     ###  or iterable of objects instead of the configuration text itself.
     def _build_space_tolerant_regex(self, linespec):
-        """SEMI-PRIVATE: Accept a string, and return a string with all
+        r"""SEMI-PRIVATE: Accept a string, and return a string with all
         spaces replaced with '\s+'"""
 
-        # Unicode below
+        # Unicode below...
         backslash = '\x5c'
+        # escaped_space = "\\s+" (not a raw string)
+        if (sys.version_info>=(3, 0, 0,)):
+            escaped_space = (backslash + backslash + "s+").translate('utf-8')
+        else:
+            escaped_space = backslash + backslash + "s+"
 
         LINESPEC_LIST_TYPE = bool(getattr(linespec, 'append', False))
 
         if not LINESPEC_LIST_TYPE:
             assert bool(getattr(linespec, 'upper', False)) # Ensure it's a str
-            linespec = re.sub(r'\s+', backslash + "s+", linespec)
+            linespec = re.sub(r'\s+', escaped_space, linespec)
         else:
             for idx in range(0, len(linespec)):
                 ## Ensure this element is a string
                 assert bool(getattr(linespec[idx], 'upper', False))
-                linespec[idx] = re.sub(r'\s+', backslash + "s+", linespec[idx])
+                linespec[idx] = re.sub(r'\s+', escaped_space, linespec[idx])
 
         return linespec
 
@@ -2615,7 +2643,7 @@
         list_idx = len(self._list)
         self.insert(list_idx, val)
 
-    def config_heirarchy(self):
+    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)
@@ -2623,7 +2651,7 @@
         parent_siblings = list()
         nonparent_siblings = list()
 
-        for obj in self.CiscoConfParse.find_objects('^\S+'):
+        for obj in self.CiscoConfParse.find_objects(r'^\S+'):
             if obj.is_comment:
                 continue
             elif len(obj.children) == 0:
@@ -2638,7 +2666,7 @@
         banner_objs = list(
             filter(lambda obj: REGEX.search(obj.text), self._list))
 
-        BANNER_STR_RE = 
r'^(?:(?P<btype>(?:set\s+)*banner\s\w+\s+)(?P<bchar>\S)(?:\S)?)$'
+        BANNER_STR_RE = 
r'^(?:(?P<btype>(?:set\s+)*banner\s\w+\s+)(?P<bchar>\S))'
         for parent in banner_objs:
             parent.oldest_ancestor = True
 
@@ -2670,6 +2698,7 @@
                                                      parent.linenum))
                         break
 
+                ## Use code below to identify children of the banner line
                 idx += 1
                 try:
                     obj = self._list[idx]
@@ -2687,8 +2716,9 @@
                         parent.child_indent = 0
                         obj.parent = parent
                         break
-                    elif obj.is_comment and (obj.indent == 0):
-                        break
+                    # Commenting the following lines out; fix Github issue #115
+                    #elif obj.is_comment and (obj.indent == 0):
+                    #    break
                     parent.children.append(obj)
                     parent.child_indent = 0
                     obj.parent = parent
@@ -3026,7 +3056,7 @@
         list_idx = len(self._list)
         self.insert(list_idx, val)
 
-    def config_heirarchy(self):
+    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)
@@ -3034,7 +3064,7 @@
         parent_siblings = list()
         nonparent_siblings = list()
 
-        for obj in self.CiscoConfParse.find_objects('^\S+'):
+        for obj in self.CiscoConfParse.find_objects(r'^\S+'):
             if obj.is_comment:
                 continue
             elif len(obj.children) == 0:
@@ -3049,7 +3079,7 @@
         banner_objs = list(
             filter(lambda obj: REGEX.search(obj.text), self._list))
 
-        BANNER_STR_RE = 
r'^(?:(?P<btype>(?:set\s+)*banner\s\w+\s+)(?P<bchar>\S)(?:\S)?)$'
+        BANNER_STR_RE = 
r'^(?:(?P<btype>(?:set\s+)*banner\s\w+\s+)(?P<bchar>\S))'
         for parent in banner_objs:
             parent.oldest_ancestor = True
 
@@ -3437,14 +3467,14 @@
         list_idx = len(self._list)
         self.insert(list_idx, val, atomic)
 
-    def config_heirarchy(self):
+    def config_hierarchy(self):
         """Walk this configuration and return the following tuple
         at each parent 'level':
             (list_of_parent_siblings, list_of_nonparent_siblings)"""
         parent_siblings = list()
         nonparent_siblings = list()
 
-        for obj in self.CiscoConfParse.find_objects('^\S+'):
+        for obj in self.CiscoConfParse.find_objects(r'^\S+'):
             if obj.is_comment:
                 continue
             elif len(obj.children) == 0:
@@ -3633,7 +3663,7 @@
 
 
 class DiffObject(object):
-    """This object should be used at every level of heirarchy"""
+    """This object should be used at every level of hierarchy"""
 
     def __init__(self, level, nonparents, parents):
         self.level = level
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ciscoconfparse-1.3.15/ciscoconfparse/models_cisco.py 
new/ciscoconfparse-1.3.32/ciscoconfparse/models_cisco.py
--- old/ciscoconfparse-1.3.15/ciscoconfparse/models_cisco.py    2018-01-24 
12:16:08.000000000 +0100
+++ new/ciscoconfparse-1.3.32/ciscoconfparse/models_cisco.py    2019-03-17 
02:57:15.000000000 +0100
@@ -18,7 +18,7 @@
 ###
 ###   Use models_cisco.py at your own risk.  You have been warned :-)
 """ models_cisco.py - Parse, Query, Build, and Modify IOS-style configurations
-     Copyright (C) 2014-2018 David Michael Pennington
+     Copyright (C) 2014-2019 David Michael Pennington
 
      This program is free software: you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published by
@@ -37,6 +37,8 @@
      mike [~at~] pennington [/dot\] net
 """
 
+MAX_VLAN = 4094
+
 ##
 ##-------------  IOS Configuration line object
 ##
@@ -158,10 +160,10 @@
            ...     '!',
            ...     ]
            >>> parse = CiscoConfParse(config)
-           >>> obj = parse.find_objects('^interface\sSerial')[0]
+           >>> obj = parse.find_objects(r'^interface\sSerial')[0]
            >>> obj.is_subintf
            False
-           >>> obj = parse.find_objects('^interface\sATM')[0]
+           >>> obj = parse.find_objects(r'^interface\sATM')[0]
            >>> obj.is_subintf
            True
            >>>
@@ -203,10 +205,10 @@
            ...     '!',
            ...     ]
            >>> parse = CiscoConfParse(config)
-           >>> obj = parse.find_objects('^interface\sFast')[0]
+           >>> obj = parse.find_objects(r'^interface\sFast')[0]
            >>> obj.is_loopback_intf
            False
-           >>> obj = parse.find_objects('^interface\sLoop')[0]
+           >>> obj = parse.find_objects(r'^interface\sLoop')[0]
            >>> obj.is_loopback_intf
            True
            >>>
@@ -1104,6 +1106,23 @@
         return retval
 
     @property
+    def has_ip_helper_addresses(self):
+        """Return a True if the intf has helper-addresses; False if not"""
+        if len(self.ip_helper_addresses)>0:
+            return True
+        return False
+
+    @property
+    def ip_helper_addresses(self):
+        """Return a list of IP helper-addresses"""
+        retval = list()
+        for child in self.children:
+            if 'helper-address' in child.text:
+                addr = child.re_match_typed('ip\s+helper-address\s+(\S+)')
+                retval.append(addr)
+        return retval
+
+    @property
     def is_switchport(self):
         retval = self.re_match_iter_typed(
             r'^\s*(switchport)\s*', result_type=bool, default=False)
@@ -1187,54 +1206,63 @@
 
     @property
     def trunk_vlans_allowed(self):
-        """Return a CiscoRange() with the list of allowed vlan numbers.  
Return 0 if the port isn't a switchport"""
-        if self.is_switchport:
-            default_val = '1-4094'
-        else:
-            default_val = 0
+        """Return a CiscoRange() with the list of allowed vlan numbers (as 
int).  Return 0 if the port isn't a switchport in trunk mode"""
 
-        allowed = self.re_match_iter_typed(
-            r'^\s*switchport\s+trunk\s+allowed\s+vlan\s+(\S+)$',
-            result_type=str,
-            default='1-4094')
-        if allowed == 'all':
-            allowed = '1-4094'
-        elif allowed == 'none':
-            allowed = ''
-        add = self.re_match_iter_typed(
-            r'^\s*switchport\s+trunk\s+allowed\s+vlan\s+add\s+(\S+)$',
-            result_type=str,
-            default='')
-
-        if allowed and add:
-            combined = allowed + ',' + add
-        elif allowed:
-            combined = allowed
-        elif add:
-            combined = add
+        # The default values...
+        if self.is_switchport and not self.has_manual_switch_access:
+            retval = CiscoRange('1-{0}'.format(MAX_VLAN), result_type=int)
         else:
-            combined = allowed
-
-        remove = self.re_match_iter_typed(
-            r'^\s*switchport\s+trunk\s+allowed\s+vlan\s+remove\s+(\S+)$',
-            result_type=str,
-            default='')
+            return 0
 
-        if remove:
-            retval = CiscoRange(combined, result_type=int).remove(remove)
-        else:
-            retval = CiscoRange(combined, result_type=int)
-
-        _except = self.re_match_iter_typed(
-            r'^\s*switchport\s+trunk\s+allowed\s+vlan\s+except\s+(\S+)$',
-            result_type=str,
-            default='')
+        ## Iterate over switchport trunk statements
+        for obj in self.children:
 
-        if _except:
-            retval = CiscoRange(combined, result_type=int).remove(_except)
+            ## For every child object, check whether the vlan list is modified
+            abs_str = obj.re_match_typed(
+                '^\s+switchport\s+trunk\s+allowed\s+vlan\s(all|none|\d.*?)$',
+                default='_nomatch_', result_type=str).lower()
+            add_str = obj.re_match_typed(
+                '^\s+switchport\s+trunk\s+allowed\s+vlan\s+add\s+(\d.*?)$',
+                default='_nomatch_', result_type=str).lower()
+            exc_str = obj.re_match_typed(
+                '^\s+switchport\s+trunk\s+allowed\s+vlan\s+except\s+(\d.*?)$',
+                default='_nomatch_', result_type=str).lower()
+            rem_str = obj.re_match_typed(
+                '^\s+switchport\s+trunk\s+allowed\s+vlan\s+remove\s+(\d.*?)$',
+                default='_nomatch_', result_type=str).lower()
+
+            ## Build a vdict for each vlan modification statement
+            vdict = {
+                'absolute_str': abs_str,
+                'add_str': add_str,
+                'except_str': exc_str,
+                'remove_str': rem_str,
+            }
+
+            ## Analyze each vdict in sequence and apply to retval sequentially
+            for key, val in vdict.items():
+                if val!='_nomatch_':
+                    ## absolute in the key overrides previous values
+                    if 'absolute' in key:
+                        if val.lower()=='all':
+                            retval = CiscoRange('1-{0}'.format(MAX_VLAN), 
+                                result_type=int)
+                        elif val.lower()=='none':
+                            retval = CiscoRange(result_type=int)
+                        else:
+                            retval = CiscoRange(val, result_type=int)
+                    elif 'add' in key:
+                        retval.append(val)
+                    elif 'except' in key:
+                        retval = CiscoRange('1-{0}'.format(MAX_VLAN), 
+                            result_type=int)
+                        retval.remove(val)
+                    elif 'remove' in key:
+                        retval.remove(val)
 
         return retval
 
+
     @property
     def native_vlan(self):
         """Return an integer with the native vlan number.  Return 1, if the 
switchport has no explicit native vlan configured; return 0 if the port isn't a 
switchport"""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ciscoconfparse-1.3.15/ciscoconfparse/models_nxos.py 
new/ciscoconfparse-1.3.32/ciscoconfparse/models_nxos.py
--- old/ciscoconfparse-1.3.15/ciscoconfparse/models_nxos.py     2018-01-24 
12:14:54.000000000 +0100
+++ new/ciscoconfparse-1.3.32/ciscoconfparse/models_nxos.py     2019-03-17 
13:50:56.000000000 +0100
@@ -17,7 +17,7 @@
 ###
 ###   You have been warned :-)
 """ models_nxos.py - Parse, Query, Build, and Modify IOS-style configurations
-     Copyright (C) 2016-2018 David Michael Pennington
+     Copyright (C) 2016-2019 David Michael Pennington
 
      This program is free software: you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published by
@@ -40,6 +40,7 @@
 ##-------------  IOS Configuration line object
 ##
 
+MAX_VLAN = 4094
 
 class NXOSCfgLine(BaseCfgLine):
     """An object for a parsed IOS-style configuration line.  
@@ -1181,54 +1182,64 @@
 
     @property
     def trunk_vlans_allowed(self):
-        """Return a CiscoRange() with the list of allowed vlan numbers.  
Return 0 if the port isn't a switchport"""
-        if self.is_switchport:
-            default_val = '1-4094'
-        else:
-            default_val = 0
-
-        allowed = self.re_match_iter_typed(
-            r'^\s*switchport\s+trunk\s+allowed\s+vlan\s+(\S+)$',
-            result_type=str,
-            default='1-4094')
-        if allowed == 'all':
-            allowed = '1-4094'
-        elif allowed == 'none':
-            allowed = ''
-        add = self.re_match_iter_typed(
-            r'^\s*switchport\s+trunk\s+allowed\s+vlan\s+add\s+(\S+)$',
-            result_type=str,
-            default='')
-
-        if allowed and add:
-            combined = allowed + ',' + add
-        elif allowed:
-            combined = allowed
-        elif add:
-            combined = add
-        else:
-            combined = allowed
-
-        remove = self.re_match_iter_typed(
-            r'^\s*switchport\s+trunk\s+allowed\s+vlan\s+remove\s+(\S+)$',
-            result_type=str,
-            default='')
+        """Return a CiscoRange() with the list of allowed vlan numbers (as 
int).  Return 0 if the port isn't a switchport in trunk mode"""
 
-        if remove:
-            retval = CiscoRange(combined, result_type=int).remove(remove)
+        # The default values...
+        if self.is_switchport and not self.has_manual_switch_access:
+            retval = CiscoRange('1-{0}'.format(MAX_VLAN), result_type=int)
         else:
-            retval = CiscoRange(combined, result_type=int)
+            return 0
 
-        _except = self.re_match_iter_typed(
-            r'^\s*switchport\s+trunk\s+allowed\s+vlan\s+except\s+(\S+)$',
-            result_type=str,
-            default='')
+        ## Iterate over switchport trunk statements
+        for obj in self.children:
 
-        if _except:
-            retval = CiscoRange(combined, result_type=int).remove(_except)
+            ## For every child object, check whether the vlan list is modified
+            abs_str = obj.re_match_typed(
+                '^\s+switchport\s+trunk\s+allowed\s+vlan\s(all|none|\d.*?)$',
+                default='_nomatch_', result_type=str).lower()
+            add_str = obj.re_match_typed(
+                '^\s+switchport\s+trunk\s+allowed\s+vlan\s+add\s+(\d.*?)$',
+                default='_nomatch_', result_type=str).lower()
+            exc_str = obj.re_match_typed(
+                '^\s+switchport\s+trunk\s+allowed\s+vlan\s+except\s+(\d.*?)$',
+                default='_nomatch_', result_type=str).lower()
+            rem_str = obj.re_match_typed(
+                '^\s+switchport\s+trunk\s+allowed\s+vlan\s+remove\s+(\d.*?)$',
+                default='_nomatch_', result_type=str).lower()
+
+
+            ## Build a vdict for each vlan modification statement
+            vdict = {
+                'absolute_str': abs_str,
+                'add_str': add_str,
+                'except_str': exc_str,
+                'remove_str': rem_str,
+            }
+
+            ## Analyze each vdict in sequence and apply to retval sequentially
+            for key, val in vdict.items():
+                if val!='_nomatch_':
+                    ## absolute in the key overrides previous values
+                    if 'absolute' in key:
+                        if val.lower()=='all':
+                            retval = CiscoRange('1-{0}'.format(MAX_VLAN),
+                                result_type=int)
+                        elif val.lower()=='none':
+                            retval = CiscoRange(result_type=int)
+                        else:
+                            retval = CiscoRange(val, result_type=int)
+                    elif 'add' in key:
+                        retval.append(val)
+                    elif 'except' in key:
+                        retval = CiscoRange('1-{0}'.format(MAX_VLAN),
+                            result_type=int)
+                        retval.remove(val)
+                    elif 'remove' in key:
+                        retval.remove(val)
 
         return retval
 
+
     @property
     def native_vlan(self):
         """Return an integer with the native vlan number.  Return 1, if the 
switchport has no explicit native vlan configured; return 0 if the port isn't a 
switchport"""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ciscoconfparse-1.3.15/ciscoconfparse/version 
new/ciscoconfparse-1.3.32/ciscoconfparse/version
--- old/ciscoconfparse-1.3.15/ciscoconfparse/version    2018-04-21 
16:08:38.000000000 +0200
+++ new/ciscoconfparse-1.3.32/ciscoconfparse/version    2019-03-17 
13:45:07.000000000 +0100
@@ -1 +1 @@
-1.3.15
+1.3.32
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ciscoconfparse-1.3.15/ciscoconfparse.egg-info/PKG-INFO 
new/ciscoconfparse-1.3.32/ciscoconfparse.egg-info/PKG-INFO
--- old/ciscoconfparse-1.3.15/ciscoconfparse.egg-info/PKG-INFO  2018-04-21 
16:09:21.000000000 +0200
+++ new/ciscoconfparse-1.3.32/ciscoconfparse.egg-info/PKG-INFO  2019-03-17 
13:53:07.000000000 +0100
@@ -1,12 +1,11 @@
 Metadata-Version: 1.1
 Name: ciscoconfparse
-Version: 1.3.15
+Version: 1.3.32
 Summary: Parse, Audit, Query, Build, and Modify Cisco IOS-style configurations
 Home-page: http://www.pennington.net/py/ciscoconfparse/
 Author: David Michael Pennington
 Author-email: [email protected]
 License: GPLv3
-Description-Content-Type: UNKNOWN
 Description: ==============
         ciscoconfparse
         ==============
@@ -83,9 +82,9 @@
         Pre-requisites
         ==============
         
-        ciscoconfparse_ requires Python versions 2.6, 2.7 or 3.2+; the OS 
should not
-        matter. If you want to run it under a Python virtualenv_, it's been 
heavily 
-        tested in that environment as well.
+        As of version 1.3.32, ciscoconfparse_ requires Python versions 2.7 or 
3.4+ 
+        (note: version 3.7.0 has a bug - ref Github issue #117, but version 
3.7.1 
+        works); the OS should not matter.
         
         .. _Installation:
         
@@ -98,7 +97,7 @@
         ::
         
               # Substitute whatever ciscoconfparse version you like...
-              easy_install -U ciscoconfparse==1.3.14
+              easy_install -U ciscoconfparse==1.3.32
         
         Alternatively you can install into Python2.x with pip_:
         
@@ -133,7 +132,7 @@
         FAQ
         ===
         
-        #) *QUESTION*: I want to use ciscoconfparse_ with Python3; is that 
safe?  *ANSWER*: As long as you're using Python 3.3 or higher, it's safe. I 
test every release against Python 3.2+; however, Python 3.2 is currently 
exposed to a small bug for some configurations (see `Github Issue #14`_).
+        #) *QUESTION*: I want to use ciscoconfparse_ with Python3; is that 
safe?  *ANSWER*: *ANSWER*: Yes.
         
         #) *QUESTION*: Some of the code in the documentation looks different 
than what I'm used to seeing.  Did you change something?  *ANSWER*: Yes, 
starting around ciscoconfparse_ v0.9.10 I introducted more methods directly on 
``IOSConfigLine()`` objects; going forward, these methods are the preferred way 
to use ciscoconfparse_.  Please start using the new methods shown in the 
example, since they're faster, and you type much less code this way.
         
@@ -167,7 +166,7 @@
         Unit-Tests
         ==========
         
-        `Travis CI project <https://travis-ci.org>`_ tests ciscoconfparse on 
Python versions 2.6 through 3.6, as well as a `pypy JIT`_ executable.
+        `Travis CI project <https://travis-ci.org>`_ tests ciscoconfparse on 
Python versions 2.7 through 3.7, as well as a `pypy JIT`_ executable.
         
         Click the image below for details; the current build status is:
         
@@ -182,7 +181,7 @@
         =====================
         
         ciscoconfparse_ is licensed GPLv3_; Copyright `David Michael 
Pennington`_, 
-        2007-2018.
+        2007-2019.
         
         ciscoconfparse_ is not affiliated with Cisco Systems in any way; the 
word "Cisco" is a registered trademark of Cisco Systems
         
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ciscoconfparse-1.3.15/ciscoconfparse.egg-info/requires.txt 
new/ciscoconfparse-1.3.32/ciscoconfparse.egg-info/requires.txt
--- old/ciscoconfparse-1.3.15/ciscoconfparse.egg-info/requires.txt      
2018-04-21 16:09:21.000000000 +0200
+++ new/ciscoconfparse-1.3.32/ciscoconfparse.egg-info/requires.txt      
2019-03-17 13:53:07.000000000 +0100
@@ -1,3 +1,6 @@
-ipaddr>=2.1.11
-dnspython
 colorama
+passlib
+dnspython
+
+[:python_version<'3']
+ipaddr>=2.1.11
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ciscoconfparse-1.3.15/requirements.txt 
new/ciscoconfparse-1.3.32/requirements.txt
--- old/ciscoconfparse-1.3.15/requirements.txt  2018-03-03 15:34:43.000000000 
+0100
+++ new/ciscoconfparse-1.3.32/requirements.txt  2019-02-19 03:02:10.000000000 
+0100
@@ -1,4 +1,6 @@
-ipaddr>=2.1.11
-passlib
 colorama
+passlib
 dnspython
+
+[:python_version<'3']
+ipaddr>=2.1.11
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ciscoconfparse-1.3.15/setup.py 
new/ciscoconfparse-1.3.32/setup.py
--- old/ciscoconfparse-1.3.15/setup.py  2018-01-20 12:47:16.000000000 +0100
+++ new/ciscoconfparse-1.3.32/setup.py  2019-02-19 03:02:10.000000000 +0100
@@ -16,12 +16,11 @@
 
 
 ## Conditionally require the correct ipaddr package in Python2 vs Python3
-if sys.version_info[0]<3:
-    IPADDR = "ipaddr>=2.1.11"
-    DNSPYTHON = "dnspython"
-else:
-    IPADDR = "ipaddress"
-    DNSPYTHON = "dnspython3"
+# Ref Github issue #127 - sdist improvements
+REQUIRES = ['colorama', 'passlib', 'dnspython']
+EXTRAS = {
+    ":python_version<'3'": ['ipaddr>=2.1.11'],
+}
 
 setup(name='ciscoconfparse',
       version=open(os.path.join(os.path.dirname(os.path.abspath(__file__)),
@@ -39,7 +38,8 @@
       packages=find_packages(),
       use_2to3=True,             # Reqd for Windows + Py3 - ref Github issue 
#32
       zip_safe=False,
-      install_requires = [IPADDR, DNSPYTHON, 'colorama'],   # Package 
dependencies here
+      install_requires = REQUIRES,
+      extras_require = EXTRAS, # Conditional dependencies Github isssue #127
       #setup_requires=["setuptools_hg"],  # setuptools_hg must be installed as 
a python module
       classifiers=[
           'Development Status :: 5 - Production/Stable',


Reply via email to