Coverage for  / home / runner / work / bijux-cli / bijux-cli / src / bijux_cli / cli / repl / parsing.py: 96%

39 statements  

« prev     ^ index     » next       coverage.py v7.13.2, created at 2026-01-26 17:59 +0000

1# SPDX-License-Identifier: Apache-2.0 

2# Copyright © 2025 Bijan Mousavi 

3 

4"""Parsing helpers for REPL input and command suggestions.""" 

5 

6from __future__ import annotations 

7 

8from collections.abc import Iterator 

9from contextlib import suppress 

10import json 

11from pathlib import Path 

12import re 

13from typing import cast 

14 

15from rapidfuzz import process as rf_process 

16 

17_ansi_re = re.compile(r"\x1b\[[0-?]*[ -/]*[@-~]") 

18_semicolon_re = re.compile( 

19 r""" 

20 ; 

21 (?=(?:[^'"]|'[^']*'|"[^"]*")*$) 

22""", 

23 re.VERBOSE, 

24) 

25 

26 

27def _filter_control(text: str) -> str: 

28 """Removes ANSI control sequences from a string.""" 

29 return _ansi_re.sub("", text) 

30 

31 

32def _split_segments(input_text: str) -> Iterator[str]: 

33 """Splits input text into individual, non-empty command segments.""" 

34 clean = _filter_control(input_text) 

35 for ln in clean.splitlines(): 

36 for part in _semicolon_re.split(ln): 

37 seg = part.strip() 

38 if seg: 

39 yield seg 

40 

41 

42def _known_commands() -> list[str]: 

43 """Loads the list of known CLI commands.""" 

44 here = Path(__file__).resolve() 

45 for p in [here, *here.parents]: 

46 spec = p.parent / "spec.json" 

47 if spec.is_file(): 

48 with suppress(Exception): 

49 data = json.loads(spec.read_text()) 

50 cmds = data.get("commands") 

51 if isinstance(cmds, list): 51 ↛ 52line 51 didn't jump to line 52 because the condition on line 51 was never true

52 return cmds 

53 return [ 

54 "audit", 

55 "config", 

56 "dev", 

57 "docs", 

58 "doctor", 

59 "help", 

60 "history", 

61 "memory", 

62 "plugins", 

63 "repl", 

64 "sleep", 

65 "status", 

66 "version", 

67 ] 

68 

69 

70def _suggest(cmd: str) -> str | None: 

71 """Suggests a command based on fuzzy matching.""" 

72 choices = _known_commands() 

73 if not choices: 

74 return None 

75 result = cast(tuple[str, int, object], rf_process.extractOne(cmd, choices)) 

76 best, score, _ = result 

77 if score >= 60 and best != cmd: 

78 return f" Did you mean '{best}'?" 

79 return None