Coverage for src/pip_project_template/cli/_GlobalArgumentParser.py: 100.00%

38 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-08-28 08:53 +1000

1#!/usr/bin/env python3 

2# -*- coding: utf-8 -*- 

3# Timestamp: "2025-08-27 10:45:47 (ywatanabe)" 

4# File: /home/ywatanabe/proj/pip-project-template/src/pip_project_template/cli/_GlobalArgumentParser.py 

5# ---------------------------------------- 

6from __future__ import annotations 

7import os 

8__FILE__ = ( 

9 "./src/pip_project_template/cli/_GlobalArgumentParser.py" 

10) 

11__DIR__ = os.path.dirname(__FILE__) 

12# ---------------------------------------- 

13 

14"""Central argument parser configuration.""" 

15 

16import argparse 

17import importlib 

18import pkgutil 

19from typing import Dict, Tuple 

20 

21 

22class GlobalArgumentParser: 

23 """Central argument parser for all CLI commands.""" 

24 

25 @classmethod 

26 def get_command_parsers(cls): 

27 """Dynamically discover and load parsers from command modules.""" 

28 parsers = {} 

29 descriptions = {} 

30 

31 # Get the directory of this CLI package 

32 cli_package_path = os.path.dirname(__file__) 

33 

34 # Scan all Python files in the CLI directory 

35 for importer, modname, ispkg in pkgutil.iter_modules( 

36 [cli_package_path] 

37 ): 

38 # Skip private modules and the central parser itself 

39 if modname.startswith("_") or modname == "__pycache__": 

40 continue 

41 

42 try: 

43 # Dynamic import 

44 module = importlib.import_module( 

45 f".{modname}", package="pip_project_template.cli" 

46 ) 

47 

48 # Check if module has create_parser function 

49 if hasattr(module, "create_parser"): 

50 parser = module.create_parser() 

51 command_name = modname.replace( 

52 "_", "-" 

53 ) # Convert underscores to hyphens for CLI 

54 parsers[command_name] = parser 

55 descriptions[command_name] = getattr( 

56 parser, "description", f"Run {command_name} command" 

57 ) 

58 

59 except ( 

60 ImportError, 

61 AttributeError, 

62 Exception, 

63 ) as e: 

64 # Silently skip modules that can't be imported or don't have create_parser 

65 continue 

66 

67 return parsers, descriptions 

68 

69 @classmethod 

70 def get_main_parser(cls) -> Tuple[argparse.ArgumentParser, Dict]: 

71 """Create main parser with subcommands.""" 

72 parser = argparse.ArgumentParser( 

73 prog="python -m pip_project_template", 

74 description="Pip Project Template CLI", 

75 ) 

76 

77 subparsers = parser.add_subparsers( 

78 dest="command", help="Available commands" 

79 ) 

80 

81 parsers, descriptions = cls.get_command_parsers() 

82 subparsers_dict = {} 

83 

84 for command_name, command_parser in parsers.items(): 

85 # Create subparser with description from individual parser 

86 subparser = subparsers.add_parser( 

87 command_name, 

88 help=descriptions.get( 

89 command_name, f"Run {command_name} command" 

90 ), 

91 parents=[command_parser], 

92 add_help=False, 

93 ) 

94 subparsers_dict[command_name] = subparser 

95 

96 return parser, subparsers_dict 

97 

98# EOF