cmd2 2.5.10__py3-none-any.whl → 2.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cmd2/__init__.py +11 -22
- cmd2/ansi.py +78 -91
- cmd2/argparse_completer.py +109 -132
- cmd2/argparse_custom.py +199 -217
- cmd2/clipboard.py +2 -6
- cmd2/cmd2.py +447 -521
- cmd2/command_definition.py +34 -44
- cmd2/constants.py +1 -3
- cmd2/decorators.py +47 -58
- cmd2/exceptions.py +23 -46
- cmd2/history.py +29 -43
- cmd2/parsing.py +59 -84
- cmd2/plugin.py +6 -10
- cmd2/py_bridge.py +20 -26
- cmd2/rl_utils.py +45 -69
- cmd2/table_creator.py +83 -106
- cmd2/transcript.py +55 -59
- cmd2/utils.py +143 -176
- {cmd2-2.5.10.dist-info → cmd2-2.6.0.dist-info}/METADATA +34 -17
- cmd2-2.6.0.dist-info/RECORD +24 -0
- {cmd2-2.5.10.dist-info → cmd2-2.6.0.dist-info}/WHEEL +1 -1
- cmd2-2.5.10.dist-info/RECORD +0 -24
- {cmd2-2.5.10.dist-info → cmd2-2.6.0.dist-info/licenses}/LICENSE +0 -0
- {cmd2-2.5.10.dist-info → cmd2-2.6.0.dist-info}/top_level.txt +0 -0
cmd2/transcript.py
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
#
|
2
|
-
# -*- coding: utf-8 -*-
|
3
1
|
"""Machinery for running and validating transcripts.
|
4
2
|
|
5
3
|
If the user wants to run a transcript (see docs/transcript.rst),
|
@@ -12,13 +10,11 @@ class is used in cmd2.py::run_transcript_tests()
|
|
12
10
|
|
13
11
|
import re
|
14
12
|
import unittest
|
13
|
+
from collections.abc import Iterator
|
15
14
|
from typing import (
|
16
15
|
TYPE_CHECKING,
|
17
|
-
Iterator,
|
18
|
-
List,
|
19
16
|
Optional,
|
20
17
|
TextIO,
|
21
|
-
Tuple,
|
22
18
|
cast,
|
23
19
|
)
|
24
20
|
|
@@ -46,31 +42,33 @@ class Cmd2TestCase(unittest.TestCase):
|
|
46
42
|
cmdapp: Optional['Cmd'] = None
|
47
43
|
|
48
44
|
def setUp(self) -> None:
|
45
|
+
"""Instructions that will be executed before each test method."""
|
49
46
|
if self.cmdapp:
|
50
|
-
self.
|
47
|
+
self._fetch_transcripts()
|
51
48
|
|
52
49
|
# Trap stdout
|
53
50
|
self._orig_stdout = self.cmdapp.stdout
|
54
51
|
self.cmdapp.stdout = cast(TextIO, utils.StdSim(cast(TextIO, self.cmdapp.stdout)))
|
55
52
|
|
56
53
|
def tearDown(self) -> None:
|
54
|
+
"""Instructions that will be executed after each test method."""
|
57
55
|
if self.cmdapp:
|
58
56
|
# Restore stdout
|
59
57
|
self.cmdapp.stdout = self._orig_stdout
|
60
58
|
|
61
|
-
def runTest(self) -> None: # was testall
|
59
|
+
def runTest(self) -> None: # was testall # noqa: N802
|
60
|
+
"""Override of the default runTest method for the unittest.TestCase class."""
|
62
61
|
if self.cmdapp:
|
63
62
|
its = sorted(self.transcripts.items())
|
64
63
|
for fname, transcript in its:
|
65
64
|
self._test_transcript(fname, transcript)
|
66
65
|
|
67
|
-
def
|
66
|
+
def _fetch_transcripts(self) -> None:
|
68
67
|
self.transcripts = {}
|
69
|
-
testfiles = cast(
|
68
|
+
testfiles = cast(list[str], getattr(self.cmdapp, 'testfiles', []))
|
70
69
|
for fname in testfiles:
|
71
|
-
|
72
|
-
|
73
|
-
tfile.close()
|
70
|
+
with open(fname) as tfile:
|
71
|
+
self.transcripts[fname] = iter(tfile.readlines())
|
74
72
|
|
75
73
|
def _test_transcript(self, fname: str, transcript: Iterator[str]) -> None:
|
76
74
|
if self.cmdapp is None:
|
@@ -112,9 +110,9 @@ class Cmd2TestCase(unittest.TestCase):
|
|
112
110
|
# Read the expected result from transcript
|
113
111
|
if ansi.strip_style(line).startswith(self.cmdapp.visible_prompt):
|
114
112
|
message = f'\nFile {fname}, line {line_num}\nCommand was:\n{command}\nExpected: (nothing)\nGot:\n{result}\n'
|
115
|
-
|
113
|
+
assert not result.strip(), message # noqa: S101
|
116
114
|
# If the command signaled the application to quit there should be no more commands
|
117
|
-
|
115
|
+
assert not stop, stop_msg # noqa: S101
|
118
116
|
continue
|
119
117
|
expected_parts = []
|
120
118
|
while not ansi.strip_style(line).startswith(self.cmdapp.visible_prompt):
|
@@ -128,13 +126,13 @@ class Cmd2TestCase(unittest.TestCase):
|
|
128
126
|
|
129
127
|
if stop:
|
130
128
|
# This should only be hit if the command that set stop to True had output text
|
131
|
-
|
129
|
+
assert finished, stop_msg # noqa: S101
|
132
130
|
|
133
131
|
# transform the expected text into a valid regular expression
|
134
132
|
expected = ''.join(expected_parts)
|
135
133
|
expected = self._transform_transcript_expected(expected)
|
136
134
|
message = f'\nFile {fname}, line {line_num}\nCommand was:\n{command}\nExpected:\n{expected}\nGot:\n{result}\n'
|
137
|
-
|
135
|
+
assert re.match(expected, result, re.MULTILINE | re.DOTALL), message # noqa: S101
|
138
136
|
|
139
137
|
def _transform_transcript_expected(self, s: str) -> str:
|
140
138
|
r"""Parse the string with slashed regexes into a valid regex.
|
@@ -162,29 +160,28 @@ class Cmd2TestCase(unittest.TestCase):
|
|
162
160
|
# no more slashes, add the rest of the string and bail
|
163
161
|
regex += re.escape(s[start:])
|
164
162
|
break
|
163
|
+
# there is a slash, add everything we have found so far
|
164
|
+
# add stuff before the first slash as plain text
|
165
|
+
regex += re.escape(s[start:first_slash_pos])
|
166
|
+
start = first_slash_pos + 1
|
167
|
+
# and go find the next one
|
168
|
+
(regex, second_slash_pos, start) = self._escaped_find(regex, s, start, True)
|
169
|
+
if second_slash_pos > 0:
|
170
|
+
# add everything between the slashes (but not the slashes)
|
171
|
+
# as a regular expression
|
172
|
+
regex += s[start:second_slash_pos]
|
173
|
+
# and change where we start looking for slashed on the
|
174
|
+
# turn through the loop
|
175
|
+
start = second_slash_pos + 1
|
165
176
|
else:
|
166
|
-
#
|
167
|
-
#
|
168
|
-
regex += re.escape(s[start:
|
169
|
-
|
170
|
-
# and go find the next one
|
171
|
-
(regex, second_slash_pos, start) = self._escaped_find(regex, s, start, True)
|
172
|
-
if second_slash_pos > 0:
|
173
|
-
# add everything between the slashes (but not the slashes)
|
174
|
-
# as a regular expression
|
175
|
-
regex += s[start:second_slash_pos]
|
176
|
-
# and change where we start looking for slashed on the
|
177
|
-
# turn through the loop
|
178
|
-
start = second_slash_pos + 1
|
179
|
-
else:
|
180
|
-
# No closing slash, we have to add the first slash,
|
181
|
-
# and the rest of the text
|
182
|
-
regex += re.escape(s[start - 1 :])
|
183
|
-
break
|
177
|
+
# No closing slash, we have to add the first slash,
|
178
|
+
# and the rest of the text
|
179
|
+
regex += re.escape(s[start - 1 :])
|
180
|
+
break
|
184
181
|
return regex
|
185
182
|
|
186
183
|
@staticmethod
|
187
|
-
def _escaped_find(regex: str, s: str, start: int, in_regex: bool) ->
|
184
|
+
def _escaped_find(regex: str, s: str, start: int, in_regex: bool) -> tuple[str, int, int]:
|
188
185
|
"""Find the next slash in {s} after {start} that is not preceded by a backslash.
|
189
186
|
|
190
187
|
If we find an escaped slash, add everything up to and including it to regex,
|
@@ -200,32 +197,31 @@ class Cmd2TestCase(unittest.TestCase):
|
|
200
197
|
if pos == -1:
|
201
198
|
# no match, return to caller
|
202
199
|
break
|
203
|
-
|
200
|
+
if pos == 0:
|
204
201
|
# slash at the beginning of the string, so it can't be
|
205
202
|
# escaped. We found it.
|
206
203
|
break
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
regex += s[pos]
|
217
|
-
else:
|
218
|
-
# add everything up to the backslash as escaped
|
219
|
-
# plain text
|
220
|
-
regex += re.escape(s[start : pos - 1])
|
221
|
-
# and then add the slash as escaped
|
222
|
-
# plain text
|
223
|
-
regex += re.escape(s[pos])
|
224
|
-
# update start to show we have handled everything
|
225
|
-
# before it
|
226
|
-
start = pos + 1
|
227
|
-
# and continue to look
|
204
|
+
# check if the slash is preceded by a backslash
|
205
|
+
if s[pos - 1 : pos] == '\\':
|
206
|
+
# it is.
|
207
|
+
if in_regex:
|
208
|
+
# add everything up to the backslash as a
|
209
|
+
# regular expression
|
210
|
+
regex += s[start : pos - 1]
|
211
|
+
# skip the backslash, and add the slash
|
212
|
+
regex += s[pos]
|
228
213
|
else:
|
229
|
-
#
|
230
|
-
|
214
|
+
# add everything up to the backslash as escaped
|
215
|
+
# plain text
|
216
|
+
regex += re.escape(s[start : pos - 1])
|
217
|
+
# and then add the slash as escaped
|
218
|
+
# plain text
|
219
|
+
regex += re.escape(s[pos])
|
220
|
+
# update start to show we have handled everything
|
221
|
+
# before it
|
222
|
+
start = pos + 1
|
223
|
+
# and continue to look
|
224
|
+
else:
|
225
|
+
# slash is not escaped, this is what we are looking for
|
226
|
+
break
|
231
227
|
return regex, pos, start
|