cli.py
1 """Module that contains the command line application.""" 2 3 # Why does this file exist, and why not put this in `__main__`? 4 # 5 # You might be tempted to import things from `__main__` later, 6 # but that will cause problems: the code will get executed twice: 7 # 8 # - When you run `python -m docstrings2pep727` python will execute 9 # `__main__.py` as a script. That means there won't be any 10 # `docstrings2pep727.__main__` in `sys.modules`. 11 # - When you import `__main__` it will get executed again (as a module) because 12 # there's no `docstrings2pep727.__main__` in `sys.modules`. 13 14 from __future__ import annotations 15 16 import argparse 17 import sys 18 from pathlib import Path 19 from typing import TYPE_CHECKING, Any 20 21 import libcst as cst 22 from griffe.agents.visitor import visit 23 from griffe.docstrings import Parser 24 25 from docstrings2pep727.transformer import PEP727Transformer 26 27 if TYPE_CHECKING: 28 from griffe import Docstring, Object 29 30 31 from docstrings2pep727 import debug 32 33 34 class _DebugInfo(argparse.Action): 35 def __init__(self, nargs: int | str | None = 0, **kwargs: Any) -> None: 36 super().__init__(nargs=nargs, **kwargs) 37 38 def __call__(self, *args: Any, **kwargs: Any) -> None: # noqa: ARG002 39 debug.print_debug_info() 40 sys.exit(0) 41 42 43 def _docstrings(obj: Object, store: dict | None = None) -> dict[str, Docstring]: 44 if store is None: 45 store = {} 46 if obj.docstring: 47 store[obj.path] = obj.docstring 48 for member in obj.members.values(): 49 if not member.is_alias: 50 _docstrings(member, store) # type: ignore[arg-type] 51 return store 52 53 54 def get_parser() -> argparse.ArgumentParser: 55 """Return the CLI argument parser. 56 57 Returns: 58 An argparse parser. 59 """ 60 parser = argparse.ArgumentParser(prog="docstrings2pep727") 61 parser.add_argument("module", help="Module to transform.") 62 parser.add_argument("-V", "--version", action="version", version=f"%(prog)s {debug.get_version()}") 63 parser.add_argument("--debug-info", action=_DebugInfo, help="Print debug information.") 64 return parser 65 66 67 def main(args: list[str] | None = None) -> int: 68 """Run the main program. 69 70 This function is executed when you type `docstrings2pep727` or `python -m docstrings2pep727`. 71 72 Parameters: 73 args: Arguments passed from the command line. 74 75 Returns: 76 An exit code. 77 """ 78 parser = get_parser() 79 opts = parser.parse_args(args=args) 80 81 module_path = Path(opts.module) 82 module_code = module_path.read_text() 83 module_data = visit(module_path.stem, module_path, module_code, docstring_parser=Parser("google")) 84 docstrings = _docstrings(module_data) 85 86 source_tree = cst.parse_module(module_code) 87 transformer = PEP727Transformer(source_tree, module_path.stem, docstrings) 88 modified_tree = source_tree.visit(transformer) 89 print(modified_tree.code) 90 return 0