__init__.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. __package__ = 'archivebox.cli'
  2. __command__ = 'archivebox'
  3. import os
  4. import sys
  5. import argparse
  6. from typing import Optional, Dict, List, IO, Union
  7. from pathlib import Path
  8. from ..config import OUTPUT_DIR
  9. from importlib import import_module
  10. CLI_DIR = Path(__file__).resolve().parent
  11. # these common commands will appear sorted before any others for ease-of-use
  12. meta_cmds = ('help', 'version')
  13. main_cmds = ('init', 'info', 'config')
  14. archive_cmds = ('add', 'remove', 'update', 'list', 'status')
  15. fake_db = ("oneshot",)
  16. display_first = (*meta_cmds, *main_cmds, *archive_cmds)
  17. # every imported command module must have these properties in order to be valid
  18. required_attrs = ('__package__', '__command__', 'main')
  19. # basic checks to make sure imported files are valid subcommands
  20. is_cli_module = lambda fname: fname.startswith('archivebox_') and fname.endswith('.py')
  21. is_valid_cli_module = lambda module, subcommand: (
  22. all(hasattr(module, attr) for attr in required_attrs)
  23. and module.__command__.split(' ')[-1] == subcommand
  24. )
  25. def list_subcommands() -> Dict[str, str]:
  26. """find and import all valid archivebox_<subcommand>.py files in CLI_DIR"""
  27. COMMANDS = []
  28. for filename in os.listdir(CLI_DIR):
  29. if is_cli_module(filename):
  30. subcommand = filename.replace('archivebox_', '').replace('.py', '')
  31. module = import_module('.archivebox_{}'.format(subcommand), __package__)
  32. assert is_valid_cli_module(module, subcommand)
  33. COMMANDS.append((subcommand, module.main.__doc__))
  34. globals()[subcommand] = module.main
  35. display_order = lambda cmd: (
  36. display_first.index(cmd[0])
  37. if cmd[0] in display_first else
  38. 100 + len(cmd[0])
  39. )
  40. return dict(sorted(COMMANDS, key=display_order))
  41. def run_subcommand(subcommand: str,
  42. subcommand_args: List[str]=None,
  43. stdin: Optional[IO]=None,
  44. pwd: Union[Path, str, None]=None) -> None:
  45. """Run a given ArchiveBox subcommand with the given list of args"""
  46. if subcommand not in meta_cmds:
  47. from ..config import setup_django
  48. setup_django(in_memory_db=subcommand in fake_db, check_db=subcommand in archive_cmds)
  49. module = import_module('.archivebox_{}'.format(subcommand), __package__)
  50. module.main(args=subcommand_args, stdin=stdin, pwd=pwd) # type: ignore
  51. SUBCOMMANDS = list_subcommands()
  52. class NotProvided:
  53. pass
  54. def main(args: Optional[List[str]]=NotProvided, stdin: Optional[IO]=NotProvided, pwd: Optional[str]=None) -> None:
  55. args = sys.argv[1:] if args is NotProvided else args
  56. stdin = sys.stdin if stdin is NotProvided else stdin
  57. subcommands = list_subcommands()
  58. parser = argparse.ArgumentParser(
  59. prog=__command__,
  60. description='ArchiveBox: The self-hosted internet archive',
  61. add_help=False,
  62. )
  63. group = parser.add_mutually_exclusive_group()
  64. group.add_argument(
  65. '--help', '-h',
  66. action='store_true',
  67. help=subcommands['help'],
  68. )
  69. group.add_argument(
  70. '--version',
  71. action='store_true',
  72. help=subcommands['version'],
  73. )
  74. group.add_argument(
  75. "subcommand",
  76. type=str,
  77. help= "The name of the subcommand to run",
  78. nargs='?',
  79. choices=subcommands.keys(),
  80. default=None,
  81. )
  82. parser.add_argument(
  83. "subcommand_args",
  84. help="Arguments for the subcommand",
  85. nargs=argparse.REMAINDER,
  86. )
  87. command = parser.parse_args(args or ())
  88. if command.version:
  89. command.subcommand = 'version'
  90. elif command.help or command.subcommand is None:
  91. command.subcommand = 'help'
  92. if command.subcommand not in ('help', 'version', 'status'):
  93. from ..logging_util import log_cli_command
  94. log_cli_command(
  95. subcommand=command.subcommand,
  96. subcommand_args=command.subcommand_args,
  97. stdin=stdin,
  98. pwd=pwd or OUTPUT_DIR
  99. )
  100. run_subcommand(
  101. subcommand=command.subcommand,
  102. subcommand_args=command.subcommand_args,
  103. stdin=stdin,
  104. pwd=pwd or OUTPUT_DIR,
  105. )
  106. __all__ = (
  107. 'SUBCOMMANDS',
  108. 'list_subcommands',
  109. 'run_subcommand',
  110. *SUBCOMMANDS.keys(),
  111. )