| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142 |
- import warnings
- from functools import lru_cache
- import pyparsing as pp
- @lru_cache(maxsize=None)
- def camel_to_snake(s: str) -> str:
- """
- Convert CamelCase to snake_case.
- """
- return "".join(f"_{c.lower()}" if c.isupper() else c for c in s).lstrip("_")
- pre_pep8_method_names = """
- addCondition addParseAction anyCloseTag anyOpenTag asDict asList cStyleComment canParseNext conditionAsParseAction
- convertToDate convertToDatetime convertToFloat convertToInteger countedArray cppStyleComment dblQuotedString
- dblSlashComment defaultName dictOf disableMemoization downcaseTokens enableLeftRecursion enablePackrat getName
- htmlComment ignoreWhitespace infixNotation inlineLiteralsUsing javaStyleComment leaveWhitespace
- lineEnd lineStart matchOnlyAtCol matchPreviousExpr matchPreviousLiteral nestedExpr nullDebugAction oneOf
- originalTextFor parseFile parseString parseWithTabs pythonStyleComment quotedString removeQuotes replaceWith
- resetCache restOfLine runTests scanString searchString setBreak setDebug setDebugActions setDefaultWhitespaceChars
- setFailAction setName setParseAction setResultsName setWhitespaceChars sglQuotedString stringEnd stringStart tokenMap
- traceParseAction transformString tryParse unicodeString upcaseTokens withAttribute withClass
- """.split()
- special_changes = {
- "opAssoc": "OpAssoc",
- "delimitedList": "DelimitedList",
- "delimited_list": "DelimitedList",
- "replaceHTMLEntity": "replace_html_entity",
- "makeHTMLTags": "make_html_tags",
- "makeXMLTags": "make_xml_tags",
- "commonHTMLEntity": "common_html_entity",
- "stripHTMLTags": "strip_html_tags",
- "indentedBlock": "IndentedBlock",
- "locatedExpr": "Located",
- }
- pre_pep8_arg_names = """parseAll maxMatches listAllMatches callDuringTry includeSeparators fullDump printResults
- failureTests postParse matchString identChars maxMismatches initChars bodyChars asKeyword excludeChars asGroupList
- asMatch quoteChar escChar escQuote unquoteResults endQuoteChar convertWhitespaceEscapes notChars wordChars stopOn
- failOn joinString markerString intExpr useRegex asString ignoreExpr""".split()
- special_changes_arg_names = {
- "asList": "aslist",
- }
- pre_pep8_method_name = pp.one_of(pre_pep8_method_names, as_keyword=True)
- pre_pep8_method_name.set_parse_action(lambda t: camel_to_snake(t[0]))
- special_pre_pep8_name = pp.one_of(special_changes, as_keyword=True)
- def update_special_changes(s, l, t):
- if t[0] == "indentedBlock":
- warnings.warn(
- "Conversion of 'indentedBlock' to new 'IndentedBlock'"
- " requires added code changes to remove 'indentStack' argument\n"
- f" {pp.lineno(l, s)}: {pp.line(l, s)}",
- stacklevel=2,
- )
- elif t[0] == "locatedExpr":
- warnings.warn(
- "Conversion of 'locatedExpr' to new 'Located'"
- " may require added code changes - Located does not automatically"
- " group parsed elements\n"
- f" {pp.lineno(l, s)}: {pp.line(l, s)}",
- stacklevel=2,
- )
- return special_changes[t[0]]
- special_pre_pep8_name.set_parse_action(update_special_changes)
- # only replace arg names if part of an arg list
- pre_pep8_arg_name = pp.Regex(
- rf"{pp.util.make_compressed_re(pre_pep8_arg_names)}"
- ) + pp.FollowedBy("=")
- pre_pep8_arg_name.set_parse_action(lambda t: camel_to_snake(t[0]))
- special_pre_pep8_arg_name = pp.one_of(special_changes_arg_names, as_keyword=True) + pp.FollowedBy("=")
- special_pre_pep8_arg_name.set_parse_action(lambda t: special_changes_arg_names[t[0]])
- pep8_converter = special_pre_pep8_arg_name | pre_pep8_method_name | special_pre_pep8_name | pre_pep8_arg_name
- if __name__ == "__main__":
- import argparse
- from pathlib import Path
- import sys
- argparser = argparse.ArgumentParser(
- description = (
- "Utility to convert Python pyparsing scripts using legacy"
- " camelCase names to use PEP8 snake_case names."
- "\nBy default, this script will only show whether this script would make any changes."
- )
- )
- argparser.add_argument("--verbose", "-v", action="store_true", help="Show unified diff for each source file")
- 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")
- argparser.add_argument("--update", "-u", action="store_true", help="Update source files in-place")
- argparser.add_argument("--encoding", type=str, default="utf-8", help="Encoding of source files (default: utf-8)")
- argparser.add_argument("--exit-zero-even-if-changed", "-exit0", action="store_true", help="Exit with status code 0 even if changes were made")
- argparser.add_argument("source_filename", nargs="+", help="Source filenames or filename patterns of Python files to be converted")
- args = argparser.parse_args()
- def show_diffs(original, modified):
- import difflib
- diff = difflib.unified_diff(
- original.splitlines(), modified.splitlines(), lineterm=""
- )
- sys.stdout.writelines(f"{diff_line}\n" for diff_line in diff)
- exit_status = 0
- for filename_pattern in args.source_filename:
- for filename in Path().glob(filename_pattern):
- if not Path(filename).is_file():
- continue
- try:
- original_contents = Path(filename).read_text(encoding=args.encoding)
- modified_contents = pep8_converter.transform_string(
- original_contents
- )
- if modified_contents != original_contents:
- if args.update:
- Path(filename).write_text(modified_contents, encoding=args.encoding)
- print(f"Converted {filename}")
- else:
- print(f"Found required changes in {filename}")
- if args.verbose:
- show_diffs(original_contents, modified_contents)
- print()
- exit_status = 1
- else:
- if args.verbose2:
- print(f"No required changes in {filename}")
- except Exception as e:
- print(f"Failed to convert {filename}: {type(e).__name__}: {e}")
- sys.exit(exit_status if not args.exit_zero_even_if_changed else 0)
|