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
« 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
4"""Parsing helpers for REPL input and command suggestions."""
6from __future__ import annotations
8from collections.abc import Iterator
9from contextlib import suppress
10import json
11from pathlib import Path
12import re
13from typing import cast
15from rapidfuzz import process as rf_process
17_ansi_re = re.compile(r"\x1b\[[0-?]*[ -/]*[@-~]")
18_semicolon_re = re.compile(
19 r"""
20 ;
21 (?=(?:[^'"]|'[^']*'|"[^"]*")*$)
22""",
23 re.VERBOSE,
24)
27def _filter_control(text: str) -> str:
28 """Removes ANSI control sequences from a string."""
29 return _ansi_re.sub("", text)
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
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 ]
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