@ai-ecoverse/py-pyparsing 3.3.3-1

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.
Files changed (43) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +3 -0
  3. package/lib/python3.14/site-packages/pyparsing/__init__.py +413 -0
  4. package/lib/python3.14/site-packages/pyparsing/__pycache__/__init__.cpython-314.pyc +0 -0
  5. package/lib/python3.14/site-packages/pyparsing/__pycache__/actions.cpython-314.pyc +0 -0
  6. package/lib/python3.14/site-packages/pyparsing/__pycache__/common.cpython-314.pyc +0 -0
  7. package/lib/python3.14/site-packages/pyparsing/__pycache__/core.cpython-314.pyc +0 -0
  8. package/lib/python3.14/site-packages/pyparsing/__pycache__/exceptions.cpython-314.pyc +0 -0
  9. package/lib/python3.14/site-packages/pyparsing/__pycache__/helpers.cpython-314.pyc +0 -0
  10. package/lib/python3.14/site-packages/pyparsing/__pycache__/results.cpython-314.pyc +0 -0
  11. package/lib/python3.14/site-packages/pyparsing/__pycache__/testing.cpython-314.pyc +0 -0
  12. package/lib/python3.14/site-packages/pyparsing/__pycache__/unicode.cpython-314.pyc +0 -0
  13. package/lib/python3.14/site-packages/pyparsing/__pycache__/util.cpython-314.pyc +0 -0
  14. package/lib/python3.14/site-packages/pyparsing/__pycache__/warnings.cpython-314.pyc +0 -0
  15. package/lib/python3.14/site-packages/pyparsing/actions.py +264 -0
  16. package/lib/python3.14/site-packages/pyparsing/ai/__init__.py +0 -0
  17. package/lib/python3.14/site-packages/pyparsing/ai/__pycache__/__init__.cpython-314.pyc +0 -0
  18. package/lib/python3.14/site-packages/pyparsing/ai/best_practices.md +75 -0
  19. package/lib/python3.14/site-packages/pyparsing/ai/show_best_practices/__init__.py +0 -0
  20. package/lib/python3.14/site-packages/pyparsing/ai/show_best_practices/__main__.py +2 -0
  21. package/lib/python3.14/site-packages/pyparsing/ai/show_best_practices/__pycache__/__init__.cpython-314.pyc +0 -0
  22. package/lib/python3.14/site-packages/pyparsing/ai/show_best_practices/__pycache__/__main__.cpython-314.pyc +0 -0
  23. package/lib/python3.14/site-packages/pyparsing/common.py +570 -0
  24. package/lib/python3.14/site-packages/pyparsing/core.py +6972 -0
  25. package/lib/python3.14/site-packages/pyparsing/diagram/__init__.py +761 -0
  26. package/lib/python3.14/site-packages/pyparsing/diagram/__pycache__/__init__.cpython-314.pyc +0 -0
  27. package/lib/python3.14/site-packages/pyparsing/exceptions.py +353 -0
  28. package/lib/python3.14/site-packages/pyparsing/helpers.py +1769 -0
  29. package/lib/python3.14/site-packages/pyparsing/py.typed +0 -0
  30. package/lib/python3.14/site-packages/pyparsing/results.py +928 -0
  31. package/lib/python3.14/site-packages/pyparsing/testing.py +398 -0
  32. package/lib/python3.14/site-packages/pyparsing/tools/__init__.py +0 -0
  33. package/lib/python3.14/site-packages/pyparsing/tools/__pycache__/__init__.cpython-314.pyc +0 -0
  34. package/lib/python3.14/site-packages/pyparsing/tools/__pycache__/cvt_pyparsing_pep8_names.cpython-314.pyc +0 -0
  35. package/lib/python3.14/site-packages/pyparsing/tools/cvt_pyparsing_pep8_names.py +142 -0
  36. package/lib/python3.14/site-packages/pyparsing/unicode.py +356 -0
  37. package/lib/python3.14/site-packages/pyparsing/util.py +514 -0
  38. package/lib/python3.14/site-packages/pyparsing/warnings.py +10 -0
  39. package/lib/python3.14/site-packages/pyparsing-3.3.3.dist-info/METADATA +147 -0
  40. package/lib/python3.14/site-packages/pyparsing-3.3.3.dist-info/RECORD +23 -0
  41. package/lib/python3.14/site-packages/pyparsing-3.3.3.dist-info/WHEEL +4 -0
  42. package/lib/python3.14/site-packages/pyparsing-3.3.3.dist-info/licenses/LICENSE +20 -0
  43. package/package.json +27 -0
@@ -0,0 +1,398 @@
1
+ # testing.py
2
+
3
+ from contextlib import contextmanager
4
+ import re
5
+ import typing
6
+ import unittest
7
+
8
+
9
+ from .core import (
10
+ ParserElement,
11
+ ParseException,
12
+ Keyword,
13
+ __diag__,
14
+ __compat__,
15
+ )
16
+ from . import core_builtin_exprs
17
+
18
+
19
+ class pyparsing_test:
20
+ """
21
+ namespace class for classes useful in writing unit tests
22
+ """
23
+
24
+ class reset_pyparsing_context:
25
+ """
26
+ Context manager to be used when writing unit tests that modify pyparsing config values:
27
+ - packrat parsing
28
+ - bounded recursion parsing
29
+ - default whitespace characters
30
+ - default keyword characters
31
+ - literal string auto-conversion class
32
+ - ``__diag__`` settings
33
+
34
+ Example:
35
+
36
+ .. testcode::
37
+
38
+ ppt = pyparsing.pyparsing_test
39
+
40
+ class MyTestClass(ppt.TestParseResultsAsserts):
41
+ def test_literal(self):
42
+ with ppt.reset_pyparsing_context():
43
+ # test that literals used to construct
44
+ # a grammar are automatically suppressed
45
+ ParserElement.inline_literals_using(Suppress)
46
+
47
+ term = Word(alphas) | Word(nums)
48
+ group = Group('(' + term[...] + ')')
49
+
50
+ # assert that the '()' characters
51
+ # are not included in the parsed tokens
52
+ self.assertParseAndCheckList(
53
+ group,
54
+ "(abc 123 def)",
55
+ ['abc', '123', 'def']
56
+ )
57
+
58
+ # after exiting context manager, literals
59
+ # are converted to Literal expressions again
60
+ """
61
+
62
+ def __init__(self):
63
+ self._save_context = {}
64
+
65
+ def save(self):
66
+ self._save_context["default_whitespace"] = ParserElement.DEFAULT_WHITE_CHARS
67
+ self._save_context["default_keyword_chars"] = Keyword.DEFAULT_KEYWORD_CHARS
68
+
69
+ self._save_context["literal_string_class"] = (
70
+ ParserElement._literalStringClass
71
+ )
72
+
73
+ self._save_context["verbose_stacktrace"] = ParserElement.verbose_stacktrace
74
+
75
+ self._save_context["packrat_enabled"] = ParserElement._packratEnabled
76
+ if ParserElement._packratEnabled:
77
+ self._save_context["packrat_cache_size"] = (
78
+ ParserElement.packrat_cache.size
79
+ )
80
+ else:
81
+ self._save_context["packrat_cache_size"] = None
82
+ self._save_context["packrat_parse"] = ParserElement._parse
83
+ self._save_context["recursion_enabled"] = (
84
+ ParserElement._left_recursion_enabled
85
+ )
86
+
87
+ self._save_context["__diag__"] = {
88
+ name: getattr(__diag__, name) for name in __diag__._all_names
89
+ }
90
+
91
+ self._save_context["__compat__"] = {
92
+ "collect_all_And_tokens": __compat__.collect_all_And_tokens
93
+ }
94
+
95
+ return self
96
+
97
+ def restore(self):
98
+ # reset pyparsing global state
99
+ if (
100
+ ParserElement.DEFAULT_WHITE_CHARS
101
+ != self._save_context["default_whitespace"]
102
+ ):
103
+ ParserElement.set_default_whitespace_chars(
104
+ self._save_context["default_whitespace"]
105
+ )
106
+
107
+ ParserElement.verbose_stacktrace = self._save_context["verbose_stacktrace"]
108
+
109
+ Keyword.DEFAULT_KEYWORD_CHARS = self._save_context["default_keyword_chars"]
110
+ ParserElement.inline_literals_using(
111
+ self._save_context["literal_string_class"]
112
+ )
113
+
114
+ for name, value in self._save_context["__diag__"].items():
115
+ (__diag__.enable if value else __diag__.disable)(name)
116
+
117
+ ParserElement._packratEnabled = False
118
+ if self._save_context["packrat_enabled"]:
119
+ ParserElement.enable_packrat(self._save_context["packrat_cache_size"])
120
+ else:
121
+ ParserElement._parse = self._save_context["packrat_parse"]
122
+ ParserElement._left_recursion_enabled = self._save_context[
123
+ "recursion_enabled"
124
+ ]
125
+
126
+ # clear debug flags on all builtins
127
+ for expr in core_builtin_exprs:
128
+ expr.set_debug(False)
129
+
130
+ __compat__.collect_all_And_tokens = self._save_context["__compat__"]
131
+
132
+ return self
133
+
134
+ def copy(self):
135
+ ret = type(self)()
136
+ ret._save_context.update(self._save_context)
137
+ return ret
138
+
139
+ def __enter__(self):
140
+ return self.save()
141
+
142
+ def __exit__(self, *args):
143
+ self.restore()
144
+
145
+ class TestParseResultsAsserts(unittest.TestCase):
146
+ """
147
+ A mixin class to add parse results assertion methods to normal unittest.TestCase classes.
148
+ """
149
+
150
+ def assertParseResultsEquals(
151
+ self, result, expected_list=None, expected_dict=None, msg=None
152
+ ):
153
+ """
154
+ Unit test assertion to compare a :class:`ParseResults` object with an optional ``expected_list``,
155
+ and compare any defined results names with an optional ``expected_dict``.
156
+ """
157
+ if expected_list is not None:
158
+ self.assertEqual(expected_list, result.as_list(), msg=msg)
159
+ if expected_dict is not None:
160
+ self.assertEqual(expected_dict, result.as_dict(), msg=msg)
161
+
162
+ def assertParseAndCheckList(
163
+ self, expr, test_string, expected_list, msg=None, verbose=True
164
+ ):
165
+ """
166
+ Convenience wrapper assert to test a parser element and input string, and assert that
167
+ the resulting :meth:`ParseResults.as_list` is equal to the ``expected_list``.
168
+ """
169
+ result = expr.parse_string(test_string, parse_all=True)
170
+ if verbose:
171
+ print(result.dump())
172
+ else:
173
+ print(result.as_list())
174
+ self.assertParseResultsEquals(result, expected_list=expected_list, msg=msg)
175
+
176
+ def assertParseAndCheckDict(
177
+ self, expr, test_string, expected_dict, msg=None, verbose=True
178
+ ):
179
+ """
180
+ Convenience wrapper assert to test a parser element and input string, and assert that
181
+ the resulting :meth:`ParseResults.as_dict` is equal to the ``expected_dict``.
182
+ """
183
+ result = expr.parse_string(test_string, parse_all=True)
184
+ if verbose:
185
+ print(result.dump())
186
+ else:
187
+ print(result.as_list())
188
+ self.assertParseResultsEquals(result, expected_dict=expected_dict, msg=msg)
189
+
190
+ def assertRunTestResults(
191
+ self, run_tests_report, expected_parse_results=None, msg=None
192
+ ):
193
+ """
194
+ Unit test assertion to evaluate output of
195
+ :meth:`~ParserElement.run_tests`.
196
+
197
+ If a list of list-dict tuples is given as the
198
+ ``expected_parse_results`` argument, then these are zipped
199
+ with the report tuples returned by ``run_tests()``
200
+ and evaluated using :meth:`assertParseResultsEquals`.
201
+ Finally, asserts that the overall
202
+ `:meth:~ParserElement.run_tests` success value is ``True``.
203
+
204
+ :param run_tests_report: the return value from :meth:`ParserElement.run_tests`
205
+ :type run_tests_report: tuple[bool, list[tuple[str, ParseResults | Exception]]]
206
+ :param expected_parse_results: (optional)
207
+ :type expected_parse_results: list[tuple[str | list | dict | Exception, ...]]
208
+ """
209
+ run_test_success, run_test_results = run_tests_report
210
+
211
+ if expected_parse_results is None:
212
+ self.assertTrue(
213
+ run_test_success, msg=msg if msg is not None else "failed runTests"
214
+ )
215
+ return
216
+
217
+ merged = [
218
+ (*rpt, expected)
219
+ for rpt, expected in zip(run_test_results, expected_parse_results)
220
+ ]
221
+ for test_string, result, expected in merged:
222
+ # expected should be a tuple containing a list and/or a dict or an exception,
223
+ # and optional failure message string
224
+ # an empty tuple will skip any result validation
225
+ fail_msg = next((exp for exp in expected if isinstance(exp, str)), None)
226
+ expected_exception = next(
227
+ (
228
+ exp
229
+ for exp in expected
230
+ if isinstance(exp, type) and issubclass(exp, Exception)
231
+ ),
232
+ None,
233
+ )
234
+ if expected_exception is not None:
235
+ with self.assertRaises(
236
+ expected_exception=expected_exception, msg=fail_msg or msg
237
+ ):
238
+ if isinstance(result, Exception):
239
+ raise result
240
+ else:
241
+ expected_list = next(
242
+ (exp for exp in expected if isinstance(exp, list)), None
243
+ )
244
+ expected_dict = next(
245
+ (exp for exp in expected if isinstance(exp, dict)), None
246
+ )
247
+ if (expected_list, expected_dict) != (None, None):
248
+ self.assertParseResultsEquals(
249
+ result,
250
+ expected_list=expected_list,
251
+ expected_dict=expected_dict,
252
+ msg=fail_msg or msg,
253
+ )
254
+ else:
255
+ # warning here maybe?
256
+ print(f"no validation for {test_string!r}")
257
+
258
+ # do this last, in case some specific test results can be reported instead
259
+ self.assertTrue(
260
+ run_test_success, msg=msg if msg is not None else "failed runTests"
261
+ )
262
+
263
+ @contextmanager
264
+ def assertRaisesParseException(
265
+ self, exc_type=ParseException, expected_msg=None, msg=None
266
+ ):
267
+ if expected_msg is not None:
268
+ if isinstance(expected_msg, str):
269
+ expected_msg = re.escape(expected_msg)
270
+ with self.assertRaisesRegex(exc_type, expected_msg, msg=msg) as ctx:
271
+ yield ctx
272
+
273
+ else:
274
+ with self.assertRaises(exc_type, msg=msg) as ctx:
275
+ yield ctx
276
+
277
+ @staticmethod
278
+ def with_line_numbers(
279
+ s: str,
280
+ start_line: typing.Optional[int] = None,
281
+ end_line: typing.Optional[int] = None,
282
+ expand_tabs: bool = True,
283
+ eol_mark: str = "|",
284
+ mark_spaces: typing.Optional[str] = None,
285
+ mark_control: typing.Optional[str] = None,
286
+ *,
287
+ indent: typing.Union[str, int] = "",
288
+ base_1: bool = True,
289
+ ) -> str:
290
+ """
291
+ Helpful method for debugging a parser - prints a string with line and column numbers.
292
+ (Line and column numbers are 1-based by default - if debugging a parse action,
293
+ pass base_1=False, to correspond to the loc value passed to the parse action.)
294
+
295
+ :param s: string to be printed with line and column numbers
296
+ :param start_line: starting line number in s to print (default=1)
297
+ :param end_line: ending line number in s to print (default=len(s))
298
+ :param expand_tabs: expand tabs to spaces, to match the pyparsing default
299
+ :param eol_mark: string to mark the end of lines, helps visualize trailing spaces
300
+ :param mark_spaces: special character to display in place of spaces
301
+ :param mark_control: convert non-printing control characters to a placeholding
302
+ character; valid values:
303
+
304
+ - ``"unicode"`` - replaces control chars with Unicode symbols, such as "␍" and "␊"
305
+ - any single character string - replace control characters with given string
306
+ - ``None`` (default) - string is displayed as-is
307
+
308
+
309
+ :param indent: string to indent with line and column numbers; if an int
310
+ is passed, converted to ``" " * indent``
311
+ :param base_1: whether to label string using base 1; if False, string will be
312
+ labeled based at 0
313
+
314
+ :returns: input string with leading line numbers and column number headers
315
+
316
+ .. versionchanged:: 3.2.0
317
+ New ``indent`` and ``base_1`` arguments.
318
+ """
319
+ if expand_tabs:
320
+ s = s.expandtabs()
321
+ if isinstance(indent, int):
322
+ indent = " " * indent
323
+ indent = indent.expandtabs()
324
+ if mark_control is not None:
325
+ mark_control = typing.cast(str, mark_control)
326
+ if mark_control == "unicode":
327
+ transtable_map = {
328
+ c: u for c, u in zip(range(0, 33), range(0x2400, 0x2433))
329
+ }
330
+ transtable_map[127] = 0x2421
331
+ tbl = str.maketrans(transtable_map)
332
+ eol_mark = ""
333
+ else:
334
+ ord_mark_control = ord(mark_control)
335
+ tbl = str.maketrans(
336
+ {c: ord_mark_control for c in list(range(0, 32)) + [127]}
337
+ )
338
+ s = s.translate(tbl)
339
+ if mark_spaces is not None and mark_spaces != " ":
340
+ if mark_spaces == "unicode":
341
+ tbl = str.maketrans({9: 0x2409, 32: 0x2423})
342
+ s = s.translate(tbl)
343
+ else:
344
+ s = s.replace(" ", mark_spaces)
345
+ if start_line is None:
346
+ start_line = 0
347
+ if end_line is None:
348
+ end_line = len(s.splitlines())
349
+ end_line = min(end_line, len(s.splitlines()))
350
+ start_line = min(max(0, start_line), end_line)
351
+
352
+ if mark_control != "unicode":
353
+ s_lines = s.splitlines()[max(start_line - base_1, 0) : end_line]
354
+ else:
355
+ s_lines = [
356
+ line + "␊"
357
+ for line in s.split("␊")[max(start_line - base_1, 0) : end_line]
358
+ ]
359
+ if not s_lines:
360
+ return ""
361
+
362
+ lineno_width = len(str(end_line))
363
+ max_line_len = max(len(line) for line in s_lines)
364
+ lead = f"{indent}{' ' * (lineno_width + 1)}"
365
+
366
+ if max_line_len >= 99:
367
+ header0 = (
368
+ lead
369
+ + ("" if base_1 else " ")
370
+ + "".join(
371
+ f"{' ' * 99}{(i + 1) % 100}"
372
+ for i in range(max(max_line_len // 100, 1))
373
+ )
374
+ + "\n"
375
+ )
376
+ else:
377
+ header0 = ""
378
+
379
+ header1 = (
380
+ ("" if base_1 else " ")
381
+ + lead
382
+ + "".join(f" {(i + 1) % 10}" for i in range(-(-max_line_len // 10)))
383
+ + "\n"
384
+ )
385
+ digits = "1234567890"
386
+ header2 = (
387
+ lead + ("" if base_1 else "0") + digits * (-(-max_line_len // 10)) + "\n"
388
+ )
389
+ return (
390
+ header0
391
+ + header1
392
+ + header2
393
+ + "\n".join(
394
+ f"{indent}{i:{lineno_width}d}:{line}{eol_mark}"
395
+ for i, line in enumerate(s_lines, start=start_line + base_1)
396
+ )
397
+ + "\n"
398
+ )
@@ -0,0 +1,142 @@
1
+ import warnings
2
+ from functools import lru_cache
3
+ import pyparsing as pp
4
+
5
+
6
+ @lru_cache(maxsize=None)
7
+ def camel_to_snake(s: str) -> str:
8
+ """
9
+ Convert CamelCase to snake_case.
10
+ """
11
+ return "".join(f"_{c.lower()}" if c.isupper() else c for c in s).lstrip("_")
12
+
13
+
14
+ pre_pep8_method_names = """
15
+ addCondition addParseAction anyCloseTag anyOpenTag asDict asList cStyleComment canParseNext conditionAsParseAction
16
+ convertToDate convertToDatetime convertToFloat convertToInteger countedArray cppStyleComment dblQuotedString
17
+ dblSlashComment defaultName dictOf disableMemoization downcaseTokens enableLeftRecursion enablePackrat getName
18
+ htmlComment ignoreWhitespace infixNotation inlineLiteralsUsing javaStyleComment leaveWhitespace
19
+ lineEnd lineStart matchOnlyAtCol matchPreviousExpr matchPreviousLiteral nestedExpr nullDebugAction oneOf
20
+ originalTextFor parseFile parseString parseWithTabs pythonStyleComment quotedString removeQuotes replaceWith
21
+ resetCache restOfLine runTests scanString searchString setBreak setDebug setDebugActions setDefaultWhitespaceChars
22
+ setFailAction setName setParseAction setResultsName setWhitespaceChars sglQuotedString stringEnd stringStart tokenMap
23
+ traceParseAction transformString tryParse unicodeString upcaseTokens withAttribute withClass
24
+ """.split()
25
+
26
+ special_changes = {
27
+ "opAssoc": "OpAssoc",
28
+ "delimitedList": "DelimitedList",
29
+ "delimited_list": "DelimitedList",
30
+ "replaceHTMLEntity": "replace_html_entity",
31
+ "makeHTMLTags": "make_html_tags",
32
+ "makeXMLTags": "make_xml_tags",
33
+ "commonHTMLEntity": "common_html_entity",
34
+ "stripHTMLTags": "strip_html_tags",
35
+ "indentedBlock": "IndentedBlock",
36
+ "locatedExpr": "Located",
37
+ }
38
+
39
+ pre_pep8_arg_names = """parseAll maxMatches listAllMatches callDuringTry includeSeparators fullDump printResults
40
+ failureTests postParse matchString identChars maxMismatches initChars bodyChars asKeyword excludeChars asGroupList
41
+ asMatch quoteChar escChar escQuote unquoteResults endQuoteChar convertWhitespaceEscapes notChars wordChars stopOn
42
+ failOn joinString markerString intExpr useRegex asString ignoreExpr""".split()
43
+
44
+ special_changes_arg_names = {
45
+ "asList": "aslist",
46
+ }
47
+
48
+ pre_pep8_method_name = pp.one_of(pre_pep8_method_names, as_keyword=True)
49
+ pre_pep8_method_name.set_parse_action(lambda t: camel_to_snake(t[0]))
50
+ special_pre_pep8_name = pp.one_of(special_changes, as_keyword=True)
51
+ def update_special_changes(s, l, t):
52
+ if t[0] == "indentedBlock":
53
+ warnings.warn(
54
+ "Conversion of 'indentedBlock' to new 'IndentedBlock'"
55
+ " requires added code changes to remove 'indentStack' argument\n"
56
+ f" {pp.lineno(l, s)}: {pp.line(l, s)}",
57
+ stacklevel=2,
58
+ )
59
+ elif t[0] == "locatedExpr":
60
+ warnings.warn(
61
+ "Conversion of 'locatedExpr' to new 'Located'"
62
+ " may require added code changes - Located does not automatically"
63
+ " group parsed elements\n"
64
+ f" {pp.lineno(l, s)}: {pp.line(l, s)}",
65
+ stacklevel=2,
66
+ )
67
+ return special_changes[t[0]]
68
+ special_pre_pep8_name.set_parse_action(update_special_changes)
69
+ # only replace arg names if part of an arg list
70
+ pre_pep8_arg_name = pp.Regex(
71
+ rf"{pp.util.make_compressed_re(pre_pep8_arg_names)}"
72
+ ) + pp.FollowedBy("=")
73
+ pre_pep8_arg_name.set_parse_action(lambda t: camel_to_snake(t[0]))
74
+ special_pre_pep8_arg_name = pp.one_of(special_changes_arg_names, as_keyword=True) + pp.FollowedBy("=")
75
+ special_pre_pep8_arg_name.set_parse_action(lambda t: special_changes_arg_names[t[0]])
76
+
77
+ pep8_converter = special_pre_pep8_arg_name | pre_pep8_method_name | special_pre_pep8_name | pre_pep8_arg_name
78
+
79
+ if __name__ == "__main__":
80
+ import argparse
81
+ from pathlib import Path
82
+ import sys
83
+
84
+ argparser = argparse.ArgumentParser(
85
+ description = (
86
+ "Utility to convert Python pyparsing scripts using legacy"
87
+ " camelCase names to use PEP8 snake_case names."
88
+ "\nBy default, this script will only show whether this script would make any changes."
89
+ )
90
+ )
91
+ argparser.add_argument("--verbose", "-v", action="store_true", help="Show unified diff for each source file")
92
+ argparser.add_argument("-vv", action="store_true", dest="verbose2", help="Show unified diff for each source file, plus names of scanned files with no changes")
93
+ argparser.add_argument("--update", "-u", action="store_true", help="Update source files in-place")
94
+ argparser.add_argument("--encoding", type=str, default="utf-8", help="Encoding of source files (default: utf-8)")
95
+ argparser.add_argument("--exit-zero-even-if-changed", "-exit0", action="store_true", help="Exit with status code 0 even if changes were made")
96
+ argparser.add_argument("source_filename", nargs="+", help="Source filenames or filename patterns of Python files to be converted")
97
+ args = argparser.parse_args()
98
+
99
+
100
+ def show_diffs(original, modified):
101
+ import difflib
102
+
103
+ diff = difflib.unified_diff(
104
+ original.splitlines(), modified.splitlines(), lineterm=""
105
+ )
106
+ sys.stdout.writelines(f"{diff_line}\n" for diff_line in diff)
107
+
108
+ exit_status = 0
109
+
110
+ for filename_pattern in args.source_filename:
111
+
112
+ for filename in Path().glob(filename_pattern):
113
+ if not Path(filename).is_file():
114
+ continue
115
+
116
+ try:
117
+ original_contents = Path(filename).read_text(encoding=args.encoding)
118
+ modified_contents = pep8_converter.transform_string(
119
+ original_contents
120
+ )
121
+
122
+ if modified_contents != original_contents:
123
+ if args.update:
124
+ Path(filename).write_text(modified_contents, encoding=args.encoding)
125
+ print(f"Converted {filename}")
126
+ else:
127
+ print(f"Found required changes in {filename}")
128
+
129
+ if args.verbose:
130
+ show_diffs(original_contents, modified_contents)
131
+ print()
132
+
133
+ exit_status = 1
134
+
135
+ else:
136
+ if args.verbose2:
137
+ print(f"No required changes in {filename}")
138
+
139
+ except Exception as e:
140
+ print(f"Failed to convert {filename}: {type(e).__name__}: {e}")
141
+
142
+ sys.exit(exit_status if not args.exit_zero_even_if_changed else 0)