syntax revamp

This commit is contained in:
2026-05-24 12:32:57 -07:00
parent 6c0a1b4415
commit 5041d9fbb9
20 changed files with 1512 additions and 1060 deletions
+101 -12
View File
@@ -70,6 +70,17 @@ def _tokenize_stage(stage_text: str) -> list[str]:
return text.split()
def _parse_pipeline_tokens(raw: str) -> list[tuple[str, list[str]]]:
parsed: list[tuple[str, list[str]]] = []
for stage in _split_pipeline_stages(raw):
tokens = _tokenize_stage(stage)
if not tokens:
continue
cmd = str(tokens[0]).replace("_", "-").strip().lower()
parsed.append((cmd, tokens))
return parsed
def _has_flag(tokens: list[str], *flags: str) -> bool:
want = {str(f).strip().lower() for f in flags if str(f).strip()}
if not want:
@@ -115,18 +126,10 @@ def _validate_add_note_requires_add_file_order(raw: str) -> Optional[SyntaxError
Rationale: add-note requires a known (store, hash) target; piping before add-file
means the item likely has no hash yet.
"""
stages = _split_pipeline_stages(raw)
if len(stages) <= 1:
parsed = _parse_pipeline_tokens(raw)
if len(parsed) <= 1:
return None
parsed: list[tuple[str, list[str]]] = []
for stage in stages:
tokens = _tokenize_stage(stage)
if not tokens:
continue
cmd = str(tokens[0]).replace("_", "-").strip().lower()
parsed.append((cmd, tokens))
add_file_positions = [i for i, (cmd, _toks) in enumerate(parsed) if cmd == "add-file"]
if not add_file_positions:
return None
@@ -165,7 +168,85 @@ def _validate_add_note_requires_add_file_order(raw: str) -> Optional[SyntaxError
return None
def validate_pipeline_text(text: str) -> Optional[SyntaxErrorDetail]:
def _validate_file_cmdlet_stage_actions(raw: str) -> Optional[SyntaxErrorDetail]:
parsed = _parse_pipeline_tokens(raw)
if not parsed:
return None
try:
from cmdlet.file_cmdlet import File as FileCmdlet
except Exception:
return None
for cmd, tokens in parsed:
if cmd != "file":
continue
action, _passthrough, seen = FileCmdlet._extract_action(tokens[1:])
if action is not None:
continue
if seen:
rendered = ", ".join(f"-{name}" for name in seen)
return SyntaxErrorDetail(
f"Pipeline error: 'file' has conflicting actions ({rendered}); choose exactly one."
)
if _has_flag(tokens[1:], "-plugin", "--plugin", "-instance", "--instance", "-path", "--path"):
return SyntaxErrorDetail(
"Pipeline error: 'file' requires an explicit action here. "
"Use 'file -add -plugin local -instance <name|path>' for local export, or 'file -search ...' for search."
)
return SyntaxErrorDetail(
"Pipeline error: 'file' requires -search/-query for search or exactly one action flag "
"(-search, -add, -delete, -merge, -download, -convert, -trim, -archive, -screenshot)."
)
return None
def _validate_add_file_stage_preflight(
raw: str,
config: Optional[Dict[str, Any]],
) -> Optional[SyntaxErrorDetail]:
if not isinstance(config, dict):
return None
parsed = _parse_pipeline_tokens(raw)
if not parsed:
return None
try:
from cmdlet.file.add import Add_File
from cmdlet.file_cmdlet import File as FileCmdlet
except Exception:
return None
for cmd, tokens in parsed:
stage_args: Optional[list[str]] = None
if cmd == "add-file":
stage_args = tokens[1:]
elif cmd == "file":
action, passthrough, _seen = FileCmdlet._extract_action(tokens[1:])
if action == "add":
stage_args = passthrough
if stage_args is None:
continue
message = Add_File.validate_preflight_args(stage_args, config)
if message:
return SyntaxErrorDetail(message)
return None
def validate_pipeline_text(
text: str,
config: Optional[Dict[str, Any]] = None,
) -> Optional[SyntaxErrorDetail]:
"""Validate raw CLI input before tokenization/execution.
This is intentionally lightweight and focuses on user-facing syntax issues:
@@ -252,11 +333,19 @@ def validate_pipeline_text(text: str) -> Optional[SyntaxErrorDetail]:
if not in_single and not in_double and not ch.isspace():
seen_nonspace_since_pipe = True
# Semantic rules (still lightweight; no cmdlet imports)
# Pipeline-only semantic rules.
semantic_error = _validate_add_note_requires_add_file_order(raw)
if semantic_error is not None:
return semantic_error
semantic_error = _validate_file_cmdlet_stage_actions(raw)
if semantic_error is not None:
return semantic_error
semantic_error = _validate_add_file_stage_preflight(raw, config)
if semantic_error is not None:
return semantic_error
return None