edq.clilib.list

Show the CLI tools available in this package.

  1"""
  2Show the CLI tools available in this package.
  3"""
  4
  5import argparse
  6import inspect
  7import os
  8
  9import edq.clilib.model
 10import edq.util.dirent
 11import edq.util.pyimport
 12
 13def auto_list(
 14        recursive: bool = False,
 15        skip_dirs: bool = False,
 16        ) -> None:
 17    """
 18    Print the caller's docstring and call _list_dir() on it,
 19    but will figure out the package's docstring, base_dir, and command_prefix automatically.
 20    This will use the inspect library, so only use in places that use code normally.
 21    The first stack frame not in this file will be used.
 22    """
 23
 24    this_path = os.path.realpath(__file__)
 25
 26    caller_frame_info = None
 27    for frame_info in inspect.stack():
 28        if (edq.util.dirent.same(this_path, frame_info.filename)):
 29            # Ignore this file.
 30            continue
 31
 32        caller_frame_info = frame_info
 33        break
 34
 35    if (caller_frame_info is None):
 36        raise ValueError("Unable to determine caller's stack frame.")
 37
 38    path = caller_frame_info.filename
 39    base_dir = os.path.dirname(path)
 40
 41    try:
 42        module = inspect.getmodule(caller_frame_info.frame)
 43        if (module is None):
 44            raise ValueError(f"Unable to get module for '{path}'.")
 45    except Exception as ex:
 46        raise ValueError("Unable to get caller information for listing CLI information.") from ex
 47
 48    if (module.__package__ is None):
 49        raise ValueError(f"Caller module has no package information: '{path}'.")
 50
 51    package = edq.clilib.model.CLIPackage.from_path(base_dir, module.__package__)
 52    if (package is None):
 53        raise ValueError(f"Caller package is not a CLI package: '{base_dir}'.")
 54
 55    print(package.get_description())
 56    _list_dir(package, recursive, skip_dirs)
 57
 58def _list_dir(package: edq.clilib.model.CLIPackage, recursive: bool, skip_dirs: bool) -> None:
 59    """
 60    List/descend the given dir.
 61    Don't output information out this directory itself, just the entries.
 62    """
 63
 64    for dirent in package.dirents:
 65        if (isinstance(dirent, edq.clilib.model.CLIModule)):
 66            _handle_module(dirent)
 67        elif (isinstance(dirent, edq.clilib.model.CLIPackage)):
 68            if (not skip_dirs):
 69                _handle_package(dirent)
 70
 71            if (recursive):
 72                _list_dir(dirent, recursive, skip_dirs)
 73
 74def _handle_module(module: edq.clilib.model.CLIModule) -> None:
 75    """ Process a module. """
 76
 77    print()
 78    print(module.qualified_name)
 79    print(module.get_description())
 80    print(module.get_usage_text())
 81
 82def _handle_package(package: edq.clilib.model.CLIPackage) -> None:
 83    """ Process a package. """
 84
 85    print()
 86    print(package.qualified_name + '.*')
 87    print(package.get_description())
 88    print(f"See `python3 -m {package.qualified_name}` for more information.")
 89
 90def _get_parser() -> argparse.ArgumentParser:
 91    parser = argparse.ArgumentParser(
 92        description = __doc__.strip(),
 93        epilog = ("Note that you don't need to provide a package as an argument,"
 94            + " since you already called this on the target package."))
 95
 96    parser.add_argument('-r', '--recursive', dest = 'recursive',
 97        action = 'store_true', default = False,
 98        help = 'Recur into each package to look for tools and subpackages (default: %(default)s).')
 99
100    parser.add_argument('-s', '--skip-dirs', dest = 'skip_dirs',
101        action = 'store_true', default = False,
102        help = ('Do not output information about directories/packages,'
103            + ' only tools/files/modules (default: %(default)s).'))
104
105    return parser
106
107def run_cli(args: argparse.Namespace) -> int:
108    """
109    List the caller's dir.
110    """
111
112    auto_list(recursive = args.recursive, skip_dirs = args.skip_dirs)
113
114    return 0
115
116def main() -> int:
117    """
118    Run as if this process has been called as a executable.
119    This will parse the command line and list the caller's dir.
120    """
121
122    return run_cli(_get_parser().parse_args())
def auto_list(recursive: bool = False, skip_dirs: bool = False) -> None:
14def auto_list(
15        recursive: bool = False,
16        skip_dirs: bool = False,
17        ) -> None:
18    """
19    Print the caller's docstring and call _list_dir() on it,
20    but will figure out the package's docstring, base_dir, and command_prefix automatically.
21    This will use the inspect library, so only use in places that use code normally.
22    The first stack frame not in this file will be used.
23    """
24
25    this_path = os.path.realpath(__file__)
26
27    caller_frame_info = None
28    for frame_info in inspect.stack():
29        if (edq.util.dirent.same(this_path, frame_info.filename)):
30            # Ignore this file.
31            continue
32
33        caller_frame_info = frame_info
34        break
35
36    if (caller_frame_info is None):
37        raise ValueError("Unable to determine caller's stack frame.")
38
39    path = caller_frame_info.filename
40    base_dir = os.path.dirname(path)
41
42    try:
43        module = inspect.getmodule(caller_frame_info.frame)
44        if (module is None):
45            raise ValueError(f"Unable to get module for '{path}'.")
46    except Exception as ex:
47        raise ValueError("Unable to get caller information for listing CLI information.") from ex
48
49    if (module.__package__ is None):
50        raise ValueError(f"Caller module has no package information: '{path}'.")
51
52    package = edq.clilib.model.CLIPackage.from_path(base_dir, module.__package__)
53    if (package is None):
54        raise ValueError(f"Caller package is not a CLI package: '{base_dir}'.")
55
56    print(package.get_description())
57    _list_dir(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:
108def run_cli(args: argparse.Namespace) -> int:
109    """
110    List the caller's dir.
111    """
112
113    auto_list(recursive = args.recursive, skip_dirs = args.skip_dirs)
114
115    return 0

List the caller's dir.

def main() -> int:
117def main() -> int:
118    """
119    Run as if this process has been called as a executable.
120    This will parse the command line and list the caller's dir.
121    """
122
123    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.