Advertisement
Guest User

Pants sets.py script

a guest
Aug 21st, 2018
110
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 5.81 KB | None | 0 0
  1. #!/usr/bin/env python3
  2.  
  3. """Update sets to use literal style when possible.
  4.  
  5. * ./sets.py folder  -> preview which files in folder need changes
  6. * ./sets.py folder file.py [file2.py..] -> update file(s)
  7. """
  8.  
  9. import argparse
  10. import subprocess
  11. import re
  12. from textwrap import dedent
  13.  
  14. from typing import List, NamedTuple
  15.  
  16.  
  17. def main() -> None:
  18.   parser = create_parser()
  19.   args = parser.parse_args()
  20.   if not args.file_names:
  21.     target_root = determine_target_root(args.folder, args.test, args.contrib)
  22.     check_what_needs_changes(target_root, args.root_only)
  23.     return
  24.   for file_name in args.file_names:
  25.     path = determine_path(args, file_name)
  26.     old_usages = check_usages(path)
  27.     prompt_review_of_file(file_name)
  28.     for usage in old_usages:
  29.       review(path, usage)
  30.  
  31.  
  32. # --------------------------------------------------
  33. # Command line utils
  34. # -------------------------------------------------
  35.  
  36. def get_stdout(command: List[str]) -> str:
  37.   return subprocess.run(
  38.     command,
  39.     stdout=subprocess.PIPE,
  40.     encoding='utf-8') \
  41.     .stdout.strip()
  42.  
  43.  
  44. def get_stderr(command: List[str]) -> str:
  45.   return subprocess.run(
  46.     command,
  47.     stderr=subprocess.PIPE,
  48.     encoding='utf-8') \
  49.     .stderr.strip()
  50.  
  51.  
  52. # --------------------------------------------------
  53. # Setup
  54. # -------------------------------------------------
  55.  
  56. def create_parser() -> argparse.ArgumentParser:
  57.   parser = argparse.ArgumentParser()
  58.   parser.add_argument('folder')
  59.   parser.add_argument('file_names', nargs='*', default=[])
  60.   parser.add_argument('-t', '--test', action='store_true')
  61.   parser.add_argument('-r', '--root-only', action='store_true')
  62.   parser.add_argument('-c', '--contrib', action='store_true')
  63.   return parser
  64.  
  65.  
  66. SRC_BASE_ROOT = 'src/python/pants'
  67. TEST_BASE_ROOT = 'tests/python/pants_test'
  68.  
  69.  
  70. def determine_path(args, file_name: str) -> str:
  71.   target_root = determine_target_root(args.folder, args.test, args.contrib)
  72.   return f'{target_root}/{file_name}'
  73.  
  74.  
  75. def determine_target_root(folder: str, is_test: bool, is_contrib: bool) -> str:
  76.   if is_contrib:
  77.     target_folder_root = folder.split('/')[0]
  78.     base_root = (f'contrib/{target_folder_root}/{TEST_BASE_ROOT}/contrib'
  79.                  if is_test
  80.                  else f'contrib/{target_folder_root}/{SRC_BASE_ROOT}/contrib')
  81.   else:
  82.     base_root = TEST_BASE_ROOT if is_test else SRC_BASE_ROOT
  83.   return f'{base_root}/{folder}' if folder else base_root
  84.  
  85.  
  86. # --------------------------------------------------
  87. # Grep
  88. # -------------------------------------------------
  89.  
  90. REGEX = r'set\(\['
  91.  
  92.  
  93. def check_what_needs_changes(folder_root: str, root_only: bool) -> None:
  94.   target = f"{folder_root}/*.py" if root_only else f"{folder_root}/**/*.py"
  95.   grep_output = get_stdout(['rg', '-l', REGEX, '-g', target]).split('\n')
  96.   remove_unnecessary = [p for p in grep_output if p]
  97.   if not remove_unnecessary:
  98.     print('No verbose set constructor usages 🐍 🎉')
  99.     return
  100.   pretty_printed = format_for_cli(remove_unnecessary, root_only)
  101.   print(pretty_printed)
  102.  
  103.  
  104. def format_for_cli(file_paths: List[str], root_only: bool) -> str:
  105.   def drop_prefix(line: str) -> str:
  106.     return (line.split(f'{TEST_BASE_ROOT}/')[1]
  107.             if TEST_BASE_ROOT in line
  108.             else line.split(f'{SRC_BASE_ROOT}/')[1])
  109.   remove_path_prefix = [drop_prefix(line) for line in file_paths]
  110.  
  111.   if 'contrib' in file_paths[0]:
  112.     remove_path_prefix = [line.split('contrib/')[1] for line in remove_path_prefix]
  113.   formatted_for_cli = ([f"{line.split('/')[-1]}" for line in remove_path_prefix]
  114.                        if root_only
  115.                        else [f"{'/'.join(line.split('/')[:-1])} {line.split('/')[-1]}" for line in remove_path_prefix])
  116.   delimiter = '\n' if not root_only else ' '
  117.   return delimiter.join(sorted(formatted_for_cli))
  118.  
  119.  
  120.  
  121. # --------------------------------------------------
  122. # Review usage
  123. # -------------------------------------------------
  124.  
  125. class Usage(NamedTuple):
  126.   line: str
  127.   line_no: int
  128.  
  129.  
  130. def check_usages(file_path: str) -> List[Usage]:
  131.   rg_search = get_stdout(['rg', REGEX, file_path]).split('\n')
  132.   stripped_rg_search = [line.strip() for line in rg_search]
  133.   with open(file_path, 'r') as f:
  134.     lines = f.readlines()
  135.     stripped_lines = [line.strip() for line in lines]
  136.   return [Usage(line, stripped_lines.index(line)) for line in stripped_rg_search]
  137.  
  138.  
  139. def prompt_review_of_file(file_path: str) -> None:
  140.   print(dedent(f"""\
  141.  ----------------------------------------------------------------------
  142.  
  143.  Beginning review of {file_path}.
  144.  
  145.  For every usage, input `y` to accept the script's fix or `n` to reject it and move on.
  146.  
  147.  ----------------------------------------------------------------------
  148.  
  149.  """))
  150.  
  151.  
  152. def review(file_path: str, usage: Usage) -> None:
  153.   new_line = generate_fix(usage.line)
  154.   print(f'Original ({usage.line_no + 1}): {usage.line}')
  155.   print(f'Proposed fix: {new_line}')
  156.   answer = input()
  157.   if answer == 'y':
  158.     rewrite(file_path, new_line, usage.line_no)
  159.   print('\n')
  160.  
  161.  
  162. def _generate_frozenset_fix(line: str) -> str:
  163.   regex = r'\(\[(?P<literal>.*)\]\)'
  164.   return re.sub(regex, '({\g<literal>})', line)
  165.  
  166.  
  167. def _generate_set_fix(line: str) -> str:
  168.   regex = r'set\(\[(?P<literal>.*)\]\)'
  169.   return re.sub(regex, '{\g<literal>}', line)
  170.  
  171.  
  172. def generate_fix(line: str) -> str:
  173.   return (_generate_set_fix(line)
  174.           if 'frozenset' not in line
  175.           else _generate_frozenset_fix(line))
  176.  
  177.  
  178. def rewrite(file_path: str, new_line: str, line_no: int) -> None:
  179.   with open(file_path, 'r') as f:
  180.     lines = list(f.readlines())
  181.   white_space = re.match(r' *', lines[line_no]).group(0)
  182.   lines[line_no] = f'{white_space}{new_line}\n'
  183.   with open(file_path, 'w') as f:
  184.     f.writelines(lines)
  185.  
  186.  
  187. if __name__ == '__main__':
  188.   try:
  189.     main()
  190.   except KeyboardInterrupt:
  191.     pass
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement