edq.util.cli
Show the CLI tools available in this package.
This will look for objects that "look like" CLI tools. A package looks like a CLI package if it has a __main__.py file. A module looks like a CLI tool if it has the following functions:
def _get_parser() -> argparse.ArgumentParser:def run_cli(args: argparse.Namespace) -> int:
1""" 2Show the CLI tools available in this package. 3 4This will look for objects that "look like" CLI tools. 5A package looks like a CLI package if it has a __main__.py file. 6A module looks like a CLI tool if it has the following functions: 7 - `def _get_parser() -> argparse.ArgumentParser:` 8 - `def run_cli(args: argparse.Namespace) -> int:` 9""" 10 11import argparse 12import inspect 13import os 14 15import edq.util.dirent 16import edq.util.pyimport 17 18def auto_list( 19 recursive: bool = False, 20 skip_dirs: bool = False, 21 ) -> None: 22 """ 23 Print the caller's docstring and call _list_dir() on it, 24 but will figure out the package's docstring, base_dir, and command_prefix automatically. 25 This will use the inspect library, so only use in places that use code normally. 26 The first stack frame not in this file will be used. 27 """ 28 29 this_path = os.path.realpath(__file__) 30 31 caller_frame_info = None 32 for frame_info in inspect.stack(): 33 if (edq.util.dirent.same(this_path, frame_info.filename)): 34 # Ignore this file. 35 continue 36 37 caller_frame_info = frame_info 38 break 39 40 if (caller_frame_info is None): 41 raise ValueError("Unable to determine caller's stack frame.") 42 43 path = caller_frame_info.filename 44 base_dir = os.path.dirname(path) 45 46 try: 47 module = inspect.getmodule(caller_frame_info.frame) 48 if (module is None): 49 raise ValueError(f"Unable to get module for '{path}'.") 50 except Exception as ex: 51 raise ValueError("Unable to get caller information for listing CLI information.") from ex 52 53 if (module.__package__ is None): 54 raise ValueError(f"Caller module has no package information: '{path}'.") 55 56 if (module.__doc__ is None): 57 raise ValueError(f"Caller module has no docstring: '{path}'.") 58 59 print(module.__doc__.strip()) 60 _list_dir(base_dir, module.__package__, recursive, skip_dirs) 61 62def _list_dir(base_dir: str, command_prefix: str, recursive: bool, skip_dirs: bool) -> None: 63 """ List/descend the given dir. """ 64 65 for dirent in sorted(os.listdir(base_dir)): 66 path = os.path.join(base_dir, dirent) 67 cmd = command_prefix + '.' + os.path.splitext(dirent)[0] 68 69 if (dirent.startswith('__')): 70 continue 71 72 if (os.path.isfile(path)): 73 _handle_file(path, cmd) 74 else: 75 if (not skip_dirs): 76 _handle_dir(path, cmd) 77 78 if (recursive): 79 _list_dir(path, cmd, recursive, skip_dirs) 80 81def _handle_file(path: str, cmd: str) -> None: 82 """ Process a file (possible module). """ 83 84 if (not path.endswith('.py')): 85 return 86 87 try: 88 module = edq.util.pyimport.import_path(path) 89 except Exception: 90 print("ERROR Importing: ", path) 91 return 92 93 if ('_get_parser' not in dir(module)): 94 return 95 96 parser = module._get_parser() 97 parser.prog = 'python3 -m ' + cmd 98 99 print() 100 print(cmd) 101 print(parser.description) 102 parser.print_usage() 103 104def _handle_dir(path: str, cmd: str) -> None: 105 """ Process a dir (possible package). """ 106 107 try: 108 module = edq.util.pyimport.import_path(os.path.join(path, '__main__.py')) 109 except Exception: 110 return 111 112 description = module.__doc__.strip() 113 114 print() 115 print(cmd + '.*') 116 print(description) 117 print(f"See `python3 -m {cmd}` for more information.") 118 119def _get_parser() -> argparse.ArgumentParser: 120 parser = argparse.ArgumentParser( 121 description = __doc__.strip(), 122 epilog = ("Note that you don't need to provide a package as an argument," 123 + " since you already called this on the target package.")) 124 125 parser.add_argument('-r', '--recursive', dest = 'recursive', 126 action = 'store_true', default = False, 127 help = 'Recur into each package to look for tools and subpackages (default: %(default)s).') 128 129 parser.add_argument('-s', '--skip-dirs', dest = 'skip_dirs', 130 action = 'store_true', default = False, 131 help = ('Do not output information about directories/packages,' 132 + ' only tools/files/modules (default: %(default)s).')) 133 134 return parser 135 136def run_cli(args: argparse.Namespace) -> int: 137 """ 138 List the caller's dir. 139 """ 140 141 auto_list(recursive = args.recursive, skip_dirs = args.skip_dirs) 142 143 return 0 144 145def main() -> int: 146 """ 147 Run as if this process has been called as a executable. 148 This will parse the command line and list the caller's dir. 149 """ 150 151 return run_cli(_get_parser().parse_args())
def
auto_list(recursive: bool = False, skip_dirs: bool = False) -> None:
19def auto_list( 20 recursive: bool = False, 21 skip_dirs: bool = False, 22 ) -> None: 23 """ 24 Print the caller's docstring and call _list_dir() on it, 25 but will figure out the package's docstring, base_dir, and command_prefix automatically. 26 This will use the inspect library, so only use in places that use code normally. 27 The first stack frame not in this file will be used. 28 """ 29 30 this_path = os.path.realpath(__file__) 31 32 caller_frame_info = None 33 for frame_info in inspect.stack(): 34 if (edq.util.dirent.same(this_path, frame_info.filename)): 35 # Ignore this file. 36 continue 37 38 caller_frame_info = frame_info 39 break 40 41 if (caller_frame_info is None): 42 raise ValueError("Unable to determine caller's stack frame.") 43 44 path = caller_frame_info.filename 45 base_dir = os.path.dirname(path) 46 47 try: 48 module = inspect.getmodule(caller_frame_info.frame) 49 if (module is None): 50 raise ValueError(f"Unable to get module for '{path}'.") 51 except Exception as ex: 52 raise ValueError("Unable to get caller information for listing CLI information.") from ex 53 54 if (module.__package__ is None): 55 raise ValueError(f"Caller module has no package information: '{path}'.") 56 57 if (module.__doc__ is None): 58 raise ValueError(f"Caller module has no docstring: '{path}'.") 59 60 print(module.__doc__.strip()) 61 _list_dir(base_dir, module.__package__, recursive, skip_dirs)
Print the caller's docstring and call _list_dir() on it, but will figure out the package's docstring, base_dir, and command_prefix automatically. This will use the inspect library, so only use in places that use code normally. The first stack frame not in this file will be used.
def
run_cli(args: argparse.Namespace) -> int:
137def run_cli(args: argparse.Namespace) -> int: 138 """ 139 List the caller's dir. 140 """ 141 142 auto_list(recursive = args.recursive, skip_dirs = args.skip_dirs) 143 144 return 0
List the caller's dir.
def
main() -> int:
146def main() -> int: 147 """ 148 Run as if this process has been called as a executable. 149 This will parse the command line and list the caller's dir. 150 """ 151 152 return run_cli(_get_parser().parse_args())
Run as if this process has been called as a executable. This will parse the command line and list the caller's dir.