Coverage for little_loops / cli_args.py: 30%

77 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2026-03-18 16:20 -0500

1"""Shared CLI argument definitions for little-loops tools. 

2 

3Provides reusable functions for adding common command-line arguments 

4to argparse parsers, ensuring consistency across ll-auto, ll-parallel, 

5and ll-sprint commands. 

6""" 

7 

8from __future__ import annotations 

9 

10import argparse 

11from pathlib import Path 

12 

13 

14def add_dry_run_arg(parser: argparse.ArgumentParser) -> None: 

15 """Add --dry-run/-n argument to parser.""" 

16 parser.add_argument( 

17 "--dry-run", 

18 "-n", 

19 action="store_true", 

20 help="Show what would be done without making changes", 

21 ) 

22 

23 

24def add_resume_arg(parser: argparse.ArgumentParser) -> None: 

25 """Add --resume/-r argument to parser.""" 

26 parser.add_argument( 

27 "--resume", 

28 "-r", 

29 action="store_true", 

30 help="Resume from previous checkpoint", 

31 ) 

32 

33 

34def add_config_arg(parser: argparse.ArgumentParser) -> None: 

35 """Add --config argument to parser.""" 

36 parser.add_argument( 

37 "--config", 

38 type=Path, 

39 default=None, 

40 help="Path to project root (default: current directory)", 

41 ) 

42 

43 

44def add_only_arg(parser: argparse.ArgumentParser) -> None: 

45 """Add --only argument for filtering specific issues.""" 

46 parser.add_argument( 

47 "--only", 

48 type=str, 

49 default=None, 

50 help="Comma-separated list of issue IDs to process (e.g., BUG-001,FEAT-002)", 

51 ) 

52 

53 

54def add_skip_arg(parser: argparse.ArgumentParser, help_text: str | None = None) -> None: 

55 """Add --skip argument for excluding specific issues. 

56 

57 Args: 

58 parser: The argument parser to add the argument to 

59 help_text: Optional custom help text. If not provided, uses default. 

60 """ 

61 if help_text is None: 

62 help_text = "Comma-separated list of issue IDs to skip (e.g., BUG-003,FEAT-004)" 

63 parser.add_argument( 

64 "--skip", 

65 type=str, 

66 default=None, 

67 help=help_text, 

68 ) 

69 

70 

71def add_max_workers_arg(parser: argparse.ArgumentParser, default: int | None = None) -> None: 

72 """Add --max-workers/-w argument for parallel execution. 

73 

74 Args: 

75 parser: The argument parser to add the argument to 

76 default: Default value. If None, no default is specified. 

77 """ 

78 if default is not None: 

79 parser.add_argument( 

80 "--max-workers", 

81 "-w", 

82 type=int, 

83 default=default, 

84 help=f"Maximum parallel workers (default: {default})", 

85 ) 

86 else: 

87 parser.add_argument( 

88 "--max-workers", 

89 "-w", 

90 type=int, 

91 default=None, 

92 help="Maximum parallel workers", 

93 ) 

94 

95 

96def add_timeout_arg(parser: argparse.ArgumentParser, default: int | None = None) -> None: 

97 """Add --timeout/-t argument for per-issue timeout. 

98 

99 Args: 

100 parser: The argument parser to add the argument to 

101 default: Default value in seconds. If None, no default is specified. 

102 """ 

103 if default is not None: 

104 parser.add_argument( 

105 "--timeout", 

106 "-t", 

107 type=int, 

108 default=default, 

109 help=f"Timeout in seconds (default: {default})", 

110 ) 

111 else: 

112 parser.add_argument( 

113 "--timeout", 

114 "-t", 

115 type=int, 

116 default=None, 

117 help="Timeout in seconds", 

118 ) 

119 

120 

121def add_idle_timeout_arg(parser: argparse.ArgumentParser) -> None: 

122 """Add --idle-timeout argument for idle process termination. 

123 

124 Args: 

125 parser: The argument parser to add the argument to 

126 """ 

127 parser.add_argument( 

128 "--idle-timeout", 

129 type=int, 

130 default=None, 

131 help="Kill worker if no output for N seconds (0 to disable, default: from config)", 

132 ) 

133 

134 

135def add_handoff_threshold_arg(parser: argparse.ArgumentParser) -> None: 

136 """Add --handoff-threshold argument for per-run context handoff override. 

137 

138 Args: 

139 parser: The argument parser to add the argument to 

140 """ 

141 parser.add_argument( 

142 "--handoff-threshold", 

143 type=int, 

144 default=None, 

145 help="Override auto-handoff context threshold (1-100, default: from config)", 

146 ) 

147 

148 

149def add_quiet_arg(parser: argparse.ArgumentParser) -> None: 

150 """Add --quiet/-q argument to suppress output.""" 

151 parser.add_argument( 

152 "--quiet", 

153 "-q", 

154 action="store_true", 

155 help="Suppress non-essential output", 

156 ) 

157 

158 

159def add_skip_analysis_arg(parser: argparse.ArgumentParser) -> None: 

160 """Add --skip-analysis argument to skip dependency discovery.""" 

161 parser.add_argument( 

162 "--skip-analysis", 

163 action="store_true", 

164 help="Skip dependency analysis (use when dependencies are known to be current)", 

165 ) 

166 

167 

168def add_max_issues_arg(parser: argparse.ArgumentParser) -> None: 

169 """Add --max-issues/-m argument for limiting issues processed.""" 

170 parser.add_argument( 

171 "--max-issues", 

172 "-m", 

173 type=int, 

174 default=0, 

175 help="Limit number of issues to process (0 = unlimited)", 

176 ) 

177 

178 

179def parse_issue_ids(value: str | None) -> set[str] | None: 

180 """Parse comma-separated issue IDs into a set. 

181 

182 Args: 

183 value: Comma-separated string like "BUG-001,FEAT-002" or None 

184 

185 Returns: 

186 Set of uppercase issue IDs, or None if value is None 

187 

188 Example: 

189 >>> parse_issue_ids("BUG-001,feat-002") 

190 {'BUG-001', 'FEAT-002'} 

191 >>> parse_issue_ids(None) 

192 None 

193 """ 

194 if value is None: 

195 return None 

196 return {i.strip().upper() for i in value.split(",")} 

197 

198 

199def parse_issue_ids_ordered(value: str | None) -> list[str] | None: 

200 """Parse comma-separated issue IDs into an ordered list. 

201 

202 Like parse_issue_ids but preserves input order, enabling callers to 

203 honor the sequence in which IDs were specified. 

204 

205 Args: 

206 value: Comma-separated string like "BUG-001,FEAT-002" or None 

207 

208 Returns: 

209 List of uppercase issue IDs in input order, or None if value is None 

210 

211 Example: 

212 >>> parse_issue_ids_ordered("BUG-010,FEAT-005,ENH-020") 

213 ['BUG-010', 'FEAT-005', 'ENH-020'] 

214 >>> parse_issue_ids_ordered(None) 

215 None 

216 """ 

217 if value is None: 

218 return None 

219 return [i.strip().upper() for i in value.split(",")] 

220 

221 

222VALID_ISSUE_TYPES = {"BUG", "FEAT", "ENH"} 

223 

224 

225def add_type_arg(parser: argparse.ArgumentParser) -> None: 

226 """Add --type argument for filtering issues by type prefix.""" 

227 parser.add_argument( 

228 "--type", 

229 type=str, 

230 default=None, 

231 help="Comma-separated issue types to process (e.g., BUG, FEAT, ENH)", 

232 ) 

233 

234 

235def parse_issue_types(value: str | None) -> set[str] | None: 

236 """Parse comma-separated issue types into a validated set. 

237 

238 Args: 

239 value: Comma-separated string like "BUG,ENH" or None 

240 

241 Returns: 

242 Set of uppercase type prefixes, or None if value is None 

243 

244 Raises: 

245 SystemExit: If invalid issue types are provided (via argparse error) 

246 

247 Example: 

248 >>> parse_issue_types("bug,enh") 

249 {'BUG', 'ENH'} 

250 >>> parse_issue_types(None) 

251 None 

252 """ 

253 if value is None: 

254 return None 

255 types = {t.strip().upper() for t in value.split(",")} 

256 invalid = types - VALID_ISSUE_TYPES 

257 if invalid: 

258 import sys 

259 

260 print( 

261 f"error: invalid issue type(s): {', '.join(sorted(invalid))}. " 

262 f"Valid types: {', '.join(sorted(VALID_ISSUE_TYPES))}", 

263 file=sys.stderr, 

264 ) 

265 sys.exit(2) 

266 return types 

267 

268 

269def add_common_auto_args(parser: argparse.ArgumentParser) -> None: 

270 """Add arguments common to ll-auto command. 

271 

272 Adds: --resume, --dry-run, --max-issues, --quiet, --only, --skip, --type, --config, 

273 --idle-timeout, --handoff-threshold 

274 """ 

275 add_resume_arg(parser) 

276 add_dry_run_arg(parser) 

277 add_max_issues_arg(parser) 

278 add_quiet_arg(parser) 

279 add_only_arg(parser) 

280 add_skip_arg(parser) 

281 add_type_arg(parser) 

282 add_config_arg(parser) 

283 add_idle_timeout_arg(parser) 

284 add_handoff_threshold_arg(parser) 

285 

286 

287def add_common_parallel_args(parser: argparse.ArgumentParser) -> None: 

288 """Add arguments common to parallel execution tools. 

289 

290 Adds: --dry-run, --resume, --max-workers, --timeout, --idle-timeout, --quiet, --only, --skip, --type, --config 

291 """ 

292 add_dry_run_arg(parser) 

293 add_resume_arg(parser) 

294 add_max_workers_arg(parser) 

295 add_timeout_arg(parser) 

296 add_idle_timeout_arg(parser) 

297 add_quiet_arg(parser) 

298 add_only_arg(parser) 

299 add_skip_arg(parser) 

300 add_type_arg(parser) 

301 add_config_arg(parser) 

302 

303 

304__all__ = [ 

305 "add_dry_run_arg", 

306 "add_resume_arg", 

307 "add_config_arg", 

308 "add_only_arg", 

309 "add_skip_arg", 

310 "add_type_arg", 

311 "add_max_workers_arg", 

312 "add_timeout_arg", 

313 "add_idle_timeout_arg", 

314 "add_handoff_threshold_arg", 

315 "add_quiet_arg", 

316 "add_skip_analysis_arg", 

317 "add_max_issues_arg", 

318 "parse_issue_ids", 

319 "parse_issue_types", 

320 "VALID_ISSUE_TYPES", 

321 "add_common_auto_args", 

322 "add_common_parallel_args", 

323]