Skip to content

Commit e43e75f

Browse files
committed
feat(cli): amélioration de la commande scaffold avec prompt interactif et gestion intelligente des chemins
1 parent 43b17d6 commit e43e75f

4 files changed

Lines changed: 106 additions & 6 deletions

File tree

docs/Cli.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,3 +177,15 @@ Executes the pre-compilation pipeline manually.
177177

178178
---
179179
*End of Specification v1.0.0*
180+
181+
#### **`pycompiler_ark scaffold engine <name> [--path <dir>]`**
182+
#### **`pycompiler_ark scaffold plugin-bcasl <name> [--path <dir>]`**
183+
184+
Generates starter templates for engines or plugins.
185+
186+
- **Path Resolution**:
187+
1. If `--path` is provided, it is used as the base directory.
188+
2. If a developer directory is explicitly configured (via `set dev-engine-dir` or `set dev-plugin-dir`), the template is created directly inside that folder.
189+
3. Otherwise, the CLI will interactively prompt for a destination path.
190+
- **Interactive Prompt**: The command will wait for user input unless in a non-interactive environment or bypassed with a path/config.
191+
- **Structure**: Templates are created with the correct directory structure (`engines/<name>` or `Plugins/<name>`) unless created inside a `dev` directory, where they are placed at the root of that directory.

pycompiler_ark/Ui/Cli/discovery.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -232,10 +232,16 @@ def bcasl_doctor_payload(workspace: str | None = None) -> dict[str, Any]:
232232
}
233233

234234

235-
def scaffold_engine(target_name: str, root_dir: str | None = None) -> dict[str, Any]:
235+
def scaffold_engine(
236+
target_name: str, root_dir: str | None = None, is_dev: bool = False
237+
) -> dict[str, Any]:
236238
safe = str(target_name).strip().replace("-", "_").replace(" ", "_").lower()
237239
base_root = Path(root_dir or Path.cwd())
238-
engine_dir = base_root / "engines" / safe
240+
if is_dev:
241+
engine_dir = base_root / safe
242+
else:
243+
engine_dir = base_root / "engines" / safe
244+
239245
lang_dir = engine_dir / "languages"
240246
created: list[str] = []
241247
if engine_dir.exists():
@@ -277,10 +283,16 @@ def scaffold_engine(target_name: str, root_dir: str | None = None) -> dict[str,
277283
return {"created": True, "path": str(engine_dir)}
278284

279285

280-
def scaffold_plugin(target_name: str, root_dir: str | None = None) -> dict[str, Any]:
286+
def scaffold_plugin(
287+
target_name: str, root_dir: str | None = None, is_dev: bool = False
288+
) -> dict[str, Any]:
281289
safe = str(target_name).strip().replace("-", "_").replace(" ", "_")
282290
base_root = Path(root_dir or Path.cwd())
283-
plugin_dir = base_root / "Plugins" / safe
291+
if is_dev:
292+
plugin_dir = base_root / safe
293+
else:
294+
plugin_dir = base_root / "Plugins" / safe
295+
284296
lang_dir = plugin_dir / "languages"
285297
if plugin_dir.exists():
286298
return {"created": False, "path": str(plugin_dir), "reason": "already exists"}

pycompiler_ark/Ui/Cli/helpers.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -603,11 +603,46 @@ def list_plugins_payload() -> dict[str, Any]:
603603

604604

605605
def scaffold_engine_payload(name: str, root_dir: str | None = None) -> dict[str, Any]:
606-
return scaffold_engine(name, root_dir=root_dir)
606+
from pycompiler_ark.Core.Configs import config_file_for, resolve_config_value
607+
from pycompiler_ark.Ui.Cli.interactive import ask_path
608+
609+
is_dev = False
610+
if not root_dir:
611+
# Check if user DEFINED a custom dir (explicitly set via 'set')
612+
conf_path = config_file_for("dev-engine-dir", create_root=False)
613+
if conf_path.exists():
614+
root_dir = resolve_config_value("dev-engine-dir", create_default=False)
615+
is_dev = True
616+
617+
if not root_dir:
618+
# Prompt user
619+
root_dir = ask_path("Path for the new engine")
620+
if not root_dir:
621+
return {"created": False, "reason": "No destination path provided"}
622+
623+
# We need to tell scaffold_engine if we are in a dev dir to avoid nesting
624+
return scaffold_engine(name, root_dir=root_dir, is_dev=is_dev)
607625

608626

609627
def scaffold_plugin_payload(name: str, root_dir: str | None = None) -> dict[str, Any]:
610-
return scaffold_plugin(name, root_dir=root_dir)
628+
from pycompiler_ark.Core.Configs import config_file_for, resolve_config_value
629+
from pycompiler_ark.Ui.Cli.interactive import ask_path
630+
631+
is_dev = False
632+
if not root_dir:
633+
# Check if user DEFINED a custom dir
634+
conf_path = config_file_for("dev-plugin-dir", create_root=False)
635+
if conf_path.exists():
636+
root_dir = resolve_config_value("dev-plugin-dir", create_default=False)
637+
is_dev = True
638+
639+
if not root_dir:
640+
# Prompt user
641+
root_dir = ask_path("Path for the new plugin")
642+
if not root_dir:
643+
return {"created": False, "reason": "No destination path provided"}
644+
645+
return scaffold_plugin(name, root_dir=root_dir, is_dev=is_dev)
611646

612647

613648
def run_bcasl_before_compile_sync(

pycompiler_ark/Ui/Cli/interactive.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,47 @@ def ask_yes_no(prompt: str, *, default_yes: bool = True) -> bool:
118118
pass
119119

120120

121+
def ask_path(prompt: str, default: Optional[str] = None) -> str | None:
122+
"""Prompt the user for a directory path."""
123+
if os.environ.get("PYCOMPILER_YES") == "1":
124+
return default or os.getcwd()
125+
126+
stream, close_stream = _open_tty_stream()
127+
if stream is None:
128+
if _is_noninteractive_env():
129+
return default
130+
return None
131+
132+
try:
133+
out = _REAL_STDOUT
134+
default_label = f" ({default})" if default else ""
135+
while True:
136+
out.write(f"{prompt}{default_label}: ")
137+
out.flush()
138+
line = stream.readline()
139+
if line == "":
140+
out.write("\n")
141+
out.flush()
142+
return None
143+
answer = line.strip()
144+
if answer == "":
145+
out.write("\n")
146+
out.flush()
147+
return default or os.getcwd()
148+
149+
# Normalize and return
150+
path = os.path.abspath(os.path.expanduser(answer))
151+
out.write("\n")
152+
out.flush()
153+
return path
154+
finally:
155+
if close_stream and stream is not None:
156+
try:
157+
stream.close()
158+
except Exception:
159+
pass
160+
161+
121162
def register_cli_status(status: Any) -> None:
122163
"""Register a Rich status spinner that must pause during user prompts."""
123164
if status is not None and status not in _active_statuses:

0 commit comments

Comments
 (0)