From 0204428d4b48bcc87a9ed7832a267c89027d958be1b8e7d8c2d1b90f35b4e865 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mark=C3=A9ta=20Machov=C3=A1?= Date: Tue, 22 Apr 2025 06:37:51 +0000 Subject: [PATCH] - Update to version 2.1.0 * Improved routine for terminal dimensions. * Improve clarity or variable names * Remove TerminalSize function .... no longer needed * Refactor pager class - Update to version 2.0.0 * Makes use of shutil library for terminal operations. - Drop not longer needed patches: * correct-version.patch * python-textfsm-no-python2.patch OBS-URL: https://build.opensuse.org/package/show/devel:languages:python/python-textfsm?expand=0&rev=27 --- correct-version.patch | 10 - python-textfsm-no-python2.patch | 2666 ------------------------------- python-textfsm.changes | 14 + python-textfsm.spec | 8 +- textfsm-1.1.3.tar.gz | 3 - textfsm-2.1.0.tar.gz | 3 + 6 files changed, 19 insertions(+), 2685 deletions(-) delete mode 100644 correct-version.patch delete mode 100644 python-textfsm-no-python2.patch delete mode 100644 textfsm-1.1.3.tar.gz create mode 100644 textfsm-2.1.0.tar.gz diff --git a/correct-version.patch b/correct-version.patch deleted file mode 100644 index 6ffa759..0000000 --- a/correct-version.patch +++ /dev/null @@ -1,10 +0,0 @@ -Index: textfsm-1.1.3/textfsm/__init__.py -=================================================================== ---- textfsm-1.1.3.orig/textfsm/__init__.py -+++ textfsm-1.1.3/textfsm/__init__.py -@@ -10,4 +10,4 @@ for each input entity. - """ - from textfsm.parser import * - --__version__ = '1.1.2' -+__version__ = '1.1.3' diff --git a/python-textfsm-no-python2.patch b/python-textfsm-no-python2.patch deleted file mode 100644 index 5e3ec53..0000000 --- a/python-textfsm-no-python2.patch +++ /dev/null @@ -1,2666 +0,0 @@ -From c8843d69daa9b565fea99a0283ad13c324d5b563 Mon Sep 17 00:00:00 2001 -From: Daniel Harrison -Date: Mon, 29 Jan 2024 12:48:00 +1100 -Subject: [PATCH] Remove python2 support. (#121) - -Change triggers release renumber to v2.0.0 (which is to be done when we pull into the stable branch) ---- - setup.py | 60 ++- - tests/clitable_test.py | 21 +- - tests/copyable_regex_object_test.py | 42 -- - tests/terminal_test.py | 11 +- - tests/textfsm_test.py | 641 ++++++++++++++++------------ - tests/texttable_test.py | 21 +- - textfsm/clitable.py | 40 +- - textfsm/copyable_regex_object.py | 41 -- - textfsm/parser.py | 188 ++++---- - textfsm/terminal.py | 159 +++---- - textfsm/texttable.py | 152 +++---- - 11 files changed, 689 insertions(+), 687 deletions(-) - delete mode 100755 tests/copyable_regex_object_test.py - delete mode 100755 textfsm/copyable_regex_object.py - -Index: textfsm-1.1.3/setup.py -=================================================================== ---- textfsm-1.1.3.orig/setup.py -+++ textfsm-1.1.3/setup.py -@@ -16,42 +16,40 @@ - - """Setup script.""" - --from setuptools import setup, find_packages --import textfsm - # To use a consistent encoding - from codecs import open - from os import path -+from setuptools import find_packages, setup -+import textfsm - - here = path.abspath(path.dirname(__file__)) - - # Get the long description from the README file --with open(path.join(here, 'README.md'), encoding="utf8") as f: -- long_description = f.read() -+with open(path.join(here, 'README.md'), encoding='utf8') as f: -+ long_description = f.read() - --setup(name='textfsm', -- maintainer='Google', -- maintainer_email='textfsm-dev@googlegroups.com', -- version=textfsm.__version__, -- description='Python module for parsing semi-structured text into python tables.', -- long_description=long_description, -- long_description_content_type='text/markdown', -- url='https://github.com/google/textfsm', -- license='Apache License, Version 2.0', -- classifiers=[ -- 'Development Status :: 5 - Production/Stable', -- 'Intended Audience :: Developers', -- 'License :: OSI Approved :: Apache Software License', -- 'Operating System :: OS Independent', -- 'Programming Language :: Python :: 2', -- 'Programming Language :: Python :: 3', -- 'Topic :: Software Development :: Libraries'], -- packages=['textfsm'], -- entry_points={ -- 'console_scripts': [ -- 'textfsm=textfsm.parser:main' -- ] -- }, -- include_package_data=True, -- package_data={'textfsm': ['../testdata/*']}, -- install_requires=['six', 'future'], -- ) -+setup( -+ name='textfsm', -+ maintainer='Google', -+ maintainer_email='textfsm-dev@googlegroups.com', -+ version=textfsm.__version__, -+ description=( -+ 'Python module for parsing semi-structured text into python tables.' -+ ), -+ long_description=long_description, -+ long_description_content_type='text/markdown', -+ url='https://github.com/google/textfsm', -+ license='Apache License, Version 2.0', -+ classifiers=[ -+ 'Development Status :: 5 - Production/Stable', -+ 'Intended Audience :: Developers', -+ 'License :: OSI Approved :: Apache Software License', -+ 'Operating System :: OS Independent', -+ 'Programming Language :: Python :: 3', -+ 'Topic :: Software Development :: Libraries', -+ ], -+ packages=['textfsm'], -+ entry_points={'console_scripts': ['textfsm=textfsm.parser:main']}, -+ include_package_data=True, -+ package_data={'textfsm': ['../testdata/*']}, -+) -Index: textfsm-1.1.3/tests/clitable_test.py -=================================================================== ---- textfsm-1.1.3.orig/tests/clitable_test.py -+++ textfsm-1.1.3/tests/clitable_test.py -@@ -16,19 +16,12 @@ - - """Unittest for clitable script.""" - --from __future__ import absolute_import --from __future__ import division --from __future__ import print_function --from __future__ import unicode_literals -- - import copy -+import io - import os - import re - import unittest -- --from io import StringIO - from textfsm import clitable --from textfsm import copyable_regex_object - - - class UnitTestIndexTable(unittest.TestCase): -@@ -47,8 +40,7 @@ class UnitTestIndexTable(unittest.TestCa - - self.assertEqual(indx.compiled.size, 3) - for col in ('Command', 'Vendor', 'Template', 'Hostname'): -- self.assertTrue(isinstance(indx.compiled[1][col], -- copyable_regex_object.CopyableRegexObject)) -+ self.assertIsInstance(indx.compiled[1][col], re.Pattern) - - self.assertTrue(indx.compiled[1]['Hostname'].match('random string')) - -@@ -66,8 +58,7 @@ class UnitTestIndexTable(unittest.TestCa - indx = clitable.IndexTable(_PreParse, _PreCompile, file_path) - self.assertEqual(indx.index[2]['Template'], 'CLITABLE_TEMPLATEC') - self.assertEqual(indx.index[1]['Command'], 'sh[[ow]] ve[[rsion]]') -- self.assertTrue(isinstance(indx.compiled[1]['Hostname'], -- copyable_regex_object.CopyableRegexObject)) -+ self.assertIsInstance(indx.compiled[1]['Hostname'], re.Pattern) - self.assertFalse(indx.compiled[1]['Command']) - - def testGetRowMatch(self): -@@ -101,7 +92,7 @@ class UnitTestCliTable(unittest.TestCase - 'Start\n' - ' ^${Col1} ${Col2} ${Col3} -> Record\n' - '\n') -- self.template_file = StringIO(self.template) -+ self.template_file = io.StringIO(self.template) - - def testCompletion(self): - """Tests '[[]]' syntax replacement.""" -@@ -123,7 +114,7 @@ class UnitTestCliTable(unittest.TestCase - - self.assertEqual('sh(o(w)?)? ve(r(s(i(o(n)?)?)?)?)?', - self.clitable.index.index[1]['Command']) -- self.assertEqual(None, self.clitable.index.compiled[1]['Template']) -+ self.assertIsNone(self.clitable.index.compiled[1]['Template']) - self.assertTrue( - self.clitable.index.compiled[1]['Command'].match('sho vers')) - -@@ -267,7 +258,7 @@ class UnitTestCliTable(unittest.TestCase - 'Start\n' - ' ^${Col1} ${Col2} ${Col3} -> Record\n' - '\n') -- self.template_file = StringIO(self.template) -+ self.template_file = io.StringIO(self.template) - self.clitable._TemplateNamesToFiles = lambda t: [self.template_file] - self.clitable.ParseCmd(self.input_data + input_data2, - attributes={'Command': 'sh ver'}) -Index: textfsm-1.1.3/tests/textfsm_test.py -=================================================================== ---- textfsm-1.1.3.orig/tests/textfsm_test.py -+++ textfsm-1.1.3/tests/textfsm_test.py -@@ -16,16 +16,9 @@ - # permissions and limitations under the License. - - """Unittest for textfsm module.""" --from __future__ import absolute_import --from __future__ import division --from __future__ import print_function --from __future__ import unicode_literals - --from builtins import str -+import io - import unittest --from io import StringIO -- -- - - import textfsm - -@@ -60,27 +53,27 @@ class UnitTestFSM(unittest.TestCase): - self.assertEqual(v.OptionNames(), ['Required']) - - # regex must be bounded by parenthesis. -- self.assertRaises(textfsm.TextFSMTemplateError, -- v.Parse, -- 'Value beer (boo(hoo)))boo') -- self.assertRaises(textfsm.TextFSMTemplateError, -- v.Parse, -- 'Value beer boo(boo(hoo)))') -- self.assertRaises(textfsm.TextFSMTemplateError, -- v.Parse, -- 'Value beer (boo)hoo)') -+ self.assertRaises( -+ textfsm.TextFSMTemplateError, v.Parse, 'Value beer (boo(hoo)))boo' -+ ) -+ self.assertRaises( -+ textfsm.TextFSMTemplateError, v.Parse, 'Value beer boo(boo(hoo)))' -+ ) -+ self.assertRaises( -+ textfsm.TextFSMTemplateError, v.Parse, 'Value beer (boo)hoo)' -+ ) - - # Escaped parentheses don't count. - v = textfsm.TextFSMValue(options_class=textfsm.TextFSMOptions) - v.Parse(r'Value beer (boo\)hoo)') - self.assertEqual(v.name, 'beer') - self.assertEqual(v.regex, r'(boo\)hoo)') -- self.assertRaises(textfsm.TextFSMTemplateError, -- v.Parse, -- r'Value beer (boohoo\)') -- self.assertRaises(textfsm.TextFSMTemplateError, -- v.Parse, -- r'Value beer (boo)hoo\)') -+ self.assertRaises( -+ textfsm.TextFSMTemplateError, v.Parse, r'Value beer (boohoo\)' -+ ) -+ self.assertRaises( -+ textfsm.TextFSMTemplateError, v.Parse, r'Value beer (boo)hoo\)' -+ ) - - # Unbalanced parenthesis can exist if within square "[]" braces. - v = textfsm.TextFSMValue(options_class=textfsm.TextFSMOptions) -@@ -89,17 +82,16 @@ class UnitTestFSM(unittest.TestCase): - self.assertEqual(v.regex, '(boo[(]hoo)') - - # Escaped braces don't count. -- self.assertRaises(textfsm.TextFSMTemplateError, -- v.Parse, -- r'Value beer (boo\[)\]hoo)') -+ self.assertRaises( -+ textfsm.TextFSMTemplateError, v.Parse, r'Value beer (boo\[)\]hoo)' -+ ) - - # String function. - v = textfsm.TextFSMValue(options_class=textfsm.TextFSMOptions) - v.Parse('Value Required beer (boo(hoo))') - self.assertEqual(str(v), 'Value Required beer (boo(hoo))') - v = textfsm.TextFSMValue(options_class=textfsm.TextFSMOptions) -- v.Parse( -- r'Value Required,Filldown beer (bo\S+(hoo))') -+ v.Parse(r'Value Required,Filldown beer (bo\S+(hoo))') - self.assertEqual(str(v), r'Value Required,Filldown beer (bo\S+(hoo))') - - def testFSMRule(self): -@@ -144,144 +136,174 @@ class UnitTestFSM(unittest.TestCase): - self.assertEqual(r.record_op, 'NoRecord') - - # Bad syntax tests. -- self.assertRaises(textfsm.TextFSMTemplateError, textfsm.TextFSMRule, -- ' ^A beer called ${beer} -> Next Next Next') -- self.assertRaises(textfsm.TextFSMTemplateError, textfsm.TextFSMRule, -- ' ^A beer called ${beer} -> Boo.hoo') -- self.assertRaises(textfsm.TextFSMTemplateError, textfsm.TextFSMRule, -- ' ^A beer called ${beer} -> Continue.Record $Hi') -+ self.assertRaises( -+ textfsm.TextFSMTemplateError, -+ textfsm.TextFSMRule, -+ ' ^A beer called ${beer} -> Next Next Next', -+ ) -+ self.assertRaises( -+ textfsm.TextFSMTemplateError, -+ textfsm.TextFSMRule, -+ ' ^A beer called ${beer} -> Boo.hoo', -+ ) -+ self.assertRaises( -+ textfsm.TextFSMTemplateError, -+ textfsm.TextFSMRule, -+ ' ^A beer called ${beer} -> Continue.Record $Hi', -+ ) - - def testRulePrefixes(self): - """Test valid and invalid rule prefixes.""" - - # Bad syntax tests. - for prefix in (' ', '.^', ' \t', ''): -- f = StringIO('Value unused (.)\n\nStart\n' + prefix + 'A simple string.') -+ f = io.StringIO( -+ 'Value unused (.)\n\nStart\n' + prefix + 'A simple string.' -+ ) - self.assertRaises(textfsm.TextFSMTemplateError, textfsm.TextFSM, f) - - # Good syntax tests. - for prefix in (' ^', ' ^', '\t^'): -- f = StringIO('Value unused (.)\n\nStart\n' + prefix + 'A simple string.') -+ f = io.StringIO( -+ 'Value unused (.)\n\nStart\n' + prefix + 'A simple string.' -+ ) - self.assertIsNotNone(textfsm.TextFSM(f)) - - def testImplicitDefaultRules(self): - -- for line in (' ^A beer called ${beer} -> Record End', -- ' ^A beer called ${beer} -> End', -- ' ^A beer called ${beer} -> Next.NoRecord End', -- ' ^A beer called ${beer} -> Clear End', -- ' ^A beer called ${beer} -> Error "Hello World"'): -+ for line in ( -+ ' ^A beer called ${beer} -> Record End', -+ ' ^A beer called ${beer} -> End', -+ ' ^A beer called ${beer} -> Next.NoRecord End', -+ ' ^A beer called ${beer} -> Clear End', -+ ' ^A beer called ${beer} -> Error "Hello World"', -+ ): - r = textfsm.TextFSMRule(line) - self.assertEqual(str(r), line) - -- for line in (' ^A beer called ${beer} -> Next "Hello World"', -- ' ^A beer called ${beer} -> Record.Next', -- ' ^A beer called ${beer} -> Continue End', -- ' ^A beer called ${beer} -> Beer End'): -- self.assertRaises(textfsm.TextFSMTemplateError, -- textfsm.TextFSMRule, line) -+ for line in ( -+ ' ^A beer called ${beer} -> Next "Hello World"', -+ ' ^A beer called ${beer} -> Record.Next', -+ ' ^A beer called ${beer} -> Continue End', -+ ' ^A beer called ${beer} -> Beer End', -+ ): -+ self.assertRaises(textfsm.TextFSMTemplateError, textfsm.TextFSMRule, line) - - def testSpacesAroundAction(self): -- for line in (' ^Hello World -> Boo', -- ' ^Hello World -> Boo', -- ' ^Hello World -> Boo'): -- self.assertEqual( -- str(textfsm.TextFSMRule(line)), ' ^Hello World -> Boo') -+ for line in ( -+ ' ^Hello World -> Boo', -+ ' ^Hello World -> Boo', -+ ' ^Hello World -> Boo', -+ ): -+ self.assertEqual(str(textfsm.TextFSMRule(line)), ' ^Hello World -> Boo') - - # A '->' without a leading space is considered part of the matching line. -- self.assertEqual(' A simple line-> Boo -> Next', -- str(textfsm.TextFSMRule(' A simple line-> Boo -> Next'))) -+ self.assertEqual( -+ ' A simple line-> Boo -> Next', -+ str(textfsm.TextFSMRule(' A simple line-> Boo -> Next')), -+ ) - - def testParseFSMVariables(self): - # Trivial template to initiate object. -- f = StringIO('Value unused (.)\n\nStart\n') -+ f = io.StringIO('Value unused (.)\n\nStart\n') - t = textfsm.TextFSM(f) - - # Trivial entry - buf = 'Value Filldown Beer (beer)\n\n' -- f = StringIO(buf) -+ f = io.StringIO(buf) - t._ParseFSMVariables(f) - - # Single variable with commented header. - buf = '# Headline\nValue Filldown Beer (beer)\n\n' -- f = StringIO(buf) -+ f = io.StringIO(buf) - t._ParseFSMVariables(f) - self.assertEqual(str(t._GetValue('Beer')), 'Value Filldown Beer (beer)') - - # Multiple variables. -- buf = ('# Headline\n' -- 'Value Filldown Beer (beer)\n' -- 'Value Required Spirits (whiskey)\n' -- 'Value Filldown Wine (claret)\n' -- '\n') -+ buf = ( -+ '# Headline\n' -+ 'Value Filldown Beer (beer)\n' -+ 'Value Required Spirits (whiskey)\n' -+ 'Value Filldown Wine (claret)\n' -+ '\n' -+ ) - t._line_num = 0 -- f = StringIO(buf) -+ f = io.StringIO(buf) - t._ParseFSMVariables(f) - self.assertEqual(str(t._GetValue('Beer')), 'Value Filldown Beer (beer)') - self.assertEqual( -- str(t._GetValue('Spirits')), 'Value Required Spirits (whiskey)') -+ str(t._GetValue('Spirits')), 'Value Required Spirits (whiskey)' -+ ) - self.assertEqual(str(t._GetValue('Wine')), 'Value Filldown Wine (claret)') - - # Multiple variables. -- buf = ('# Headline\n' -- 'Value Filldown Beer (beer)\n' -- ' # A comment\n' -- 'Value Spirits ()\n' -- 'Value Filldown,Required Wine ((c|C)laret)\n' -- '\n') -+ buf = ( -+ '# Headline\n' -+ 'Value Filldown Beer (beer)\n' -+ ' # A comment\n' -+ 'Value Spirits ()\n' -+ 'Value Filldown,Required Wine ((c|C)laret)\n' -+ '\n' -+ ) - -- f = StringIO(buf) -+ f = io.StringIO(buf) - t._ParseFSMVariables(f) - self.assertEqual(str(t._GetValue('Beer')), 'Value Filldown Beer (beer)') -+ self.assertEqual(str(t._GetValue('Spirits')), 'Value Spirits ()') - self.assertEqual( -- str(t._GetValue('Spirits')), 'Value Spirits ()') -- self.assertEqual(str(t._GetValue('Wine')), -- 'Value Filldown,Required Wine ((c|C)laret)') -+ str(t._GetValue('Wine')), 'Value Filldown,Required Wine ((c|C)laret)' -+ ) - - # Malformed variables. - buf = 'Value Beer (beer) beer' -- f = StringIO(buf) -+ f = io.StringIO(buf) - self.assertRaises(textfsm.TextFSMTemplateError, t._ParseFSMVariables, f) - - buf = 'Value Filldown, Required Spirits ()' -- f = StringIO(buf) -+ f = io.StringIO(buf) - self.assertRaises(textfsm.TextFSMTemplateError, t._ParseFSMVariables, f) - buf = 'Value filldown,Required Wine ((c|C)laret)' -- f = StringIO(buf) -+ f = io.StringIO(buf) - self.assertRaises(textfsm.TextFSMTemplateError, t._ParseFSMVariables, f) - - # Values that look bad but are OK. -- buf = ('# Headline\n' -- 'Value Filldown Beer (bee(r), (and) (M)ead$)\n' -- '# A comment\n' -- 'Value Spirits,and,some ()\n' -- 'Value Filldown,Required Wine ((c|C)laret)\n' -- '\n') -- f = StringIO(buf) -+ buf = ( -+ '# Headline\n' -+ 'Value Filldown Beer (bee(r), (and) (M)ead$)\n' -+ '# A comment\n' -+ 'Value Spirits,and,some ()\n' -+ 'Value Filldown,Required Wine ((c|C)laret)\n' -+ '\n' -+ ) -+ f = io.StringIO(buf) - t._ParseFSMVariables(f) -- self.assertEqual(str(t._GetValue('Beer')), -- 'Value Filldown Beer (bee(r), (and) (M)ead$)') - self.assertEqual( -- str(t._GetValue('Spirits,and,some')), 'Value Spirits,and,some ()') -- self.assertEqual(str(t._GetValue('Wine')), -- 'Value Filldown,Required Wine ((c|C)laret)') -+ str(t._GetValue('Beer')), 'Value Filldown Beer (bee(r), (and) (M)ead$)' -+ ) -+ self.assertEqual( -+ str(t._GetValue('Spirits,and,some')), 'Value Spirits,and,some ()' -+ ) -+ self.assertEqual( -+ str(t._GetValue('Wine')), 'Value Filldown,Required Wine ((c|C)laret)' -+ ) - - # Variable name too long. -- buf = ('Value Filldown ' -- 'nametoolong_nametoolong_nametoolo_nametoolong_nametoolong ' -- '(beer)\n\n') -- f = StringIO(buf) -- self.assertRaises(textfsm.TextFSMTemplateError, -- t._ParseFSMVariables, f) -+ buf = ( -+ 'Value Filldown ' -+ 'nametoolong_nametoolong_nametoolo_nametoolong_nametoolong ' -+ '(beer)\n\n' -+ ) -+ f = io.StringIO(buf) -+ self.assertRaises(textfsm.TextFSMTemplateError, t._ParseFSMVariables, f) - - def testParseFSMState(self): - -- f = StringIO('Value Beer (.)\nValue Wine (\\w)\n\nStart\n') -+ f = io.StringIO('Value Beer (.)\nValue Wine (\\w)\n\nStart\n') - t = textfsm.TextFSM(f) - - # Fails as we already have 'Start' state. - buf = 'Start\n ^.\n' -- f = StringIO(buf) -+ f = io.StringIO(buf) - self.assertRaises(textfsm.TextFSMTemplateError, t._ParseFSMState, f) - - # Remove start so we can test new Start state. -@@ -289,7 +311,7 @@ class UnitTestFSM(unittest.TestCase): - - # Single state. - buf = '# Headline\nStart\n ^.\n\n' -- f = StringIO(buf) -+ f = io.StringIO(buf) - t._ParseFSMState(f) - self.assertEqual(str(t.states['Start'][0]), ' ^.') - try: -@@ -299,7 +321,7 @@ class UnitTestFSM(unittest.TestCase): - - # Multiple states. - buf = '# Headline\nStart\n ^.\n ^Hello World\n ^Last-[Cc]ha$$nge\n' -- f = StringIO(buf) -+ f = io.StringIO(buf) - t._line_num = 0 - t.states = {} - t._ParseFSMState(f) -@@ -315,21 +337,23 @@ class UnitTestFSM(unittest.TestCase): - t.states = {} - # Malformed states. - buf = 'St%art\n ^.\n ^Hello World\n' -- f = StringIO(buf) -+ f = io.StringIO(buf) - self.assertRaises(textfsm.TextFSMTemplateError, t._ParseFSMState, f) - - buf = 'Start\n^.\n ^Hello World\n' -- f = StringIO(buf) -+ f = io.StringIO(buf) - self.assertRaises(textfsm.TextFSMTemplateError, t._ParseFSMState, f) - - buf = ' Start\n ^.\n ^Hello World\n' -- f = StringIO(buf) -+ f = io.StringIO(buf) - self.assertRaises(textfsm.TextFSMTemplateError, t._ParseFSMState, f) - - # Multiple variables and substitution (depends on _ParseFSMVariables). -- buf = ('# Headline\nStart\n ^.${Beer}${Wine}.\n' -- ' ^Hello $Beer\n ^Last-[Cc]ha$$nge\n') -- f = StringIO(buf) -+ buf = ( -+ '# Headline\nStart\n ^.${Beer}${Wine}.\n' -+ ' ^Hello $Beer\n ^Last-[Cc]ha$$nge\n' -+ ) -+ f = io.StringIO(buf) - t.states = {} - t._ParseFSMState(f) - self.assertEqual(str(t.states['Start'][0]), ' ^.${Beer}${Wine}.') -@@ -344,43 +368,52 @@ class UnitTestFSM(unittest.TestCase): - - # State name too long (>32 char). - buf = 'rnametoolong_nametoolong_nametoolong_nametoolong_nametoolo\n ^.\n\n' -- f = StringIO(buf) -+ f = io.StringIO(buf) - self.assertRaises(textfsm.TextFSMTemplateError, t._ParseFSMState, f) - - def testInvalidStates(self): - - # 'Continue' should not accept a destination. -- self.assertRaises(textfsm.TextFSMTemplateError, textfsm.TextFSMRule, -- '^.* -> Continue Start') -+ self.assertRaises( -+ textfsm.TextFSMTemplateError, -+ textfsm.TextFSMRule, -+ '^.* -> Continue Start', -+ ) - - # 'Error' accepts a text string but "next' state does not. -- self.assertEqual(str(textfsm.TextFSMRule(' ^ -> Error "hi there"')), -- ' ^ -> Error "hi there"') -- self.assertRaises(textfsm.TextFSMTemplateError, textfsm.TextFSMRule, -- '^.* -> Next "Hello World"') -+ self.assertEqual( -+ str(textfsm.TextFSMRule(' ^ -> Error "hi there"')), -+ ' ^ -> Error "hi there"', -+ ) -+ self.assertRaises( -+ textfsm.TextFSMTemplateError, -+ textfsm.TextFSMRule, -+ '^.* -> Next "Hello World"', -+ ) - - def testRuleStartsWithCarrot(self): - -- f = StringIO( -- 'Value Beer (.)\nValue Wine (\\w)\n\nStart\n A Simple line') -+ f = io.StringIO( -+ 'Value Beer (.)\nValue Wine (\\w)\n\nStart\n A Simple line' -+ ) - self.assertRaises(textfsm.TextFSMTemplateError, textfsm.TextFSM, f) - - def testValidateFSM(self): - - # No Values. -- f = StringIO('\nNotStart\n') -+ f = io.StringIO('\nNotStart\n') - self.assertRaises(textfsm.TextFSMTemplateError, textfsm.TextFSM, f) - - # No states. -- f = StringIO('Value unused (.)\n\n') -+ f = io.StringIO('Value unused (.)\n\n') - self.assertRaises(textfsm.TextFSMTemplateError, textfsm.TextFSM, f) - - # No 'Start' state. -- f = StringIO('Value unused (.)\n\nNotStart\n') -+ f = io.StringIO('Value unused (.)\n\nNotStart\n') - self.assertRaises(textfsm.TextFSMTemplateError, textfsm.TextFSM, f) - - # Has 'Start' state with valid destination -- f = StringIO('Value unused (.)\n\nStart\n') -+ f = io.StringIO('Value unused (.)\n\nStart\n') - t = textfsm.TextFSM(f) - t.states['Start'] = [] - t.states['Start'].append(textfsm.TextFSMRule('^.* -> Start')) -@@ -412,14 +445,14 @@ class UnitTestFSM(unittest.TestCase): - # Trivial template - buf = 'Value Beer (.*)\n\nStart\n ^\\w\n' - buf_result = buf -- f = StringIO(buf) -+ f = io.StringIO(buf) - t = textfsm.TextFSM(f) - self.assertEqual(str(t), buf_result) - - # Slightly more complex, multple vars. - buf = 'Value A (.*)\nValue B (.*)\n\nStart\n ^\\w\n\nState1\n ^.\n' - buf_result = buf -- f = StringIO(buf) -+ f = io.StringIO(buf) - t = textfsm.TextFSM(f) - self.assertEqual(str(t), buf_result) - -@@ -427,7 +460,7 @@ class UnitTestFSM(unittest.TestCase): - - # Trivial FSM, no records produced. - tplt = 'Value unused (.)\n\nStart\n ^Trivial SFM\n' -- t = textfsm.TextFSM(StringIO(tplt)) -+ t = textfsm.TextFSM(io.StringIO(tplt)) - - data = 'Non-matching text\nline1\nline 2\n' - self.assertFalse(t.ParseText(data)) -@@ -437,7 +470,7 @@ class UnitTestFSM(unittest.TestCase): - - # Simple FSM, One Variable no options. - tplt = 'Value boo (.*)\n\nStart\n ^$boo -> Next.Record\n\nEOF\n' -- t = textfsm.TextFSM(StringIO(tplt)) -+ t = textfsm.TextFSM(io.StringIO(tplt)) - - # Matching one line. - # Tests 'Next' & 'Record' actions. -@@ -452,10 +485,12 @@ class UnitTestFSM(unittest.TestCase): - self.assertListEqual(result, [['Matching text'], ['And again']]) - - # Two Variables and singular options. -- tplt = ('Value Required boo (one)\nValue Filldown hoo (two)\n\n' -- 'Start\n ^$boo -> Next.Record\n ^$hoo -> Next.Record\n\n' -- 'EOF\n') -- t = textfsm.TextFSM(StringIO(tplt)) -+ tplt = ( -+ 'Value Required boo (one)\nValue Filldown hoo (two)\n\n' -+ 'Start\n ^$boo -> Next.Record\n ^$hoo -> Next.Record\n\n' -+ 'EOF\n' -+ ) -+ t = textfsm.TextFSM(io.StringIO(tplt)) - - # Matching two lines. Only one records returned due to 'Required' flag. - # Tests 'Filldown' and 'Required' options. -@@ -463,7 +498,7 @@ class UnitTestFSM(unittest.TestCase): - result = t.ParseText(data) - self.assertListEqual(result, [['one', 'two']]) - -- t = textfsm.TextFSM(StringIO(tplt)) -+ t = textfsm.TextFSM(io.StringIO(tplt)) - # Matching two lines. Two records returned due to 'Filldown' flag. - data = 'two\none\none' - t.Reset() -@@ -471,11 +506,13 @@ class UnitTestFSM(unittest.TestCase): - self.assertListEqual(result, [['one', 'two'], ['one', 'two']]) - - # Multiple Variables and options. -- tplt = ('Value Required,Filldown boo (one)\n' -- 'Value Filldown,Required hoo (two)\n\n' -- 'Start\n ^$boo -> Next.Record\n ^$hoo -> Next.Record\n\n' -- 'EOF\n') -- t = textfsm.TextFSM(StringIO(tplt)) -+ tplt = ( -+ 'Value Required,Filldown boo (one)\n' -+ 'Value Filldown,Required hoo (two)\n\n' -+ 'Start\n ^$boo -> Next.Record\n ^$hoo -> Next.Record\n\n' -+ 'EOF\n' -+ ) -+ t = textfsm.TextFSM(io.StringIO(tplt)) - data = 'two\none\none' - result = t.ParseText(data) - self.assertListEqual(result, [['one', 'two'], ['one', 'two']]) -@@ -484,7 +521,7 @@ class UnitTestFSM(unittest.TestCase): - - # Trivial FSM, no records produced. - tplt = 'Value unused (.)\n\nStart\n ^Trivial SFM\n' -- t = textfsm.TextFSM(StringIO(tplt)) -+ t = textfsm.TextFSM(io.StringIO(tplt)) - - data = 'Non-matching text\nline1\nline 2\n' - self.assertFalse(t.ParseText(data)) -@@ -494,7 +531,7 @@ class UnitTestFSM(unittest.TestCase): - - # Simple FSM, One Variable no options. - tplt = 'Value boo (.*)\n\nStart\n ^$boo -> Next.Record\n\nEOF\n' -- t = textfsm.TextFSM(StringIO(tplt)) -+ t = textfsm.TextFSM(io.StringIO(tplt)) - - # Matching one line. - # Tests 'Next' & 'Record' actions. -@@ -506,14 +543,17 @@ class UnitTestFSM(unittest.TestCase): - t.Reset() - data = 'Matching text\nAnd again' - result = t.ParseTextToDicts(data) -- self.assertListEqual(result, -- [{'boo': 'Matching text'}, {'boo': 'And again'}]) -+ self.assertListEqual( -+ result, [{'boo': 'Matching text'}, {'boo': 'And again'}] -+ ) - - # Two Variables and singular options. -- tplt = ('Value Required boo (one)\nValue Filldown hoo (two)\n\n' -- 'Start\n ^$boo -> Next.Record\n ^$hoo -> Next.Record\n\n' -- 'EOF\n') -- t = textfsm.TextFSM(StringIO(tplt)) -+ tplt = ( -+ 'Value Required boo (one)\nValue Filldown hoo (two)\n\n' -+ 'Start\n ^$boo -> Next.Record\n ^$hoo -> Next.Record\n\n' -+ 'EOF\n' -+ ) -+ t = textfsm.TextFSM(io.StringIO(tplt)) - - # Matching two lines. Only one records returned due to 'Required' flag. - # Tests 'Filldown' and 'Required' options. -@@ -521,30 +561,34 @@ class UnitTestFSM(unittest.TestCase): - result = t.ParseTextToDicts(data) - self.assertListEqual(result, [{'hoo': 'two', 'boo': 'one'}]) - -- t = textfsm.TextFSM(StringIO(tplt)) -+ t = textfsm.TextFSM(io.StringIO(tplt)) - # Matching two lines. Two records returned due to 'Filldown' flag. - data = 'two\none\none' - t.Reset() - result = t.ParseTextToDicts(data) - self.assertListEqual( -- result, [{'hoo': 'two', 'boo': 'one'}, {'hoo': 'two', 'boo': 'one'}]) -+ result, [{'hoo': 'two', 'boo': 'one'}, {'hoo': 'two', 'boo': 'one'}] -+ ) - - # Multiple Variables and options. -- tplt = ('Value Required,Filldown boo (one)\n' -- 'Value Filldown,Required hoo (two)\n\n' -- 'Start\n ^$boo -> Next.Record\n ^$hoo -> Next.Record\n\n' -- 'EOF\n') -- t = textfsm.TextFSM(StringIO(tplt)) -+ tplt = ( -+ 'Value Required,Filldown boo (one)\n' -+ 'Value Filldown,Required hoo (two)\n\n' -+ 'Start\n ^$boo -> Next.Record\n ^$hoo -> Next.Record\n\n' -+ 'EOF\n' -+ ) -+ t = textfsm.TextFSM(io.StringIO(tplt)) - data = 'two\none\none' - result = t.ParseTextToDicts(data) - self.assertListEqual( -- result, [{'hoo': 'two', 'boo': 'one'}, {'hoo': 'two', 'boo': 'one'}]) -+ result, [{'hoo': 'two', 'boo': 'one'}, {'hoo': 'two', 'boo': 'one'}] -+ ) - - def testParseNullText(self): - - # Simple FSM, One Variable no options. - tplt = 'Value boo (.*)\n\nStart\n ^$boo -> Next.Record\n\n' -- t = textfsm.TextFSM(StringIO(tplt)) -+ t = textfsm.TextFSM(io.StringIO(tplt)) - - # Null string - data = '' -@@ -554,181 +598,210 @@ class UnitTestFSM(unittest.TestCase): - def testReset(self): - - tplt = 'Value boo (.*)\n\nStart\n ^$boo -> Next.Record\n\nEOF\n' -- t = textfsm.TextFSM(StringIO(tplt)) -+ t = textfsm.TextFSM(io.StringIO(tplt)) - data = 'Matching text' - result1 = t.ParseText(data) - t.Reset() - result2 = t.ParseText(data) - self.assertListEqual(result1, result2) - -- tplt = ('Value boo (one)\nValue hoo (two)\n\n' -- 'Start\n ^$boo -> State1\n\n' -- 'State1\n ^$hoo -> Start\n\n' -- 'EOF') -- t = textfsm.TextFSM(StringIO(tplt)) -+ tplt = ( -+ 'Value boo (one)\nValue hoo (two)\n\n' -+ 'Start\n ^$boo -> State1\n\n' -+ 'State1\n ^$hoo -> Start\n\n' -+ 'EOF' -+ ) -+ t = textfsm.TextFSM(io.StringIO(tplt)) - - data = 'one' - t.ParseText(data) - t.Reset() - self.assertEqual(t._cur_state[0].match, '^$boo') -- self.assertEqual(t._GetValue('boo').value, None) -- self.assertEqual(t._GetValue('hoo').value, None) -+ self.assertIsNone(None, t._GetValue('boo').value) -+ self.assertIsNone(t._GetValue('hoo').value) - self.assertEqual(t._result, []) - - def testClear(self): - - # Clear Filldown variable. - # Tests 'Clear'. -- tplt = ('Value Required boo (on.)\n' -- 'Value Filldown,Required hoo (tw.)\n\n' -- 'Start\n ^$boo -> Next.Record\n ^$hoo -> Next.Clear') -+ tplt = ( -+ 'Value Required boo (on.)\n' -+ 'Value Filldown,Required hoo (tw.)\n\n' -+ 'Start\n ^$boo -> Next.Record\n ^$hoo -> Next.Clear' -+ ) - -- t = textfsm.TextFSM(StringIO(tplt)) -+ t = textfsm.TextFSM(io.StringIO(tplt)) - data = 'one\ntwo\nonE\ntwO' - result = t.ParseText(data) - self.assertListEqual(result, [['onE', 'two']]) - - # Clearall, with Filldown variable. - # Tests 'Clearall'. -- tplt = ('Value Filldown boo (on.)\n' -- 'Value Filldown hoo (tw.)\n\n' -- 'Start\n ^$boo -> Next.Clearall\n' -- ' ^$hoo') -+ tplt = ( -+ 'Value Filldown boo (on.)\n' -+ 'Value Filldown hoo (tw.)\n\n' -+ 'Start\n ^$boo -> Next.Clearall\n' -+ ' ^$hoo' -+ ) - -- t = textfsm.TextFSM(StringIO(tplt)) -+ t = textfsm.TextFSM(io.StringIO(tplt)) - data = 'one\ntwo' - result = t.ParseText(data) - self.assertListEqual(result, [['', 'two']]) - - def testContinue(self): - -- tplt = ('Value Required boo (on.)\n' -- 'Value Filldown,Required hoo (on.)\n\n' -- 'Start\n ^$boo -> Continue\n ^$hoo -> Continue.Record') -+ tplt = ( -+ 'Value Required boo (on.)\n' -+ 'Value Filldown,Required hoo (on.)\n\n' -+ 'Start\n ^$boo -> Continue\n ^$hoo -> Continue.Record' -+ ) - -- t = textfsm.TextFSM(StringIO(tplt)) -+ t = textfsm.TextFSM(io.StringIO(tplt)) - data = 'one\non0' - result = t.ParseText(data) - self.assertListEqual(result, [['one', 'one'], ['on0', 'on0']]) - - def testError(self): - -- tplt = ('Value Required boo (on.)\n' -- 'Value Filldown,Required hoo (on.)\n\n' -- 'Start\n ^$boo -> Continue\n ^$hoo -> Error') -+ tplt = ( -+ 'Value Required boo (on.)\n' -+ 'Value Filldown,Required hoo (on.)\n\n' -+ 'Start\n ^$boo -> Continue\n ^$hoo -> Error' -+ ) - -- t = textfsm.TextFSM(StringIO(tplt)) -+ t = textfsm.TextFSM(io.StringIO(tplt)) - data = 'one' - self.assertRaises(textfsm.TextFSMError, t.ParseText, data) - -- tplt = ('Value Required boo (on.)\n' -- 'Value Filldown,Required hoo (on.)\n\n' -- 'Start\n ^$boo -> Continue\n ^$hoo -> Error "Hello World"') -+ tplt = ( -+ 'Value Required boo (on.)\n' -+ 'Value Filldown,Required hoo (on.)\n\n' -+ 'Start\n ^$boo -> Continue\n ^$hoo -> Error "Hello World"' -+ ) - -- t = textfsm.TextFSM(StringIO(tplt)) -+ t = textfsm.TextFSM(io.StringIO(tplt)) - self.assertRaises(textfsm.TextFSMError, t.ParseText, data) - - def testKey(self): -- tplt = ('Value Required boo (on.)\n' -- 'Value Required,Key hoo (on.)\n\n' -- 'Start\n ^$boo -> Continue\n ^$hoo -> Record') -- -- t = textfsm.TextFSM(StringIO(tplt)) -- self.assertTrue('Key' in t._GetValue('hoo').OptionNames()) -- self.assertTrue('Key' not in t._GetValue('boo').OptionNames()) -+ tplt = ( -+ 'Value Required boo (on.)\n' -+ 'Value Required,Key hoo (on.)\n\n' -+ 'Start\n ^$boo -> Continue\n ^$hoo -> Record' -+ ) -+ -+ t = textfsm.TextFSM(io.StringIO(tplt)) -+ self.assertIn('Key', t._GetValue('hoo').OptionNames()) -+ self.assertNotIn('Key', t._GetValue('boo').OptionNames()) - - def testList(self): - -- tplt = ('Value List boo (on.)\n' -- 'Value hoo (tw.)\n\n' -- 'Start\n ^$boo\n ^$hoo -> Next.Record\n\n' -- 'EOF') -+ tplt = ( -+ 'Value List boo (on.)\n' -+ 'Value hoo (tw.)\n\n' -+ 'Start\n ^$boo\n ^$hoo -> Next.Record\n\n' -+ 'EOF' -+ ) - -- t = textfsm.TextFSM(StringIO(tplt)) -+ t = textfsm.TextFSM(io.StringIO(tplt)) - data = 'one\ntwo\non0\ntw0' - result = t.ParseText(data) - self.assertListEqual(result, [[['one'], 'two'], [['on0'], 'tw0']]) - -- tplt = ('Value List,Filldown boo (on.)\n' -- 'Value hoo (on.)\n\n' -- 'Start\n ^$boo -> Continue\n ^$hoo -> Next.Record\n\n' -- 'EOF') -+ tplt = ( -+ 'Value List,Filldown boo (on.)\n' -+ 'Value hoo (on.)\n\n' -+ 'Start\n ^$boo -> Continue\n ^$hoo -> Next.Record\n\n' -+ 'EOF' -+ ) - -- t = textfsm.TextFSM(StringIO(tplt)) -+ t = textfsm.TextFSM(io.StringIO(tplt)) - data = 'one\non0\non1' - result = t.ParseText(data) -- self.assertEqual(result, ([[['one'], 'one'], -- [['one', 'on0'], 'on0'], -- [['one', 'on0', 'on1'], 'on1']])) -- -- tplt = ('Value List,Required boo (on.)\n' -- 'Value hoo (tw.)\n\n' -- 'Start\n ^$boo -> Continue\n ^$hoo -> Next.Record\n\n' -- 'EOF') -+ self.assertEqual( -+ result, -+ ([ -+ [['one'], 'one'], -+ [['one', 'on0'], 'on0'], -+ [['one', 'on0', 'on1'], 'on1'], -+ ]), -+ ) -+ -+ tplt = ( -+ 'Value List,Required boo (on.)\n' -+ 'Value hoo (tw.)\n\n' -+ 'Start\n ^$boo -> Continue\n ^$hoo -> Next.Record\n\n' -+ 'EOF' -+ ) - -- t = textfsm.TextFSM(StringIO(tplt)) -+ t = textfsm.TextFSM(io.StringIO(tplt)) - data = 'one\ntwo\ntw2' - result = t.ParseText(data) - self.assertListEqual(result, [[['one'], 'two']]) - -- - def testNestedMatching(self): -- """ -- Ensures that List-type values with nested regex capture groups are parsed -- correctly as a list of dictionaries. -- -- Additionaly, another value is used with the same group-name as one of the -- nested groups to ensure that there are no conflicts when the same name is -- used. -- """ -- tplt = ( -- # A nested group is called "name" -- r"Value List foo ((?P\w+):\s+(?P\d+)\s+(?P\w{2})\s*)" -- "\n" -- # A regular value is called "name" -- r"Value name (\w+)" -- # "${name}" here refers to the Value called "name" -- "\n\nStart\n" -- r" ^\s*${foo}" -- "\n" -- r" ^\s*${name}" -- "\n" -- r" ^\s*$$ -> Record" -- ) -- t = textfsm.TextFSM(StringIO(tplt)) -- # Julia should be parsed as "name" separately -- data = " Bob: 32 NC\n Alice: 27 NY\n Jeff: 45 CA\nJulia\n\n" -- result = t.ParseText(data) -- self.assertListEqual( -- result, ( -- [[[ -- {'name': 'Bob', 'age': '32', 'state': 'NC'}, -- {'name': 'Alice', 'age': '27', 'state': 'NY'}, -- {'name': 'Jeff', 'age': '45', 'state': 'CA'} -- ], 'Julia']] -- ) -- ) -+ """List-type values with nested regex capture groups are parsed correctly. - -- def testNestedNameConflict(self): -- tplt = ( -- # Two nested groups are called "name" -- r"Value List foo ((?P\w+)\s+(?P\w+):\s+(?P\d+)\s+(?P\w{2})\s*)" -- "\nStart\n" -- r"^\s*${foo}" -- "\n ^" -- r"\s*$$ -> Record" -- ) -- self.assertRaises(textfsm.TextFSMTemplateError, textfsm.TextFSM, StringIO(tplt)) -+ Additionaly, another value is used with the same group-name as one of the -+ nested groups to ensure that there are no conflicts when the same name is -+ used. -+ """ -+ -+ tplt = ( -+ # A nested group is called "name" -+ r'Value List foo ((?P\w+):\s+(?P\d+)\s+(?P\w{2})\s*)' -+ '\n' -+ # A regular value is called "name" -+ r'Value name (\w+)' -+ # "${name}" here refers to the Value called "name" -+ '\n\nStart\n' -+ r' ^\s*${foo}' -+ '\n' -+ r' ^\s*${name}' -+ '\n' -+ r' ^\s*$$ -> Record' -+ ) -+ t = textfsm.TextFSM(io.StringIO(tplt)) -+ # Julia should be parsed as "name" separately -+ data = ' Bob: 32 NC\n Alice: 27 NY\n Jeff: 45 CA\nJulia\n\n' -+ result = t.ParseText(data) -+ self.assertListEqual( -+ result, -+ ([[ -+ [ -+ {'name': 'Bob', 'age': '32', 'state': 'NC'}, -+ {'name': 'Alice', 'age': '27', 'state': 'NY'}, -+ {'name': 'Jeff', 'age': '45', 'state': 'CA'}, -+ ], -+ 'Julia', -+ ]]), -+ ) - -+ def testNestedNameConflict(self): -+ tplt = ( -+ # Two nested groups are called "name" -+ r'Value List foo' -+ r' ((?P\w+)\s+(?P\w+):\s+(?P\d+)\s+(?P\w{2})\s*)' -+ '\nStart\n' -+ r'^\s*${foo}' -+ '\n ^' -+ r'\s*$$ -> Record' -+ ) -+ self.assertRaises( -+ textfsm.TextFSMTemplateError, textfsm.TextFSM, io.StringIO(tplt) -+ ) - - def testGetValuesByAttrib(self): - -- tplt = ('Value Required boo (on.)\n' -- 'Value Required,List hoo (on.)\n\n' -- 'Start\n ^$boo -> Continue\n ^$hoo -> Record') -+ tplt = ( -+ 'Value Required boo (on.)\n' -+ 'Value Required,List hoo (on.)\n\n' -+ 'Start\n ^$boo -> Continue\n ^$hoo -> Record' -+ ) - - # Explicit default. -- t = textfsm.TextFSM(StringIO(tplt)) -+ t = textfsm.TextFSM(io.StringIO(tplt)) - self.assertEqual(t.GetValuesByAttrib('List'), ['hoo']) - self.assertEqual(t.GetValuesByAttrib('Filldown'), []) - result = t.GetValuesByAttrib('Required') -@@ -738,37 +811,41 @@ class UnitTestFSM(unittest.TestCase): - def testStateChange(self): - - # Sinple state change, no actions -- tplt = ('Value boo (one)\nValue hoo (two)\n\n' -- 'Start\n ^$boo -> State1\n\nState1\n ^$hoo -> Start\n\n' -- 'EOF') -- t = textfsm.TextFSM(StringIO(tplt)) -+ tplt = ( -+ 'Value boo (one)\nValue hoo (two)\n\n' -+ 'Start\n ^$boo -> State1\n\nState1\n ^$hoo -> Start\n\n' -+ 'EOF' -+ ) -+ t = textfsm.TextFSM(io.StringIO(tplt)) - - data = 'one' - t.ParseText(data) - self.assertEqual(t._cur_state[0].match, '^$hoo') - self.assertEqual('one', t._GetValue('boo').value) -- self.assertEqual(None, t._GetValue('hoo').value) -+ self.assertIsNone(t._GetValue('hoo').value) - self.assertEqual(t._result, []) - - # State change with actions. -- tplt = ('Value boo (one)\nValue hoo (two)\n\n' -- 'Start\n ^$boo -> Next.Record State1\n\n' -- 'State1\n ^$hoo -> Start\n\n' -- 'EOF') -- t = textfsm.TextFSM(StringIO(tplt)) -+ tplt = ( -+ 'Value boo (one)\nValue hoo (two)\n\n' -+ 'Start\n ^$boo -> Next.Record State1\n\n' -+ 'State1\n ^$hoo -> Start\n\n' -+ 'EOF' -+ ) -+ t = textfsm.TextFSM(io.StringIO(tplt)) - - data = 'one' - t.ParseText(data) - self.assertEqual(t._cur_state[0].match, '^$hoo') -- self.assertEqual(None, t._GetValue('boo').value) -- self.assertEqual(None, t._GetValue('hoo').value) -+ self.assertIsNone(t._GetValue('boo').value) -+ self.assertIsNone(t._GetValue('hoo').value) - self.assertEqual(t._result, [['one', '']]) - - def testEOF(self): - - # Implicit EOF. - tplt = 'Value boo (.*)\n\nStart\n ^$boo -> Next\n' -- t = textfsm.TextFSM(StringIO(tplt)) -+ t = textfsm.TextFSM(io.StringIO(tplt)) - - data = 'Matching text' - result = t.ParseText(data) -@@ -776,14 +853,14 @@ class UnitTestFSM(unittest.TestCase): - - # EOF explicitly suppressed in template. - tplt = 'Value boo (.*)\n\nStart\n ^$boo -> Next\n\nEOF\n' -- t = textfsm.TextFSM(StringIO(tplt)) -+ t = textfsm.TextFSM(io.StringIO(tplt)) - - result = t.ParseText(data) - self.assertListEqual(result, []) - - # Implicit EOF suppressed by argument. - tplt = 'Value boo (.*)\n\nStart\n ^$boo -> Next\n' -- t = textfsm.TextFSM(StringIO(tplt)) -+ t = textfsm.TextFSM(io.StringIO(tplt)) - - result = t.ParseText(data, eof=False) - self.assertListEqual(result, []) -@@ -792,7 +869,7 @@ class UnitTestFSM(unittest.TestCase): - - # End State, EOF is skipped. - tplt = 'Value boo (.*)\n\nStart\n ^$boo -> End\n ^$boo -> Record\n' -- t = textfsm.TextFSM(StringIO(tplt)) -+ t = textfsm.TextFSM(io.StringIO(tplt)) - data = 'Matching text A\nMatching text B' - - result = t.ParseText(data) -@@ -800,14 +877,14 @@ class UnitTestFSM(unittest.TestCase): - - # End State, with explicit Record. - tplt = 'Value boo (.*)\n\nStart\n ^$boo -> Record End\n' -- t = textfsm.TextFSM(StringIO(tplt)) -+ t = textfsm.TextFSM(io.StringIO(tplt)) - - result = t.ParseText(data) - self.assertListEqual(result, [['Matching text A']]) - - # EOF state transition is followed by implicit End State. - tplt = 'Value boo (.*)\n\nStart\n ^$boo -> EOF\n ^$boo -> Record\n' -- t = textfsm.TextFSM(StringIO(tplt)) -+ t = textfsm.TextFSM(io.StringIO(tplt)) - - result = t.ParseText(data) - self.assertListEqual(result, [['Matching text A']]) -@@ -815,14 +892,15 @@ class UnitTestFSM(unittest.TestCase): - def testInvalidRegexp(self): - - tplt = 'Value boo (.$*)\n\nStart\n ^$boo -> Next\n' -- self.assertRaises(textfsm.TextFSMTemplateError, -- textfsm.TextFSM, StringIO(tplt)) -+ self.assertRaises( -+ textfsm.TextFSMTemplateError, textfsm.TextFSM, io.StringIO(tplt) -+ ) - - def testValidRegexp(self): - """RegexObjects uncopyable in Python 2.6.""" - - tplt = 'Value boo (fo*)\n\nStart\n ^$boo -> Record\n' -- t = textfsm.TextFSM(StringIO(tplt)) -+ t = textfsm.TextFSM(io.StringIO(tplt)) - data = 'f\nfo\nfoo\n' - result = t.ParseText(data) - self.assertListEqual(result, [['f'], ['fo'], ['foo']]) -@@ -832,7 +910,7 @@ class UnitTestFSM(unittest.TestCase): - - tplt = 'Value boo (.*)\n\nStart\n ^$boo -> Next Stop\n\nStop\n ^abc\n' - output_text = 'one\ntwo' -- tmpl_file = StringIO(tplt) -+ tmpl_file = io.StringIO(tplt) - - t = textfsm.TextFSM(tmpl_file) - t.ParseText(output_text) -@@ -856,10 +934,11 @@ Start - 2 A2 -- - 3 -- B3 - """ -- t = textfsm.TextFSM(StringIO(tplt)) -+ t = textfsm.TextFSM(io.StringIO(tplt)) - result = t.ParseText(data) - self.assertListEqual( -- result, [['1', 'A2', 'B1'], ['2', 'A2', 'B3'], ['3', '', 'B3']]) -+ result, [['1', 'A2', 'B1'], ['2', 'A2', 'B3'], ['3', '', 'B3']] -+ ) - - - class UnitTestUnicode(unittest.TestCase): -@@ -918,7 +997,7 @@ State1 - ^$$ -> Next - ^$$ -> End - """ -- f = StringIO(buf) -+ f = io.StringIO(buf) - t = textfsm.TextFSM(f) - self.assertEqual(str(t), buf_result) - -Index: textfsm-1.1.3/tests/texttable_test.py -=================================================================== ---- textfsm-1.1.3.orig/tests/texttable_test.py -+++ textfsm-1.1.3/tests/texttable_test.py -@@ -16,14 +16,8 @@ - - """Unittest for text table.""" - --from __future__ import absolute_import --from __future__ import division --from __future__ import print_function --from __future__ import unicode_literals -- --from builtins import range -+import io - import unittest --from io import StringIO - from textfsm import terminal - from textfsm import texttable - -@@ -84,8 +78,8 @@ class UnitTestRow(unittest.TestCase): - self.assertEqual(3, len(row)) - - # Contains. -- self.assertTrue('two' not in row) -- self.assertTrue('Two' in row) -+ self.assertNotIn('two', row) -+ self.assertIn('Two', row) - - # Iteration. - self.assertEqual(['one', 'Two', 'three'], list(row)) -@@ -253,8 +247,8 @@ class UnitTestTextTable(unittest.TestCas - - def testContains(self): - t = self.BasicTable() -- self.assertTrue('a' in t) -- self.assertFalse('x' in t) -+ self.assertIn('a', t) -+ self.assertNotIn('x', t) - - def testIteration(self): - t = self.BasicTable() -@@ -271,6 +265,7 @@ class UnitTestTextTable(unittest.TestCas - - # Can we iterate repeatedly. - index = 0 -+ index2 = 0 - for r in t: - index += 1 - self.assertEqual(r, t[index]) -@@ -312,7 +307,7 @@ a,b, c, d # Trim comment - 10, 11 - # More comments. - """ -- f = StringIO(buf) -+ f = io.StringIO(buf) - t = texttable.TextTable() - self.assertEqual(2, t.CsvToTable(f)) - # pylint: disable=E1101 -@@ -514,7 +509,7 @@ a,b, c, d # Trim comment - 3, t._SmallestColSize('bbb ' + terminal.AnsiText('bb', ['red']))) - - def testFormattedTableColor(self): -- # Test to sepcify the color defined in terminal.FG_COLOR_WORDS -+ # Test to specify the color defined in terminal.FG_COLOR_WORDS - t = texttable.TextTable() - t.header = ('LSP', 'Name') - t.Append(('col1', 'col2')) -Index: textfsm-1.1.3/textfsm/clitable.py -=================================================================== ---- textfsm-1.1.3.orig/textfsm/clitable.py -+++ textfsm-1.1.3/textfsm/clitable.py -@@ -23,20 +23,12 @@ output combinations and store the data i - Is the glue between an automated command scraping program (such as RANCID) and - the TextFSM output parser. - """ --from __future__ import absolute_import --from __future__ import division --from __future__ import print_function --from __future__ import unicode_literals - - import copy - import os - import re - import threading --from builtins import object # pylint: disable=redefined-builtin --from builtins import str # pylint: disable=redefined-builtin - import textfsm -- --from textfsm import copyable_regex_object - from textfsm import texttable - - -@@ -48,7 +40,7 @@ class IndexTableError(Error): - """General INdexTable error.""" - - --class CliTableError(Error): -+class CliTableError(Error): # pylint: disable=g-bad-exception-name - """General CliTable error.""" - - -@@ -139,7 +131,7 @@ class IndexTable(object): - if precompile: - row[col] = precompile(col, row[col]) - if row[col]: -- row[col] = copyable_regex_object.CopyableRegexObject(row[col]) -+ row[col] = re.compile(row[col]) - - def GetRowMatch(self, attributes): - """Returns the row number that matches the supplied attributes.""" -@@ -148,8 +140,11 @@ class IndexTable(object): - for key in attributes: - # Silently skip attributes not present in the index file. - # pylint: disable=E1103 -- if (key in row.header and row[key] and -- not row[key].match(attributes[key])): -+ if ( -+ key in row.header -+ and row[key] -+ and not row[key].match(attributes[key]) -+ ): - # This line does not match, so break and try next row. - raise StopIteration() - return row.row -@@ -184,11 +179,12 @@ class CliTable(texttable.TextTable): - - # pylint: disable=E0213 - def Wrapper(main_obj, *args, **kwargs): -- main_obj._lock.acquire() # pylint: disable=W0212 -+ main_obj._lock.acquire() # pylint: disable=W0212 - try: - return func(main_obj, *args, **kwargs) # pylint: disable=E1102 - finally: -- main_obj._lock.release() # pylint: disable=W0212 -+ main_obj._lock.release() # pylint: disable=W0212 -+ - return Wrapper - - @synchronised -@@ -227,7 +223,7 @@ class CliTable(texttable.TextTable): - self.index = self.INDEX[fullpath] - - # Does the IndexTable have the right columns. -- if 'Template' not in self.index.index.header: # pylint: disable=E1103 -+ if 'Template' not in self.index.index.header: # pylint: disable=E1103 - raise CliTableError("Index file does not have 'Template' column.") - - def _TemplateNamesToFiles(self, template_str): -@@ -237,8 +233,7 @@ class CliTable(texttable.TextTable): - template_files = [] - try: - for tmplt in template_list: -- template_files.append( -- open(os.path.join(self.template_dir, tmplt), 'r')) -+ template_files.append(open(os.path.join(self.template_dir, tmplt), 'r')) - except: - for tmplt in template_files: - tmplt.close() -@@ -269,8 +264,9 @@ class CliTable(texttable.TextTable): - if row_idx: - templates = self.index.index[row_idx]['Template'] - else: -- raise CliTableError('No template found for attributes: "%s"' % -- attributes) -+ raise CliTableError( -+ 'No template found for attributes: "%s"' % attributes -+ ) - - template_files = self._TemplateNamesToFiles(templates) - -@@ -282,8 +278,9 @@ class CliTable(texttable.TextTable): - - # Add additional columns from any additional tables. - for tmplt in template_files[1:]: -- self.extend(self._ParseCmdItem(self.raw, template_file=tmplt), -- set(self._keys)) -+ self.extend( -+ self._ParseCmdItem(self.raw, template_file=tmplt), set(self._keys) -+ ) - finally: - for f in template_files: - f.close() -@@ -357,6 +354,7 @@ class CliTable(texttable.TextTable): - if not key and self._keys: - key = self.KeyValue - super(CliTable, self).sort(cmp=cmp, key=key, reverse=reverse) -+ - # pylint: enable=W0622 - - def AddKeys(self, key_list): -Index: textfsm-1.1.3/textfsm/parser.py -=================================================================== ---- textfsm-1.1.3.orig/textfsm/parser.py -+++ textfsm-1.1.3/textfsm/parser.py -@@ -23,28 +23,19 @@ A simple template language is used to de - parse a specific type of text input, returning a record of values - for each input entity. - """ --from __future__ import absolute_import --from __future__ import division --from __future__ import print_function --from __future__ import unicode_literals -- - - import getopt - import inspect - import re - import string - import sys --from builtins import object # pylint: disable=redefined-builtin --from builtins import str # pylint: disable=redefined-builtin --from builtins import zip # pylint: disable=redefined-builtin --import six - - - class Error(Exception): - """Base class for errors.""" - - --class Usage(Exception): -+class UsageError(Exception): - """Error in command line execution.""" - - -@@ -58,15 +49,15 @@ class TextFSMTemplateError(Error): - - # The below exceptions are internal state change triggers - # and not used as Errors. --class FSMAction(Exception): -+class FSMAction(Exception): # pylint: disable=g-bad-exception-name - """Base class for actions raised with the FSM.""" - - --class SkipRecord(FSMAction): -+class SkipRecord(FSMAction): # pylint: disable=g-bad-exception-name - """Indicate a record is to be skipped.""" - - --class SkipValue(FSMAction): -+class SkipValue(FSMAction): # pylint: disable=g-bad-exception-name - """Indicate a value is to be skipped.""" - - -@@ -175,8 +166,8 @@ class TextFSMOptions(object): - """Value constitutes part of the Key of the record.""" - - class List(OptionBase): -- r""" -- Value takes the form of a list. -+ # pylint: disable=g-space-before-docstring-summary -+ r"""Value takes the form of a list. - - If the value regex contains nested match groups in the form (?Pregex), - instead of adding a string to the list, we add a dictionary of the groups. -@@ -237,6 +228,7 @@ class TextFSMValue(object): - fsm: A TextFSMBase(), the containing FSM. - value: (str), the current value. - """ -+ - # The class which contains valid options. - - def __init__(self, fsm=None, max_name_len=48, options_class=None): -@@ -285,7 +277,6 @@ class TextFSMValue(object): - - Raises: - TextFSMTemplateError: Value declaration contains an error. -- - """ - - value_line = value.split(' ') -@@ -310,15 +301,17 @@ class TextFSMValue(object): - - if len(self.name) > self.max_name_len: - raise TextFSMTemplateError( -- "Invalid Value name '%s' or name too long." % self.name) -+ "Invalid Value name '%s' or name too long." % self.name -+ ) - -- if self.regex[0]!='(' or self.regex[-1]!=')' or self.regex[-2]=='\\': -+ if self.regex[0] != '(' or self.regex[-1] != ')' or self.regex[-2] == '\\': - raise TextFSMTemplateError( -- "Value '%s' must be contained within a '()' pair." % self.regex) -+ "Value '%s' must be contained within a '()' pair." % self.regex -+ ) - try: - compiled_regex = re.compile(self.regex) -- except re.error as e: -- raise TextFSMTemplateError(str(e)) -+ except re.error as exc: -+ raise TextFSMTemplateError(str(exc)) from exc - - self.template = re.sub(r'^\(', '(?P<%s>' % self.name, self.regex) - -@@ -345,8 +338,8 @@ class TextFSMValue(object): - # Create the option object - try: - option = self._options_cls.GetOption(name)(self) -- except AttributeError: -- raise TextFSMTemplateError('Unknown option "%s"' % name) -+ except AttributeError as exc: -+ raise TextFSMTemplateError('Unknown option "%s"' % name) from exc - - self.options.append(option) - -@@ -361,7 +354,8 @@ class TextFSMValue(object): - return 'Value %s %s %s' % ( - ','.join(self.OptionNames()), - self.name, -- self.regex) -+ self.regex, -+ ) - else: - return 'Value %s %s' % (self.name, self.regex) - -@@ -373,10 +367,10 @@ class CopyableRegexObject(object): - self.pattern = pattern - self.regex = re.compile(pattern) - -- def match(self, *args, **kwargs): -+ def match(self, *args, **kwargs): # pylint: disable=invalid-name - return self.regex.match(*args, **kwargs) - -- def sub(self, *args, **kwargs): -+ def sub(self, *args, **kwargs): # pylint: disable=invalid-name - return self.regex.sub(*args, **kwargs) - - def __copy__(self): -@@ -407,6 +401,7 @@ class TextFSMRule(object): - regex_obj: Compiled regex for which the rule matches. - line_num: Integer row number of Value. - """ -+ - # Implicit default is '(regexp) -> Next.NoRecord' - MATCH_ACTION = re.compile(r'(?P.*)(\s->(?P.*))') - -@@ -444,15 +439,16 @@ class TextFSMRule(object): - self.match = '' - self.regex = '' - self.regex_obj = None -- self.line_op = '' # Equivalent to 'Next'. -- self.record_op = '' # Equivalent to 'NoRecord'. -- self.new_state = '' # Equivalent to current state. -+ self.line_op = '' # Equivalent to 'Next'. -+ self.record_op = '' # Equivalent to 'NoRecord'. -+ self.new_state = '' # Equivalent to current state. - self.line_num = line_num - - line = line.strip() - if not line: -- raise TextFSMTemplateError('Null data in FSMRule. Line: %s' -- % self.line_num) -+ raise TextFSMTemplateError( -+ 'Null data in FSMRule. Line: %s' % self.line_num -+ ) - - # Is there '->' action present. - match_action = self.MATCH_ACTION.match(line) -@@ -466,18 +462,20 @@ class TextFSMRule(object): - if var_map: - try: - self.regex = string.Template(self.match).substitute(var_map) -- except (ValueError, KeyError): -+ except (ValueError, KeyError) as exc: - raise TextFSMTemplateError( -- "Duplicate or invalid variable substitution: '%s'. Line: %s." % -- (self.match, self.line_num)) -+ "Duplicate or invalid variable substitution: '%s'. Line: %s." -+ % (self.match, self.line_num) -+ ) from exc - - try: - # Work around a regression in Python 2.6 that makes RE Objects uncopyable. - self.regex_obj = CopyableRegexObject(self.regex) -- except re.error: -+ except re.error as exc: - raise TextFSMTemplateError( -- "Invalid regular expression: '%s'. Line: %s." % -- (self.regex, self.line_num)) -+ "Invalid regular expression: '%s'. Line: %s." -+ % (self.regex, self.line_num) -+ ) from exc - - # No '->' present, so done. - if not match_action: -@@ -493,8 +491,9 @@ class TextFSMRule(object): - action_re = self.ACTION3_RE.match(match_action.group('action')) - if not action_re: - # Last attempt, match an optional new state only. -- raise TextFSMTemplateError("Badly formatted rule '%s'. Line: %s." % -- (line, self.line_num)) -+ raise TextFSMTemplateError( -+ "Badly formatted rule '%s'. Line: %s." % (line, self.line_num) -+ ) - - # We have an Line operator. - if 'ln_op' in action_re.groupdict() and action_re.group('ln_op'): -@@ -514,14 +513,16 @@ class TextFSMRule(object): - if self.line_op == 'Continue' and self.new_state: - raise TextFSMTemplateError( - "Action '%s' with new state %s specified. Line: %s." -- % (self.line_op, self.new_state, self.line_num)) -+ % (self.line_op, self.new_state, self.line_num) -+ ) - - # Check that an error message is present only with the 'Error' operator. - if self.line_op != 'Error' and self.new_state: - if not re.match(r'\w+', self.new_state): - raise TextFSMTemplateError( - 'Alphanumeric characters only in state names. Line: %s.' -- % (self.line_num)) -+ % (self.line_num) -+ ) - - def __str__(self): - """Prints out the FSM Rule, mimic the input file.""" -@@ -555,6 +556,7 @@ class TextFSM(object): - header: Ordered list of values. - state_list: Ordered list of valid states. - """ -+ - # Variable and State name length. - MAX_NAME_LEN = 48 - comment_regex = re.compile(r'^\s*#') -@@ -709,7 +711,7 @@ class TextFSM(object): - # Blank line signifies end of Value definitions. - if not line: - return -- if not isinstance(line, six.string_types): -+ if not isinstance(line, str): - line = line.decode('utf-8') - # Skip commented lines. - if self.comment_regex.match(line): -@@ -718,21 +720,28 @@ class TextFSM(object): - if line.startswith('Value '): - try: - value = TextFSMValue( -- fsm=self, max_name_len=self.MAX_NAME_LEN, -- options_class=self._options_cls) -+ fsm=self, -+ max_name_len=self.MAX_NAME_LEN, -+ options_class=self._options_cls, -+ ) - value.Parse(line) -- except TextFSMTemplateError as error: -- raise TextFSMTemplateError('%s Line %s.' % (error, self._line_num)) -+ except TextFSMTemplateError as exc: -+ raise TextFSMTemplateError( -+ '%s Line %s.' % (exc, self._line_num) -+ ) from exc - - if value.name in self.header: - raise TextFSMTemplateError( - "Duplicate declarations for Value '%s'. Line: %s." -- % (value.name, self._line_num)) -+ % (value.name, self._line_num) -+ ) - - try: - self._ValidateOptions(value) -- except TextFSMTemplateError as error: -- raise TextFSMTemplateError('%s Line %s.' % (error, self._line_num)) -+ except TextFSMTemplateError as exc: -+ raise TextFSMTemplateError( -+ '%s Line %s.' % (exc, self._line_num) -+ ) from exc - - self.values.append(value) - self.value_map[value.name] = value.template -@@ -742,7 +751,8 @@ class TextFSM(object): - else: - raise TextFSMTemplateError( - 'Expected blank line after last Value entry. Line: %s.' -- % (self._line_num)) -+ % (self._line_num) -+ ) - - def _ValidateOptions(self, value): - """Checks that combination of Options is valid.""" -@@ -760,8 +770,8 @@ class TextFSM(object): - not clash with reserved names and are unique. - - Args: -- template: Valid template file after Value definitions -- have already been read. -+ template: Valid template file after Value definitions have already been -+ read. - - Returns: - Name of the state parsed from file. None otherwise. -@@ -778,22 +788,26 @@ class TextFSM(object): - for line in template: - self._line_num += 1 - line = line.rstrip() -- if not isinstance(line, six.string_types): -+ if not isinstance(line, str): - line = line.decode('utf-8') - # First line is state definition - if line and not self.comment_regex.match(line): -- # Ensure statename has valid syntax and is not a reserved word. -- if (not self.state_name_re.match(line) or -- len(line) > self.MAX_NAME_LEN or -- line in TextFSMRule.LINE_OP or -- line in TextFSMRule.RECORD_OP): -- raise TextFSMTemplateError("Invalid state name: '%s'. Line: %s" -- % (line, self._line_num)) -+ # Ensure statename has valid syntax and is not a reserved word. -+ if ( -+ not self.state_name_re.match(line) -+ or len(line) > self.MAX_NAME_LEN -+ or line in TextFSMRule.LINE_OP -+ or line in TextFSMRule.RECORD_OP -+ ): -+ raise TextFSMTemplateError( -+ "Invalid state name: '%s'. Line: %s" % (line, self._line_num) -+ ) - - state_name = line - if state_name in self.states: -- raise TextFSMTemplateError("Duplicate state name: '%s'. Line: %s" -- % (line, self._line_num)) -+ raise TextFSMTemplateError( -+ "Duplicate state name: '%s'. Line: %s" % (line, self._line_num) -+ ) - self.states[state_name] = [] - self.state_list.append(state_name) - break -@@ -806,7 +820,7 @@ class TextFSM(object): - # Finish rules processing on blank line. - if not line: - break -- if not isinstance(line, six.string_types): -+ if not isinstance(line, str): - line = line.decode('utf-8') - if self.comment_regex.match(line): - continue -@@ -814,11 +828,13 @@ class TextFSM(object): - # A rule within a state, starts with 1 or 2 spaces, or a tab. - if not line.startswith((' ^', ' ^', '\t^')): - raise TextFSMTemplateError( -- "Missing white space or carat ('^') before rule. Line: %s" % -- self._line_num) -+ "Missing white space or carat ('^') before rule. Line: %s" -+ % self._line_num -+ ) - - self.states[state_name].append( -- TextFSMRule(line, self._line_num, self.value_map)) -+ TextFSMRule(line, self._line_num, self.value_map) -+ ) - - return state_name - -@@ -864,8 +880,9 @@ class TextFSM(object): - - if rule.new_state not in self.states: - raise TextFSMTemplateError( -- "State '%s' not found, referenced in state '%s'" % -- (rule.new_state, state)) -+ "State '%s' not found, referenced in state '%s'" -+ % (rule.new_state, state) -+ ) - - return True - -@@ -877,7 +894,7 @@ class TextFSM(object): - Args: - text: (str), Text to parse with embedded newlines. - eof: (boolean), Set to False if we are parsing only part of the file. -- Suppresses triggering EOF state. -+ Suppresses triggering EOF state. - - Raises: - TextFSMError: An error occurred within the FSM. -@@ -902,7 +919,7 @@ class TextFSM(object): - - return self._result - -- def ParseTextToDicts(self, *args, **kwargs): -+ def ParseTextToDicts(self, text, eof=True): - """Calls ParseText and turns the result into list of dicts. - - List items are dicts of rows, dict key is column header and value is column -@@ -911,7 +928,7 @@ class TextFSM(object): - Args: - text: (str), Text to parse with embedded newlines. - eof: (boolean), Set to False if we are parsing only part of the file. -- Suppresses triggering EOF state. -+ Suppresses triggering EOF state. - - Raises: - TextFSMError: An error occurred within the FSM. -@@ -920,7 +937,7 @@ class TextFSM(object): - List of dicts. - """ - -- result_lists = self.ParseText(*args, **kwargs) -+ result_lists = self.ParseText(text, eof) - result_dicts = [] - - for row in result_lists: -@@ -972,9 +989,9 @@ class TextFSM(object): - matched: (regexp.match) Named group for each matched value. - value: (str) The matched value. - """ -- _value = self._GetValue(value) -- if _value is not None: -- _value.AssignVar(matched.group(value)) -+ self._value = self._GetValue(value) -+ if self._value is not None: -+ self._value.AssignVar(matched.group(value)) - - def _Operations(self, rule, line): - """Operators on the data record. -@@ -1017,11 +1034,15 @@ class TextFSM(object): - # Lastly process line operators. - if rule.line_op == 'Error': - if rule.new_state: -- raise TextFSMError('Error: %s. Rule Line: %s. Input Line: %s.' -- % (rule.new_state, rule.line_num, line)) -- -- raise TextFSMError('State Error raised. Rule Line: %s. Input Line: %s' -- % (rule.line_num, line)) -+ raise TextFSMError( -+ 'Error: %s. Rule Line: %s. Input Line: %s.' -+ % (rule.new_state, rule.line_num, line) -+ ) -+ -+ raise TextFSMError( -+ 'State Error raised. Rule Line: %s. Input Line: %s' -+ % (rule.line_num, line) -+ ) - - elif rule.line_op == 'Continue': - # Continue with current line without returning to the start of the state. -@@ -1060,8 +1081,8 @@ def main(argv=None): - - try: - opts, args = getopt.getopt(argv[1:], 'h', ['help']) -- except getopt.error as msg: -- raise Usage(msg) -+ except getopt.error as exc: -+ raise UsageError(exc) from exc - - for opt, _ in opts: - if opt in ('-h', '--help'): -@@ -1070,10 +1091,11 @@ def main(argv=None): - return 0 - - if not args or len(args) > 4: -- raise Usage('Invalid arguments.') -+ raise UsageError('Invalid arguments.') - - # If we have an argument, parse content of file and display as a template. - # Template displayed will match input template, minus any comment lines. -+ result = '' - with open(args[0], 'r') as template: - fsm = TextFSM(template) - print('FSM Template:\n%s\n' % fsm) -@@ -1108,7 +1130,7 @@ if __name__ == '__main__': - help_msg = '%s [--help] template [input_file [output_file]]\n' % sys.argv[0] - try: - sys.exit(main()) -- except Usage as err: -+ except UsageError as err: - print(err, file=sys.stderr) - print('For help use --help', file=sys.stderr) - sys.exit(2) -Index: textfsm-1.1.3/textfsm/terminal.py -=================================================================== ---- textfsm-1.1.3.orig/textfsm/terminal.py -+++ textfsm-1.1.3/textfsm/terminal.py -@@ -16,26 +16,20 @@ - - """Simple terminal related routines.""" - --from __future__ import absolute_import --from __future__ import division --from __future__ import print_function --from __future__ import unicode_literals -- --try: -- # Import fails on Windows machines. -- import fcntl -- import termios -- import tty --except (ImportError, ModuleNotFoundError): -- pass - import getopt - import os - import re - import struct - import sys - import time --from builtins import object # pylint: disable=redefined-builtin --from builtins import str # pylint: disable=redefined-builtin -+ -+try: -+ # Import fails on Windows machines. -+ import fcntl # pylint: disable=g-import-not-at-top -+ import termios # pylint: disable=g-import-not-at-top -+ import tty # pylint: disable=g-import-not-at-top -+except (ImportError, ModuleNotFoundError): -+ pass - - __version__ = '0.1.1' - -@@ -67,34 +61,38 @@ SGR = { - 'bg_cyan': 46, - 'bg_white': 47, - 'bg_reset': 49, -- } -+} - - # Provide a familar descriptive word for some ansi sequences. --FG_COLOR_WORDS = {'black': ['black'], -- 'dark_gray': ['bold', 'black'], -- 'blue': ['blue'], -- 'light_blue': ['bold', 'blue'], -- 'green': ['green'], -- 'light_green': ['bold', 'green'], -- 'cyan': ['cyan'], -- 'light_cyan': ['bold', 'cyan'], -- 'red': ['red'], -- 'light_red': ['bold', 'red'], -- 'purple': ['magenta'], -- 'light_purple': ['bold', 'magenta'], -- 'brown': ['yellow'], -- 'yellow': ['bold', 'yellow'], -- 'light_gray': ['white'], -- 'white': ['bold', 'white']} -- --BG_COLOR_WORDS = {'black': ['bg_black'], -- 'red': ['bg_red'], -- 'green': ['bg_green'], -- 'yellow': ['bg_yellow'], -- 'dark_blue': ['bg_blue'], -- 'purple': ['bg_magenta'], -- 'light_blue': ['bg_cyan'], -- 'grey': ['bg_white']} -+FG_COLOR_WORDS = { -+ 'black': ['black'], -+ 'dark_gray': ['bold', 'black'], -+ 'blue': ['blue'], -+ 'light_blue': ['bold', 'blue'], -+ 'green': ['green'], -+ 'light_green': ['bold', 'green'], -+ 'cyan': ['cyan'], -+ 'light_cyan': ['bold', 'cyan'], -+ 'red': ['red'], -+ 'light_red': ['bold', 'red'], -+ 'purple': ['magenta'], -+ 'light_purple': ['bold', 'magenta'], -+ 'brown': ['yellow'], -+ 'yellow': ['bold', 'yellow'], -+ 'light_gray': ['white'], -+ 'white': ['bold', 'white'], -+} -+ -+BG_COLOR_WORDS = { -+ 'black': ['bg_black'], -+ 'red': ['bg_red'], -+ 'green': ['bg_green'], -+ 'yellow': ['bg_yellow'], -+ 'dark_blue': ['bg_blue'], -+ 'purple': ['bg_magenta'], -+ 'light_blue': ['bg_cyan'], -+ 'grey': ['bg_white'], -+} - - - # Characters inserted at the start and end of ANSI strings -@@ -103,15 +101,14 @@ ANSI_START = '\001' - ANSI_END = '\002' - - --sgr_re = re.compile(r'(%s?\033\[\d+(?:;\d+)*m%s?)' % ( -- ANSI_START, ANSI_END)) -+sgr_re = re.compile(r'(%s?\033\[\d+(?:;\d+)*m%s?)' % (ANSI_START, ANSI_END)) - - - class Error(Exception): - """The base error class.""" - - --class Usage(Error): -+class UsageError(Error): - """Command line format error.""" - - -@@ -119,8 +116,8 @@ def _AnsiCmd(command_list): - """Takes a list of SGR values and formats them as an ANSI escape sequence. - - Args: -- command_list: List of strings, each string represents an SGR value. -- e.g. 'fg_blue', 'bg_yellow' -+ command_list: List of strings, each string represents an SGR value. e.g. -+ 'fg_blue', 'bg_yellow' - - Returns: - The ANSI escape sequence. -@@ -138,7 +135,7 @@ def _AnsiCmd(command_list): - # Convert to numerical strings. - command_str = [str(SGR[x.lower()]) for x in command_list] - # Wrap values in Ansi escape sequence (CSI prefix & SGR suffix). -- return '\033[%sm' % (';'.join(command_str)) -+ return '\033[%sm' % ';'.join(command_str) - - - def AnsiText(text, command_list=None, reset=True): -@@ -146,8 +143,8 @@ def AnsiText(text, command_list=None, re - - Args: - text: String to encase in sgr escape sequence. -- command_list: List of strings, each string represents an sgr value. -- e.g. 'fg_blue', 'bg_yellow' -+ command_list: List of strings, each string represents an sgr value. e.g. -+ 'fg_blue', 'bg_yellow' - reset: Boolean, if to add a reset sequence to the suffix of the text. - - Returns: -@@ -175,11 +172,11 @@ def TerminalSize(): - try: - with open(os.ctermid()) as tty_instance: - length_width = struct.unpack( -- 'hh', fcntl.ioctl(tty_instance.fileno(), termios.TIOCGWINSZ, '1234')) -+ 'hh', fcntl.ioctl(tty_instance.fileno(), termios.TIOCGWINSZ, '1234') -+ ) - except (IOError, OSError, NameError): - try: -- length_width = (int(os.environ['LINES']), -- int(os.environ['COLUMNS'])) -+ length_width = (int(os.environ['LINES']), int(os.environ['COLUMNS'])) - except (ValueError, KeyError): - length_width = (24, 80) - return length_width -@@ -201,27 +198,27 @@ def LineWrap(text, omit_sgr=False): - token_list = sgr_re.split(text_line) - text_line_list = [] - line_length = 0 -- for (index, token) in enumerate(token_list): -+ for index, token in enumerate(token_list): - # Skip null tokens. -- if token == '': -+ if not token: - continue - - if sgr_re.match(token): - # Add sgr escape sequences without splitting or counting length. - text_line_list.append(token) -- text_line = ''.join(token_list[index +1:]) -+ text_line = ''.join(token_list[index + 1 :]) - else: - if line_length + len(token) <= width: - # Token fits in line and we count it towards overall length. - text_line_list.append(token) - line_length += len(token) -- text_line = ''.join(token_list[index +1:]) -+ text_line = ''.join(token_list[index + 1 :]) - else: - # Line splits part way through this token. - # So split the token, form a new line and carry the remainder. -- text_line_list.append(token[:width - line_length]) -- text_line = token[width - line_length:] -- text_line += ''.join(token_list[index +1:]) -+ text_line_list.append(token[: width - line_length]) -+ text_line = token[width - line_length :] -+ text_line += ''.join(token_list[index + 1 :]) - break - - return (''.join(text_line_list), text_line) -@@ -233,8 +230,9 @@ def LineWrap(text, omit_sgr=False): - text_multiline = [] - for text_line in text.splitlines(): - # Is this a line that needs splitting? -- while ((omit_sgr and (len(StripAnsiText(text_line)) > width)) or -- (len(text_line) > width)): -+ while (omit_sgr and (len(StripAnsiText(text_line)) > width)) or ( -+ len(text_line) > width -+ ): - # If there are no sgr escape characters then do a straight split. - if not omit_sgr: - text_multiline.append(text_line[:width]) -@@ -284,8 +282,8 @@ class Pager(object): - - Args: - text: A string, the text that will be paged through. -- delay: A boolean, if True will cause a slight delay -- between line printing for more obvious scrolling. -+ delay: A boolean, if True will cause a slight delay between line printing -+ for more obvious scrolling. - """ - self._text = text or '' - self._delay = delay -@@ -356,7 +354,9 @@ class Pager(object): - text = LineWrap(self._text).splitlines() - while True: - # Get a list of new lines to display. -- self._newlines = text[self._displayed:self._displayed+self._lines_to_show] -+ self._newlines = text[ -+ self._displayed : self._displayed + self._lines_to_show -+ ] - for line in self._newlines: - sys.stdout.write(line + '\n') - if self._delay and self._lastscroll > 0: -@@ -366,19 +366,19 @@ class Pager(object): - if self._currentpagelines >= self._lines_to_show: - self._currentpagelines = 0 - wish = self._AskUser() -- if wish == 'q': # Quit pager. -+ if wish == 'q': # Quit pager. - return False -- elif wish == 'g': # Display till the end. -+ elif wish == 'g': # Display till the end. - self._Scroll(len(text) - self._displayed + 1) -- elif wish == '\r': # Enter, down a line. -+ elif wish == '\r': # Enter, down a line. - self._Scroll(1) - elif wish == '\033[B': # Down arrow, down a line. - self._Scroll(1) - elif wish == '\033[A': # Up arrow, up a line. - self._Scroll(-1) -- elif wish == 'b': # Up a page. -+ elif wish == 'b': # Up a page. - self._Scroll(0 - self._cli_lines) -- else: # Next page. -+ else: # Next page. - self._Scroll() - if self._displayed >= len(text): - break -@@ -389,8 +389,8 @@ class Pager(object): - """Set attributes to scroll the buffer correctly. - - Args: -- lines: An int, number of lines to scroll. If None, scrolls -- by the terminal length. -+ lines: An int, number of lines to scroll. If None, scrolls by the terminal -+ length. - """ - if lines is None: - lines = self._cli_lines -@@ -413,18 +413,19 @@ class Pager(object): - A string, the character entered by the user. - """ - if self._show_percent: -- progress = int(self._displayed*100 / (len(self._text.splitlines()))) -+ progress = int(self._displayed * 100 / (len(self._text.splitlines()))) - progress_text = ' (%d%%)' % progress - else: - progress_text = '' - question = AnsiText( -- 'Enter: next line, Space: next page, ' -- 'b: prev page, q: quit.%s' % -- progress_text, ['green']) -+ 'Enter: next line, Space: next page, b: prev page, q: quit.%s' -+ % progress_text, -+ ['green'], -+ ) - sys.stdout.write(question) - sys.stdout.flush() - ch = self._GetCh() -- sys.stdout.write('\r%s\r' % (' '*len(question))) -+ sys.stdout.write('\r%s\r' % (' ' * len(question))) - sys.stdout.flush() - return ch - -@@ -455,8 +456,8 @@ def main(argv=None): - - try: - opts, args = getopt.getopt(argv[1:], 'dhs', ['nodelay', 'help', 'size']) -- except getopt.error as msg: -- raise Usage(msg) -+ except getopt.error as exc: -+ raise UsageError(exc) from exc - - # Print usage and return, regardless of presence of other args. - for opt, _ in opts: -@@ -475,7 +476,7 @@ def main(argv=None): - elif opt in ('-d', '--delay'): - isdelay = True - else: -- raise Usage('Invalid arguments.') -+ raise UsageError('Invalid arguments.') - - # Page text supplied in either specified file or stdin. - -@@ -491,7 +492,7 @@ if __name__ == '__main__': - help_msg = '%s [--help] [--size] [--nodelay] [input_file]\n' % sys.argv[0] - try: - sys.exit(main()) -- except Usage as err: -+ except UsageError as err: - print(err, file=sys.stderr) - print('For help use --help', file=sys.stderr) - sys.exit(2) -Index: textfsm-1.1.3/textfsm/texttable.py -=================================================================== ---- textfsm-1.1.3.orig/textfsm/texttable.py -+++ textfsm-1.1.3/textfsm/texttable.py -@@ -22,22 +22,10 @@ Tables can be created from CSV input and - formats such as CSV and variable sized and justified rows. - """ - --from __future__ import absolute_import --from __future__ import division --from __future__ import print_function --from __future__ import unicode_literals -- - import copy --from functools import cmp_to_key -+import functools - import textwrap - --from builtins import next # pylint: disable=redefined-builtin --from builtins import object # pylint: disable=redefined-builtin --from builtins import range # pylint: disable=redefined-builtin --from builtins import str # pylint: disable=redefined-builtin --from builtins import zip # pylint: disable=redefined-builtin --import six -- - from textfsm import terminal - - -@@ -56,8 +44,11 @@ class Row(dict): - to make it behave like a regular dict() and list(). - - Attributes: -+ color: Colour spec of this row. -+ header: List of row's headers. - row: int, the row number in the container table. 0 is the header row. - table: A TextTable(), the associated container table. -+ values: List of row's values. - """ - - def __init__(self, *args, **kwargs): -@@ -162,7 +153,7 @@ class Row(dict): - except IndexError: - return default_value - -- def index(self, column): -+ def index(self, column): # pylint: disable=invalid-name - """Fetches the column number (0 indexed). - - Args: -@@ -174,12 +165,12 @@ class Row(dict): - Raises: - ValueError: The specified column was not found. - """ -- for i, key in enumerate(self._keys): -- if key == column: -- return i -- raise ValueError('Column "%s" not found.' % column) -+ try: -+ return self._keys.index(column) -+ except ValueError as exc: -+ raise ValueError('Column "%s" not found.' % column) from exc - -- def iterkeys(self): -+ def iterkeys(self): # pylint: disable=invalid-name - return iter(self._keys) - - def items(self): -@@ -263,12 +254,13 @@ class Row(dict): - elif isinstance(values, list) or isinstance(values, tuple): - if len(values) != len(self._values): - raise TypeError('Supplied list length != row length') -- for (index, value) in enumerate(values): -+ for index, value in enumerate(values): - self._values[index] = _ToStr(value) - - else: -- raise TypeError('Supplied argument must be Row, dict or list, not %s', -- type(values)) -+ raise TypeError( -+ 'Supplied argument must be Row, dict or list, not %s' % type(values) -+ ) - - def Insert(self, key, value, row_index): - """Inserts new values at a specified offset. -@@ -317,8 +309,8 @@ class TextTable(object): - """Initialises a new table. - - Args: -- row_class: A class to use as the row object. This should be a -- subclass of this module's Row() class. -+ row_class: A class to use as the row object. This should be a subclass of -+ this module's Row() class. - """ - self.row_class = row_class - self.separator = ', ' -@@ -327,7 +319,7 @@ class TextTable(object): - def Reset(self): - self._row_index = 1 - self._table = [[]] -- self._iterator = 0 # While loop row index -+ self._iterator = 0 # While loop row index - - def __repr__(self): - return '%s(%r)' % (self.__class__.__name__, str(self)) -@@ -337,7 +329,7 @@ class TextTable(object): - return self.table - - def __incr__(self, incr=1): -- self._SetRowIndex(self._row_index +incr) -+ self._SetRowIndex(self._row_index + incr) - - def __contains__(self, name): - """Whether the given column header name exists.""" -@@ -386,9 +378,9 @@ class TextTable(object): - """Construct Textable from the rows of which the function returns true. - - Args: -- function: A function applied to each row which returns a bool. If -- function is None, all rows with empty column values are -- removed. -+ function: A function applied to each row which returns a bool. If function -+ is None, all rows with empty column values are removed. -+ - Returns: - A new TextTable() - -@@ -403,7 +395,7 @@ class TextTable(object): - # pylint: disable=protected-access - new_table._table = [self.header] - for row in self: -- if function(row) is True: -+ if function(row): - new_table.Append(row) - return new_table - -@@ -430,6 +422,7 @@ class TextTable(object): - return new_table - - # pylint: disable=W0622 -+ # pylint: disable=invalid-name - def sort(self, cmp=None, key=None, reverse=False): - """Sorts rows in the texttable. - -@@ -455,7 +448,7 @@ class TextTable(object): - new_table = self._table[1:] - - if cmp is not None: -- key = cmp_to_key(cmp) -+ key = functools.cmp_to_key(cmp) - - new_table.sort(key=key, reverse=reverse) - -@@ -465,17 +458,19 @@ class TextTable(object): - # Re-write the 'row' attribute of each row - for index, row in enumerate(self._table): - row.row = index -+ - # pylint: enable=W0622 -+ # pylint: enable=invalid-name - -- def extend(self, table, keys=None): -+ def extend(self, table, keys=None): # pylint: disable=invalid-name - """Extends all rows in the texttable. - - The rows are extended with the new columns from the table. - - Args: - table: A texttable, the table to extend this table by. -- keys: A set, the set of columns to use as the key. If None, the -- row index is used. -+ keys: A set, the set of columns to use as the key. If None, the row index -+ is used. - - Raises: - IndexError: If key is not a valid column name. -@@ -483,7 +478,7 @@ class TextTable(object): - if keys: - for k in keys: - if k not in self._Header(): -- raise IndexError("Unknown key: '%s'", k) -+ raise IndexError("Unknown key: '%s'" % k) - - extend_with = [] - for column in table.header: -@@ -516,8 +511,8 @@ class TextTable(object): - """Removes a row from the table. - - Args: -- row: int, the row number to delete. Must be >= 1, as the header -- cannot be removed. -+ row: int, the row number to delete. Must be >= 1, as the header cannot be -+ removed. - - Raises: - TableError: Attempt to remove nonexistent or header row. -@@ -607,9 +602,7 @@ class TextTable(object): - # Avoid the global lookup cost on each iteration. - lstr = str - for row in self._table: -- result.append( -- '%s\n' % -- self.separator.join(lstr(v) for v in row)) -+ result.append('%s\n' % self.separator.join(lstr(v) for v in row)) - - return ''.join(result) - -@@ -618,7 +611,7 @@ class TextTable(object): - if not isinstance(table, TextTable): - raise TypeError('Not an instance of TextTable.') - self.Reset() -- self._table = copy.deepcopy(table._table) # pylint: disable=W0212 -+ self._table = copy.deepcopy(table._table) # pylint: disable=W0212 - # Point parent table of each row back ourselves. - for row in self: - row.table = self -@@ -666,15 +659,16 @@ class TextTable(object): - result.extend(self._TextJustify(paragraph, col_size)) - return result - -- wrapper = textwrap.TextWrapper(width=col_size-2, break_long_words=False, -- expand_tabs=False) -+ wrapper = textwrap.TextWrapper( -+ width=col_size - 2, break_long_words=False, expand_tabs=False -+ ) - try: - text_list = wrapper.wrap(text) -- except ValueError: -- raise TableError('Field too small (minimum width: 3)') -+ except ValueError as exc: -+ raise TableError('Field too small (minimum width: 3)') from exc - - if not text_list: -- return [' '*col_size] -+ return [' ' * col_size] - - for current_line in text_list: - stripped_len = len(terminal.StripAnsiText(current_line)) -@@ -687,16 +681,23 @@ class TextTable(object): - - return result - -- def FormattedTable(self, width=80, force_display=False, ml_delimiter=True, -- color=True, display_header=True, columns=None): -+ def FormattedTable( -+ self, -+ width=80, -+ force_display=False, -+ ml_delimiter=True, -+ color=True, -+ display_header=True, -+ columns=None, -+ ): - """Returns whole table, with whitespace padding and row delimiters. - - Args: - width: An int, the max width we want the table to fit in. - force_display: A bool, if set to True will display table when the table -- can't be made to fit to the width. -+ can't be made to fit to the width. - ml_delimiter: A bool, if set to False will not display the multi-line -- delimiter. -+ delimiter. - color: A bool. If true, display any colours in row.colour. - display_header: A bool. If true, display header. - columns: A list of str, show only columns with these names. -@@ -780,8 +781,9 @@ class TextTable(object): - for key in multi_word: - # If we scale past the desired width for this particular column, - # then give it its desired width and remove it from the wrapped list. -- if (largest[key] <= -- round((largest[key] / float(desired_width)) * spare_width)): -+ if largest[key] <= round( -+ (largest[key] / float(desired_width)) * spare_width -+ ): - smallest[key] = largest[key] - multi_word.remove(key) - spare_width -= smallest[key] -@@ -789,8 +791,9 @@ class TextTable(object): - done = False - # If we scale below the minimum width for this particular column, - # then leave it at its minimum and remove it from the wrapped list. -- elif (smallest[key] >= -- round((largest[key] / float(desired_width)) * spare_width)): -+ elif smallest[key] >= round( -+ (largest[key] / float(desired_width)) * spare_width -+ ): - multi_word.remove(key) - spare_width -= smallest[key] - desired_width -= largest[key] -@@ -799,8 +802,9 @@ class TextTable(object): - # Repeat the scaling algorithm with the final wrap list. - # This time we assign the extra column space by increasing 'smallest'. - for key in multi_word: -- smallest[key] = int(round((largest[key] / float(desired_width)) -- * spare_width)) -+ smallest[key] = int( -+ round((largest[key] / float(desired_width)) * spare_width) -+ ) - - total_width = 0 - row_count = 0 -@@ -822,7 +826,7 @@ class TextTable(object): - header_list.append(result_dict[key][row_idx]) - except IndexError: - # If no value than use whitespace of equal size. -- header_list.append(' '*smallest[key]) -+ header_list.append(' ' * smallest[key]) - header_list.append('\n') - - # Format and store the body lines -@@ -849,7 +853,7 @@ class TextTable(object): - prev_muli_line = True - # If current or prior line was multi-line then include delimiter. - if not first_line and prev_muli_line and ml_delimiter: -- body_list.append('-'*total_width + '\n') -+ body_list.append('-' * total_width + '\n') - if row_count == 1: - # Our current line was not wrapped, so clear flag. - prev_muli_line = False -@@ -861,20 +865,20 @@ class TextTable(object): - row_list.append(result_dict[key][row_idx]) - except IndexError: - # If no value than use whitespace of equal size. -- row_list.append(' '*smallest[key]) -+ row_list.append(' ' * smallest[key]) - row_list.append('\n') - - if color and row.color is not None: - body_list.append( -- terminal.AnsiText(''.join(row_list)[:-1], -- command_list=row.color)) -+ terminal.AnsiText(''.join(row_list)[:-1], command_list=row.color) -+ ) - body_list.append('\n') - else: - body_list.append(''.join(row_list)) - - first_line = False - -- header = ''.join(header_list) + '='*total_width -+ header = ''.join(header_list) + '=' * total_width - if color and self._Header().color is not None: - header = terminal.AnsiText(header, command_list=self._Header().color) - # Add double line delimiter between header and main body. -@@ -915,7 +919,7 @@ class TextTable(object): - - body = [] - for row in self: -- # Some of the row values are pulled into the label, stored in label_prefix. -+ # Some row values are pulled into the label, stored in label_prefix. - label_prefix = [] - value_list = [] - for key, value in row.items(): -@@ -925,8 +929,9 @@ class TextTable(object): - else: - value_list.append('%s %s' % (key, value)) - -- body.append(''.join( -- ['%s.%s\n' % ('.'.join(label_prefix), v) for v in value_list])) -+ body.append( -+ ''.join(['%s.%s\n' % ('.'.join(label_prefix), v) for v in value_list]) -+ ) - - return '%s%s' % (label_str, ''.join(body)) - -@@ -964,7 +969,6 @@ class TextTable(object): - - Raises: - TableError: Column name already exists. -- - """ - if column in self.table: - raise TableError('Column %r already in table.' % column) -@@ -1027,11 +1031,12 @@ class TextTable(object): - self.Reset() - - header_row = self.row_class() -+ header_length = 0 - if header: - line = buf.readline() - header_str = '' - while not header_str: -- if not isinstance(line, six.string_types): -+ if not isinstance(line, str): - line = line.decode('utf-8') - # Remove comments. - header_str = line.split('#')[0].strip() -@@ -1052,7 +1057,7 @@ class TextTable(object): - - # xreadlines would be better but not supported by StringIO for testing. - for line in buf: -- if not isinstance(line, six.string_types): -+ if not isinstance(line, str): - line = line.decode('utf-8') - # Support commented lines, provide '#' is first character of line. - if line.startswith('#'): -@@ -1066,8 +1071,9 @@ class TextTable(object): - if not header: - header_row = self.row_class() - header_length = len(lst) -- header_row.values = dict(zip(range(header_length), -- range(header_length))) -+ header_row.values = dict( -+ zip(range(header_length), range(header_length)) -+ ) - self._table[0] = header_row - header = True - continue -@@ -1079,7 +1085,7 @@ class TextTable(object): - - return self.size - -- def index(self, name=None): -+ def index(self, name=None): # pylint: disable=invalid-name - """Returns index number of supplied column name. - - Args: -@@ -1093,5 +1099,5 @@ class TextTable(object): - """ - try: - return self.header.index(name) -- except ValueError: -- raise TableError('Unknown index name %s.' % name) -+ except ValueError as exc: -+ raise TableError('Unknown index name %s.' % name) from exc diff --git a/python-textfsm.changes b/python-textfsm.changes index b989db5..6969517 100644 --- a/python-textfsm.changes +++ b/python-textfsm.changes @@ -1,3 +1,17 @@ +------------------------------------------------------------------- +Thu Apr 17 06:48:35 UTC 2025 - Martin Hauke + +- Update to version 2.1.0 + * Improved routine for terminal dimensions. + * Improve clarity or variable names + * Remove TerminalSize function .... no longer needed + * Refactor pager class +- Update to version 2.0.0 + * Makes use of shutil library for terminal operations. +- Drop not longer needed patches: + * correct-version.patch + * python-textfsm-no-python2.patch + ------------------------------------------------------------------- Mon Jan 29 10:03:54 UTC 2024 - pgajdos@suse.com diff --git a/python-textfsm.spec b/python-textfsm.spec index 08fd38e..7892630 100644 --- a/python-textfsm.spec +++ b/python-textfsm.spec @@ -1,7 +1,7 @@ # # spec file for package python-textfsm # -# Copyright (c) 2024 SUSE LLC +# Copyright (c) 2025 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,16 +18,12 @@ %{?sle15_python_module_pythons} Name: python-textfsm -Version: 1.1.3 +Version: 2.1.0 Release: 0 Summary: Python module for parsing semi-structured text into python tables License: Apache-2.0 URL: https://github.com/google/textfsm Source: https://github.com/google/textfsm/archive/v%{version}.tar.gz#/textfsm-%{version}.tar.gz -# PATCH-FIX-OPENSUSE https://github.com/google/textfsm/issues/118 -Patch0: correct-version.patch -# https://github.com/google/textfsm/commit/c8843d69daa9b565fea99a0283ad13c324d5b563 -Patch1: python-textfsm-no-python2.patch BuildRequires: %{python_module pip} BuildRequires: %{python_module pytest} BuildRequires: %{python_module setuptools} diff --git a/textfsm-1.1.3.tar.gz b/textfsm-1.1.3.tar.gz deleted file mode 100644 index 7c04eb8..0000000 --- a/textfsm-1.1.3.tar.gz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b11e4ac4d7e4793449deff3fb90c8ba85e223343670eec0ae5c5c8e94f479f34 -size 51296 diff --git a/textfsm-2.1.0.tar.gz b/textfsm-2.1.0.tar.gz new file mode 100644 index 0000000..d05c1a7 --- /dev/null +++ b/textfsm-2.1.0.tar.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0c0e96974250008c28ce854c2e0bad7ef75690c9c50f2c4ffeafb2f1ce4876b7 +size 52040