Coverage for little_loops / cli / loop / config_cmds.py: 0%

55 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2026-06-29 00:54 -0500

1"""ll-loop config subcommands: validate, install.""" 

2 

3from __future__ import annotations 

4 

5import argparse 

6from pathlib import Path 

7 

8from little_loops.cli.loop._helpers import get_builtin_loops_dir, resolve_loop_path 

9from little_loops.logger import Logger 

10 

11 

12def cmd_validate( 

13 loop_name: str, 

14 args: argparse.Namespace, 

15 loops_dir: Path, 

16 logger: Logger, 

17) -> int: 

18 """Validate a loop definition.""" 

19 from little_loops.fsm.validation import ValidationSeverity, load_and_validate 

20 

21 as_json = getattr(args, "json", False) 

22 

23 try: 

24 path = resolve_loop_path(loop_name, loops_dir) 

25 

26 if as_json: 

27 from little_loops.cli.output import print_json 

28 

29 fsm, violations = load_and_validate(path, raise_on_error=False) 

30 has_errors = any(v.severity == ValidationSeverity.ERROR for v in violations) 

31 print_json( 

32 { 

33 "loop": loop_name, 

34 "valid": not has_errors, 

35 "violations": [ 

36 {"severity": v.severity.value, "path": v.path, "message": v.message} 

37 for v in violations 

38 ], 

39 } 

40 ) 

41 return 1 if has_errors else 0 

42 

43 fsm, warnings = load_and_validate(path) 

44 logger.success(f"{loop_name} is valid") 

45 print(f" States: {', '.join(fsm.states.keys())}") 

46 print(f" Initial: {fsm.initial}") 

47 print(f" Max steps: {fsm.max_steps}") 

48 if fsm.max_iterations is not None: 

49 print(f" Max iterations: {fsm.max_iterations}") 

50 for w in warnings: 

51 print(f"{w}") 

52 return 0 

53 except FileNotFoundError as e: 

54 if as_json: 

55 from little_loops.cli.output import print_json 

56 

57 print_json( 

58 { 

59 "loop": loop_name, 

60 "valid": False, 

61 "violations": [{"severity": "error", "path": "<root>", "message": str(e)}], 

62 } 

63 ) 

64 else: 

65 logger.error(str(e)) 

66 return 1 

67 except ValueError as e: 

68 logger.error(f"{loop_name} is invalid: {e}") 

69 return 1 

70 

71 

72def cmd_install( 

73 loop_name: str, 

74 loops_dir: Path, 

75 logger: Logger, 

76) -> int: 

77 """Copy a built-in loop to .loops/ for customization.""" 

78 import shutil 

79 

80 builtin_dir = get_builtin_loops_dir() 

81 source = builtin_dir / f"{loop_name}.yaml" 

82 

83 if not source.exists(): 

84 available = [f.stem for f in builtin_dir.glob("*.yaml")] if builtin_dir.exists() else [] 

85 logger.error(f"No built-in loop named '{loop_name}'") 

86 if available: 

87 print(f"Available built-in loops: {', '.join(sorted(available))}") 

88 return 1 

89 

90 loops_dir.mkdir(exist_ok=True) 

91 dest = loops_dir / f"{loop_name}.yaml" 

92 

93 if dest.exists(): 

94 logger.error(f"Loop already exists: {dest}") 

95 print("Remove it first or edit it directly.") 

96 return 1 

97 

98 shutil.copy2(source, dest) 

99 print(f"Installed {loop_name} to {dest}") 

100 print("You can now customize it by editing the file.") 

101 return 0