__init__.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  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, check_data_folder, check_migrations
  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') # dont require valid data folder at all
  13. main_cmds = ('init', 'config', 'setup') # dont require existing db present
  14. archive_cmds = ('add', 'remove', 'update', 'list', 'status') # require existing db present
  15. fake_db = ("oneshot",) # use fake in-memory db
  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. subcommand_args = subcommand_args or []
  47. if subcommand not in meta_cmds:
  48. from ..config import setup_django
  49. cmd_requires_db = subcommand in archive_cmds
  50. init_pending = '--init' in subcommand_args or '--quick-init' in subcommand_args
  51. if cmd_requires_db:
  52. check_data_folder(pwd)
  53. setup_django(in_memory_db=subcommand in fake_db, check_db=cmd_requires_db and not init_pending)
  54. if cmd_requires_db:
  55. check_migrations()
  56. module = import_module('.archivebox_{}'.format(subcommand), __package__)
  57. module.main(args=subcommand_args, stdin=stdin, pwd=pwd) # type: ignore
  58. SUBCOMMANDS = list_subcommands()
  59. class NotProvided:
  60. pass
  61. def main(args: Optional[List[str]]=NotProvided, stdin: Optional[IO]=NotProvided, pwd: Optional[str]=None) -> None:
  62. args = sys.argv[1:] if args is NotProvided else args
  63. stdin = sys.stdin if stdin is NotProvided else stdin
  64. subcommands = list_subcommands()
  65. parser = argparse.ArgumentParser(
  66. prog=__command__,
  67. description='ArchiveBox: The self-hosted internet archive',
  68. add_help=False,
  69. )
  70. group = parser.add_mutually_exclusive_group()
  71. group.add_argument(
  72. '--help', '-h',
  73. action='store_true',
  74. help=subcommands['help'],
  75. )
  76. group.add_argument(
  77. '--version',
  78. action='store_true',
  79. help=subcommands['version'],
  80. )
  81. group.add_argument(
  82. "subcommand",
  83. type=str,
  84. help= "The name of the subcommand to run",
  85. nargs='?',
  86. choices=subcommands.keys(),
  87. default=None,
  88. )
  89. parser.add_argument(
  90. "subcommand_args",
  91. help="Arguments for the subcommand",
  92. nargs=argparse.REMAINDER,
  93. )
  94. command = parser.parse_args(args or ())
  95. if command.version:
  96. command.subcommand = 'version'
  97. elif command.help or command.subcommand is None:
  98. command.subcommand = 'help'
  99. if command.subcommand not in ('help', 'version', 'status'):
  100. from ..logging_util import log_cli_command
  101. log_cli_command(
  102. subcommand=command.subcommand,
  103. subcommand_args=command.subcommand_args,
  104. stdin=stdin,
  105. pwd=pwd or OUTPUT_DIR
  106. )
  107. run_subcommand(
  108. subcommand=command.subcommand,
  109. subcommand_args=command.subcommand_args,
  110. stdin=stdin,
  111. pwd=pwd or OUTPUT_DIR,
  112. )
  113. __all__ = (
  114. 'SUBCOMMANDS',
  115. 'list_subcommands',
  116. 'run_subcommand',
  117. *SUBCOMMANDS.keys(),
  118. )