__init__.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. __package__ = 'archivebox.cli'
  2. __command__ = 'archivebox'
  3. import os
  4. import sys
  5. from importlib import import_module
  6. import rich_click as click
  7. from rich import print
  8. from archivebox.config.version import VERSION
  9. if '--debug' in sys.argv:
  10. os.environ['DEBUG'] = 'True'
  11. sys.argv.remove('--debug')
  12. class ArchiveBoxGroup(click.Group):
  13. """lazy loading click group for archivebox commands"""
  14. meta_commands = {
  15. 'help': 'archivebox.cli.archivebox_help.main',
  16. 'version': 'archivebox.cli.archivebox_version.main',
  17. 'mcp': 'archivebox.cli.archivebox_mcp.main',
  18. }
  19. setup_commands = {
  20. 'init': 'archivebox.cli.archivebox_init.main',
  21. 'install': 'archivebox.cli.archivebox_install.main',
  22. }
  23. # Model commands (CRUD operations via subcommands)
  24. model_commands = {
  25. 'crawl': 'archivebox.cli.archivebox_crawl.main',
  26. 'snapshot': 'archivebox.cli.archivebox_snapshot.main',
  27. 'archiveresult': 'archivebox.cli.archivebox_archiveresult.main',
  28. 'tag': 'archivebox.cli.archivebox_tag.main',
  29. 'binary': 'archivebox.cli.archivebox_binary.main',
  30. 'process': 'archivebox.cli.archivebox_process.main',
  31. 'machine': 'archivebox.cli.archivebox_machine.main',
  32. }
  33. archive_commands = {
  34. # High-level commands
  35. 'add': 'archivebox.cli.archivebox_add.main',
  36. 'run': 'archivebox.cli.archivebox_run.main',
  37. 'update': 'archivebox.cli.archivebox_update.main',
  38. 'status': 'archivebox.cli.archivebox_status.main',
  39. 'config': 'archivebox.cli.archivebox_config.main',
  40. 'schedule': 'archivebox.cli.archivebox_schedule.main',
  41. 'server': 'archivebox.cli.archivebox_server.main',
  42. 'shell': 'archivebox.cli.archivebox_shell.main',
  43. 'manage': 'archivebox.cli.archivebox_manage.main',
  44. # Introspection commands
  45. 'pluginmap': 'archivebox.cli.archivebox_pluginmap.main',
  46. # Worker command
  47. 'worker': 'archivebox.cli.archivebox_worker.main',
  48. }
  49. all_subcommands = {
  50. **meta_commands,
  51. **setup_commands,
  52. **model_commands,
  53. **archive_commands,
  54. }
  55. renamed_commands = {
  56. 'setup': 'install',
  57. 'import': 'add',
  58. 'archive': 'add',
  59. # Old commands replaced by new model commands
  60. 'orchestrator': 'run',
  61. 'extract': 'archiveresult',
  62. }
  63. @classmethod
  64. def get_canonical_name(cls, cmd_name):
  65. return cls.renamed_commands.get(cmd_name, cmd_name)
  66. def get_command(self, ctx, cmd_name):
  67. # handle renamed commands
  68. if cmd_name in self.renamed_commands:
  69. new_name = self.renamed_commands[cmd_name]
  70. print(f' [violet]Hint:[/violet] `archivebox {cmd_name}` has been renamed to `archivebox {new_name}`')
  71. cmd_name = new_name
  72. ctx.invoked_subcommand = cmd_name
  73. # handle lazy loading of commands
  74. if cmd_name in self.all_subcommands:
  75. return self._lazy_load(cmd_name)
  76. # fall-back to using click's default command lookup
  77. return super().get_command(ctx, cmd_name)
  78. @classmethod
  79. def _lazy_load(cls, cmd_name):
  80. import_path = cls.all_subcommands[cmd_name]
  81. modname, funcname = import_path.rsplit('.', 1)
  82. # print(f'LAZY LOADING {import_path}')
  83. mod = import_module(modname)
  84. func = getattr(mod, funcname)
  85. if not hasattr(func, '__doc__'):
  86. raise ValueError(f'lazy loading of {import_path} failed - no docstring found on method')
  87. # if not isinstance(cmd, click.BaseCommand):
  88. # raise ValueError(f'lazy loading of {import_path} failed - not a click command')
  89. return func
  90. @click.group(cls=ArchiveBoxGroup, invoke_without_command=True)
  91. @click.option('--help', '-h', is_flag=True, help='Show help')
  92. @click.version_option(VERSION, '-v', '--version', package_name='archivebox', message='%(version)s')
  93. @click.pass_context
  94. def cli(ctx, help=False):
  95. """ArchiveBox: The self-hosted internet archive"""
  96. subcommand = ArchiveBoxGroup.get_canonical_name(ctx.invoked_subcommand)
  97. # if --help is passed or no subcommand is given, show custom help message
  98. if help or ctx.invoked_subcommand is None:
  99. ctx.invoke(ctx.command.get_command(ctx, 'help'))
  100. # if the subcommand is in archive_commands or model_commands,
  101. # then we need to set up the django environment and check that we're in a valid data folder
  102. if subcommand in ArchiveBoxGroup.archive_commands or subcommand in ArchiveBoxGroup.model_commands:
  103. # print('SETUP DJANGO AND CHECK DATA FOLDER')
  104. try:
  105. from archivebox.config.django import setup_django
  106. from archivebox.misc.checks import check_data_folder
  107. setup_django()
  108. check_data_folder()
  109. except Exception as e:
  110. print(f'[red][X] Error setting up Django or checking data folder: {e}[/red]', file=sys.stderr)
  111. if subcommand not in ('manage', 'shell'): # not all management commands need django to be setup beforehand
  112. raise
  113. def main(args=None, prog_name=None, stdin=None):
  114. # show `docker run archivebox xyz` in help messages if running in docker
  115. IN_DOCKER = os.environ.get('IN_DOCKER', False) in ('1', 'true', 'True', 'TRUE', 'yes')
  116. IS_TTY = sys.stdin.isatty()
  117. prog_name = prog_name or (f'docker compose run{"" if IS_TTY else " -T"} archivebox' if IN_DOCKER else 'archivebox')
  118. # stdin param allows passing input data from caller (used by __main__.py)
  119. # currently not used by click-based CLI, but kept for backwards compatibility
  120. try:
  121. cli(args=args, prog_name=prog_name)
  122. except KeyboardInterrupt:
  123. print('\n\n[red][X] Got CTRL+C. Exiting...[/red]')
  124. if __name__ == '__main__':
  125. main()