cli.py 2.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. #!/usr/bin/env python3
  2. # Copyright 2023 The ChromiumOS Authors
  3. # Use of this source code is governed by a BSD-style license that can be
  4. # found in the LICENSE file.
  5. """
  6. Provides a framework for command line interfaces based on argh.
  7. It automatically adds common arguments, such as -v, -vv and --color to provide consistent
  8. behavior.
  9. """
  10. import argparse
  11. import sys
  12. import traceback
  13. from typing import (
  14. Any,
  15. Callable,
  16. Optional,
  17. )
  18. from .util import (
  19. add_common_args,
  20. parse_common_args,
  21. print_timing_info,
  22. record_time,
  23. verbose,
  24. ensure_packages_exist,
  25. )
  26. ensure_packages_exist("argh")
  27. import argh # type: ignore
  28. # Hack: argh does not support type annotations. This prevents type errors.
  29. argh: Any # type: ignore
  30. def run_main(main_fn: Callable[..., Any], usage: Optional[str] = None):
  31. run_commands(default_fn=main_fn, usage=usage)
  32. def run_commands(
  33. *functions: Callable[..., Any],
  34. default_fn: Optional[Callable[..., Any]] = None,
  35. usage: Optional[str] = None,
  36. ):
  37. """
  38. Allow the user to call the provided functions with command line arguments translated to
  39. function arguments via argh: https://pythonhosted.org/argh
  40. """
  41. exit_code = 0
  42. try:
  43. parser = argparse.ArgumentParser(
  44. description=usage,
  45. # Docstrings are used as the description in argparse, preserve their formatting.
  46. formatter_class=argparse.RawDescriptionHelpFormatter,
  47. # Do not allow implied abbreviations. Abbreviations should be manually specified.
  48. allow_abbrev=False,
  49. )
  50. add_common_args(parser)
  51. # Add provided commands to parser. Do not use sub-commands if we just got one function.
  52. if functions:
  53. argh.add_commands(parser, functions) # type: ignore
  54. if default_fn:
  55. argh.set_default_command(parser, default_fn) # type: ignore
  56. with record_time("Total Time"):
  57. # Call main method
  58. argh.dispatch(parser) # type: ignore
  59. except Exception as e:
  60. if verbose():
  61. traceback.print_exc()
  62. else:
  63. print(e)
  64. exit_code = 1
  65. if parse_common_args().timing_info:
  66. print_timing_info()
  67. sys.exit(exit_code)
  68. if __name__ == "__main__":
  69. import doctest
  70. (failures, num_tests) = doctest.testmod(optionflags=doctest.ELLIPSIS)
  71. sys.exit(1 if failures > 0 else 0)