domd 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
domd/__init__.py ADDED
@@ -0,0 +1,8 @@
1
+ """TodoMD Package"""
2
+
3
+ __version__ = "0.1.0"
4
+
5
+ from .detector import ProjectCommandDetector
6
+ from .cli import main
7
+
8
+ __all__ = ["ProjectCommandDetector", "main", "__version__"]
domd/cli.py ADDED
@@ -0,0 +1,231 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Command Line Interface for TodoMD
4
+ """
5
+
6
+ import argparse
7
+ import sys
8
+ from pathlib import Path
9
+ from typing import Optional
10
+
11
+ from . import __version__
12
+ from .detector import ProjectCommandDetector
13
+
14
+
15
+ def create_parser() -> argparse.ArgumentParser:
16
+ """Create and configure argument parser."""
17
+ parser = argparse.ArgumentParser(
18
+ prog='domd',
19
+ description='Project Command Detector - Automatically detects and tests project commands',
20
+ epilog='Examples:\n'
21
+ ' domd # Scan current directory\n'
22
+ ' domd --path /path/to/project # Scan specific project\n'
23
+ ' domd --dry-run # Preview commands without executing\n'
24
+ ' domd --output custom.md # Custom output file',
25
+ formatter_class=argparse.RawDescriptionHelpFormatter
26
+ )
27
+
28
+ parser.add_argument(
29
+ '--version',
30
+ action='version',
31
+ version=f'%(prog)s {__version__}'
32
+ )
33
+
34
+ parser.add_argument(
35
+ '--path', '-p',
36
+ type=str,
37
+ default='.',
38
+ help='Path to project directory (default: current directory)'
39
+ )
40
+
41
+ parser.add_argument(
42
+ '--dry-run', '-d',
43
+ action='store_true',
44
+ help='Only detect commands without executing them'
45
+ )
46
+
47
+ parser.add_argument(
48
+ '--verbose', '-v',
49
+ action='store_true',
50
+ help='Enable verbose output'
51
+ )
52
+
53
+ parser.add_argument(
54
+ '--quiet', '-q',
55
+ action='store_true',
56
+ help='Suppress all output except errors'
57
+ )
58
+
59
+ parser.add_argument(
60
+ '--output', '-o',
61
+ type=str,
62
+ default='TODO.md',
63
+ help='Output file for failed commands (default: TODO.md)'
64
+ )
65
+
66
+ parser.add_argument(
67
+ '--format',
68
+ choices=['markdown', 'json', 'text'],
69
+ default='markdown',
70
+ help='Output format (default: markdown)'
71
+ )
72
+
73
+ parser.add_argument(
74
+ '--timeout',
75
+ type=int,
76
+ default=60,
77
+ help='Command timeout in seconds (default: 60)'
78
+ )
79
+
80
+ parser.add_argument(
81
+ '--exclude',
82
+ action='append',
83
+ help='Exclude specific file patterns (can be used multiple times)'
84
+ )
85
+
86
+ parser.add_argument(
87
+ '--include-only',
88
+ action='append',
89
+ help='Include only specific file patterns (can be used multiple times)'
90
+ )
91
+
92
+ return parser
93
+
94
+
95
+ def validate_args(args: argparse.Namespace) -> Optional[str]:
96
+ """Validate command line arguments."""
97
+ # Check if project path exists
98
+ project_path = Path(args.path)
99
+ if not project_path.exists():
100
+ return f"Project path does not exist: {project_path}"
101
+
102
+ if not project_path.is_dir():
103
+ return f"Project path is not a directory: {project_path}"
104
+
105
+ # Check timeout value
106
+ if args.timeout <= 0:
107
+ return "Timeout must be a positive integer"
108
+
109
+ # Check if verbose and quiet are both specified
110
+ if args.verbose and args.quiet:
111
+ return "Cannot specify both --verbose and --quiet"
112
+
113
+ return None
114
+
115
+
116
+ def setup_logging(verbose: bool, quiet: bool) -> None:
117
+ """Setup logging based on verbosity level."""
118
+ import logging
119
+
120
+ if quiet:
121
+ level = logging.ERROR
122
+ elif verbose:
123
+ level = logging.DEBUG
124
+ else:
125
+ level = logging.INFO
126
+
127
+ logging.basicConfig(
128
+ level=level,
129
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
130
+ )
131
+
132
+
133
+ def print_summary(detector: ProjectCommandDetector, total_commands: int) -> None:
134
+ """Print execution summary."""
135
+ failed = len(detector.failed_commands)
136
+ success = total_commands - failed
137
+
138
+ print(f"\n{'=' * 50}")
139
+ print("EXECUTION SUMMARY")
140
+ print(f"{'=' * 50}")
141
+ print(f"āœ… Successful: {success}/{total_commands}")
142
+ print(f"āŒ Failed: {failed}/{total_commands}")
143
+
144
+ if failed > 0:
145
+ success_rate = (success / total_commands) * 100
146
+ print(f"šŸ“Š Success rate: {success_rate:.1f}%")
147
+ print(f"šŸ“ Check output file for failed command details")
148
+ else:
149
+ print(f"šŸŽ‰ All commands executed successfully!")
150
+
151
+
152
+ def main() -> int:
153
+ """Main entry point for the CLI."""
154
+ parser = create_parser()
155
+ args = parser.parse_args()
156
+
157
+ # Validate arguments
158
+ error_msg = validate_args(args)
159
+ if error_msg:
160
+ print(f"Error: {error_msg}", file=sys.stderr)
161
+ return 1
162
+
163
+ # Setup logging
164
+ setup_logging(args.verbose, args.quiet)
165
+
166
+ try:
167
+ # Initialize detector
168
+ detector = ProjectCommandDetector(
169
+ project_path=args.path,
170
+ timeout=args.timeout,
171
+ exclude_patterns=args.exclude or [],
172
+ include_patterns=args.include_only or []
173
+ )
174
+
175
+ if not args.quiet:
176
+ print(f"TodoMD v{__version__}")
177
+ print(f"Scanning project: {Path(args.path).resolve()}")
178
+
179
+ # Scan project for commands
180
+ commands = detector.scan_project()
181
+
182
+ if not commands:
183
+ if not args.quiet:
184
+ print("No commands found to test.")
185
+ return 0
186
+
187
+ if not args.quiet:
188
+ print(f"Found {len(commands)} commands to test")
189
+
190
+ # Handle dry-run mode
191
+ if args.dry_run:
192
+ if not args.quiet:
193
+ print("\nšŸ” DRY RUN MODE - Commands found:")
194
+ for i, cmd in enumerate(commands, 1):
195
+ print(f"{i:3d}. {cmd['description']}")
196
+ print(f" Command: {cmd['command']}")
197
+ print(f" Source: {cmd['source']}")
198
+ if args.verbose:
199
+ print(f" Type: {cmd.get('type', 'unknown')}")
200
+ print()
201
+ return 0
202
+
203
+ # Execute commands
204
+ if not args.quiet:
205
+ print(f"\nTesting commands...")
206
+
207
+ detector.test_commands(commands)
208
+
209
+ # Generate output file
210
+ detector.generate_output_file(args.output, args.format)
211
+
212
+ # Print summary
213
+ if not args.quiet:
214
+ print_summary(detector, len(commands))
215
+
216
+ # Return exit code based on results
217
+ return 1 if detector.failed_commands else 0
218
+
219
+ except KeyboardInterrupt:
220
+ print("\nāš ļø Operation cancelled by user", file=sys.stderr)
221
+ return 130
222
+ except Exception as e:
223
+ print(f"āŒ Unexpected error: {e}", file=sys.stderr)
224
+ if args.verbose:
225
+ import traceback
226
+ traceback.print_exc()
227
+ return 1
228
+
229
+
230
+ if __name__ == "__main__":
231
+ sys.exit(main())