Skip to content

Commit 2acdaa0

Browse files
committed
feat(cli): imposer un workspace explicite pour les commandes workspace/venv
1 parent 3b80fc5 commit 2acdaa0

3 files changed

Lines changed: 79 additions & 46 deletions

File tree

cli/click_app.py

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,16 @@ def _resolve_workspace_path(workspace: str | None) -> str | None:
142142
return normalize_path(workspace)
143143

144144

145+
def _require_workspace_path(workspace: str | None, command_hint: str) -> str:
146+
"""Resolve a mandatory workspace path or raise a usage error."""
147+
resolved = _resolve_workspace_path(workspace)
148+
if resolved:
149+
return resolved
150+
raise click.ClickException(
151+
f"Workspace is required. Usage: pycompiler_ark {command_hint} <workspace>"
152+
)
153+
154+
145155
def _workspace_init_emit(workspace_dir: str, as_json: bool, with_venv: bool) -> None:
146156
"""Run init workflow and emit output according to CLI output mode."""
147157
# Mode JSON destiné à l'automatisation (CI, scripts, wrappers).
@@ -337,14 +347,23 @@ def gui_engines(workspace):
337347
)
338348

339349
@cli.command("prog-engine")
340-
@click.argument("engine_id")
350+
@click.argument("engine_or_workspace", required=False, type=str)
341351
@click.argument("workspace", required=False, type=click.Path(exists=False))
342-
def prog_engine_cmd(engine_id, workspace):
343-
"""Launch dedicated single-engine config GUI with live JSON editor."""
352+
def prog_engine_cmd(engine_or_workspace, workspace):
353+
"""Launch prog-engine GUI (single engine, or all engines when id is omitted)."""
354+
engine_id = None
355+
workspace_value = workspace
356+
if workspace_value:
357+
engine_id = engine_or_workspace
358+
else:
359+
workspace_value = engine_or_workspace
360+
344361
sys.exit(
345362
launch_prog_engine_gui(
346363
engine_id=engine_id,
347-
workspace_dir=_ensure_workspace_exists(_resolve_workspace_path(workspace)),
364+
workspace_dir=_ensure_workspace_exists(
365+
_require_workspace_path(workspace_value, "prog-engine")
366+
),
348367
)
349368
)
350369

@@ -656,7 +675,9 @@ def venv():
656675
@click.option("--json", "as_json", is_flag=True)
657676
def venv_status(workspace, as_json):
658677
"""Show current workspace Python mode and venv preference."""
659-
payload = venv_status_payload(_resolve_workspace_path(workspace or "."))
678+
payload = venv_status_payload(
679+
_require_workspace_path(workspace, "venv status")
680+
)
660681
if as_json:
661682
if not payload.get("ok"):
662683
_emit_and_exit(payload, EXIT_WORKSPACE_INVALID, as_json=True)
@@ -676,7 +697,9 @@ def venv_status(workspace, as_json):
676697
@click.option("--json", "as_json", is_flag=True)
677698
def venv_use_system(workspace, as_json):
678699
"""Switch workspace Python mode to system interpreter."""
679-
payload = venv_use_system_payload(_resolve_workspace_path(workspace or "."))
700+
payload = venv_use_system_payload(
701+
_require_workspace_path(workspace, "venv use-system")
702+
)
680703
if as_json:
681704
if not payload.get("ok"):
682705
_emit_and_exit(payload, EXIT_WORKSPACE_INVALID, as_json=True)
@@ -698,7 +721,7 @@ def venv_use_system(workspace, as_json):
698721
def venv_use_venv(workspace, venv_path, create, as_json):
699722
"""Switch workspace Python mode to venv and persist preference."""
700723
payload = venv_use_venv_payload(
701-
_resolve_workspace_path(workspace or "."),
724+
_require_workspace_path(workspace, "venv use-venv"),
702725
venv_path=venv_path,
703726
create_if_missing=bool(create),
704727
)
@@ -719,7 +742,7 @@ def venv_use_venv(workspace, venv_path, create, as_json):
719742
def venv_install_req(workspace, force_pip, as_json):
720743
"""Install workspace requirements using current Python mode."""
721744
payload = venv_install_requirements_payload(
722-
_resolve_workspace_path(workspace or "."),
745+
_require_workspace_path(workspace, "venv install-req"),
723746
force_pip=bool(force_pip),
724747
)
725748
if as_json:
@@ -741,6 +764,7 @@ def venv_install_req(workspace, force_pip, as_json):
741764
cli=cli,
742765
click=click,
743766
resolve_workspace_path=_resolve_workspace_path,
767+
require_workspace_path=_require_workspace_path,
744768
emit_and_exit=_emit_and_exit,
745769
echo_payload=_echo_payload,
746770
workspace_init_emit=_workspace_init_emit,

cli/click_workspace_commands.py

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ def register_workspace_commands(
1818
cli: Any,
1919
click: Any,
2020
resolve_workspace_path: Callable[[str | None], str | None],
21+
require_workspace_path: Callable[[str | None, str], str],
2122
emit_and_exit: Callable[[object, int, bool], None],
2223
echo_payload: Callable[[object, bool], None],
2324
workspace_init_emit: Callable[[str, bool, bool], None],
@@ -42,7 +43,9 @@ def workspace():
4243
def workspace_inspect(path, as_json, strict):
4344
"""Inspect workspace state and optionally enforce strict existence checks."""
4445
# Inspection non destructive: lit l'état workspace sans rien modifier.
45-
payload = workspace_inspect_payload(resolve_workspace_path(path or "."))
46+
payload = workspace_inspect_payload(
47+
require_workspace_path(path, "workspace inspect")
48+
)
4649
if as_json:
4750
if strict and not payload.get("exists"):
4851
emit_and_exit(payload, EXIT_WORKSPACE_INVALID, True)
@@ -67,7 +70,9 @@ def workspace_inspect(path, as_json, strict):
6770
def workspace_entrypoint(path, as_json, strict):
6871
"""Return the current workspace entrypoint in compact text or JSON form."""
6972
# Sortie volontairement compacte pour simplifier les scripts shell.
70-
payload = workspace_inspect_payload(resolve_workspace_path(path or "."))
73+
payload = workspace_inspect_payload(
74+
require_workspace_path(path, "workspace entrypoint")
75+
)
7176
result = {
7277
"workspace": payload.get("workspace"),
7378
"entrypoint": payload.get("entrypoint"),
@@ -88,7 +93,7 @@ def workspace_entrypoint(path, as_json, strict):
8893
def workspace_entrypoint_set(path, entrypoint, as_json):
8994
"""Set and persist an explicit workspace entrypoint."""
9095
payload = workspace_entrypoint_set_payload(
91-
resolve_workspace_path(path or "."),
96+
require_workspace_path(path, "workspace entrypoint-set"),
9297
entrypoint,
9398
)
9499
if as_json:
@@ -112,7 +117,7 @@ def workspace_entrypoint_set(path, entrypoint, as_json):
112117
def workspace_entrypoint_clear(path, as_json):
113118
"""Clear the persisted workspace entrypoint."""
114119
payload = workspace_entrypoint_clear_payload(
115-
resolve_workspace_path(path or ".")
120+
require_workspace_path(path, "workspace entrypoint-clear")
116121
)
117122
if as_json:
118123
if not payload.get("ok"):
@@ -137,7 +142,9 @@ def workspace_entrypoint_clear(path, as_json):
137142
def workspace_files(path, as_json):
138143
"""Show a preview list of Python files discovered in the workspace."""
139144
# Preview partielle: on ne dump pas toute l'arborescence pour rester lisible.
140-
payload = workspace_inspect_payload(resolve_workspace_path(path or "."))
145+
payload = workspace_inspect_payload(
146+
require_workspace_path(path, "workspace files")
147+
)
141148
result = {
142149
"workspace": payload.get("workspace"),
143150
"python_file_count": payload.get("python_file_count", 0),
@@ -190,8 +197,9 @@ def workspace_apply(
190197
require_entrypoint,
191198
):
192199
"""Apply the full workspace workflow in a single command."""
200+
resolved_path = require_workspace_path(path, "workspace apply")
193201
args = build_workspace_apply_args(
194-
path=path,
202+
path=resolved_path,
195203
as_json=bool(as_json),
196204
with_venv=bool(with_venv),
197205
entrypoint=entrypoint,
@@ -246,8 +254,9 @@ def workspace_select(
246254
require_entrypoint,
247255
):
248256
"""Alias of workspace apply that keeps GUI wording familiarity."""
257+
resolved_path = require_workspace_path(path, "workspace select")
249258
args = build_workspace_apply_args(
250-
path=path,
259+
path=resolved_path,
251260
as_json=bool(as_json),
252261
with_venv=bool(with_venv),
253262
entrypoint=entrypoint,
@@ -271,7 +280,7 @@ def workspace_select(
271280
def init_cmd(workspace, as_json, with_venv):
272281
"""Initialize workspace structure and baseline configuration files."""
273282
# Les effets de bord (création fichiers/dossiers) sont encapsulés dans workspace_init_emit.
274-
workspace_dir = resolve_workspace_path(workspace or ".")
283+
workspace_dir = require_workspace_path(workspace, "init")
275284
workspace_init_emit(workspace_dir, as_json=as_json, with_venv=with_venv)
276285

277286
@cli.command("config-auto")
@@ -282,7 +291,7 @@ def config_auto_cmd(workspace, entrypoint, as_json):
282291
"""Auto-configure workspace entrypoint and dependency settings."""
283292
# Support d'override explicite via --entrypoint pour bypasser l'auto-détection.
284293
workspace_config_auto_emit(
285-
resolve_workspace_path(workspace or "."),
294+
require_workspace_path(workspace, "config-auto"),
286295
entrypoint=entrypoint,
287296
as_json=as_json,
288297
)
@@ -294,7 +303,7 @@ def config_auto_cmd(workspace, entrypoint, as_json):
294303
def cfg_auto_cmd(workspace, entrypoint, as_json):
295304
"""Short alias for config-auto."""
296305
workspace_config_auto_emit(
297-
resolve_workspace_path(workspace or "."),
306+
require_workspace_path(workspace, "cfg-auto"),
298307
entrypoint=entrypoint,
299308
as_json=as_json,
300309
)
@@ -312,7 +321,7 @@ def ws():
312321
def ws_init_cmd(workspace, as_json, with_venv):
313322
"""Initialize workspace using the short ws namespace."""
314323
# Alias strict de `init` pour garder une UX type "git-like".
315-
workspace_dir = resolve_workspace_path(workspace or ".")
324+
workspace_dir = require_workspace_path(workspace, "ws init")
316325
workspace_init_emit(workspace_dir, as_json=as_json, with_venv=with_venv)
317326

318327
@ws.command("config-auto")
@@ -322,7 +331,7 @@ def ws_init_cmd(workspace, as_json, with_venv):
322331
def ws_config_auto_cmd(workspace, entrypoint, as_json):
323332
"""Auto-configure workspace using the short ws namespace."""
324333
workspace_config_auto_emit(
325-
resolve_workspace_path(workspace or "."),
334+
require_workspace_path(workspace, "ws config-auto"),
326335
entrypoint=entrypoint,
327336
as_json=as_json,
328337
)
@@ -334,7 +343,7 @@ def ws_config_auto_cmd(workspace, entrypoint, as_json):
334343
def ws_entrypoint_set_cmd(workspace, entrypoint, as_json):
335344
"""Set workspace entrypoint through the short ws namespace."""
336345
payload = workspace_entrypoint_set_payload(
337-
resolve_workspace_path(workspace or "."),
346+
require_workspace_path(workspace, "ws entrypoint-set"),
338347
entrypoint,
339348
)
340349
if as_json:
@@ -358,7 +367,7 @@ def ws_entrypoint_set_cmd(workspace, entrypoint, as_json):
358367
def ws_entrypoint_clear_cmd(workspace, as_json):
359368
"""Clear workspace entrypoint through the short ws namespace."""
360369
payload = workspace_entrypoint_clear_payload(
361-
resolve_workspace_path(workspace or ".")
370+
require_workspace_path(workspace, "ws entrypoint-clear")
362371
)
363372
if as_json:
364373
if not payload.get("ok"):
@@ -417,8 +426,9 @@ def ws_apply_cmd(
417426
require_entrypoint,
418427
):
419428
"""Apply workspace workflow using the short ws namespace."""
429+
resolved_path = require_workspace_path(workspace, "ws apply")
420430
args = build_workspace_apply_args(
421-
path=workspace,
431+
path=resolved_path,
422432
as_json=bool(as_json),
423433
with_venv=bool(with_venv),
424434
entrypoint=entrypoint,
@@ -473,8 +483,9 @@ def ws_select_cmd(
473483
require_entrypoint,
474484
):
475485
"""Select/apply workspace using the short ws namespace."""
486+
resolved_path = require_workspace_path(workspace, "ws select")
476487
args = build_workspace_apply_args(
477-
path=workspace,
488+
path=resolved_path,
478489
as_json=bool(as_json),
479490
with_venv=bool(with_venv),
480491
entrypoint=entrypoint,

cli/headless_commands.py

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,17 @@ def extract_option_value(args: list[str], option: str) -> tuple[str | None, list
5959
return value, clean
6060

6161

62+
def require_workspace_arg(args: list[str], command_hint: str) -> str | None:
63+
"""Return explicit workspace from args or emit usage error and return None."""
64+
workspace = normalize_path(
65+
first_positional([token for token in args if not token.startswith("-")])
66+
)
67+
if workspace:
68+
return workspace
69+
error(f"Workspace is required. Usage: {command_hint} <workspace>")
70+
return None
71+
72+
6273
def run_check(args: list[str]) -> int:
6374
"""Execute the strict CI/CD check command in headless mode."""
6475
# On garde les mêmes valeurs par défaut que la commande click `check`.
@@ -91,12 +102,9 @@ def run_init(args: list[str]) -> int:
91102
# Le rendu texte suit le format partagé pour limiter les divergences d'UX.
92103
as_json = "--json" in args
93104
with_venv = "--with-venv" in args
94-
workspace = (
95-
normalize_path(
96-
first_positional([token for token in args if not token.startswith("-")])
97-
)
98-
or "."
99-
)
105+
workspace = require_workspace_arg(args, "init")
106+
if not workspace:
107+
return EXIT_USAGE_ERROR
100108
# L'initialisation unifie création dossier/config/bcasl/pref (et venv optionnel).
101109
payload = workspace_init_payload(workspace, with_venv=with_venv)
102110
if as_json:
@@ -121,14 +129,9 @@ def run_config_auto(args: list[str]) -> int:
121129
except ValueError as exc:
122130
error(str(exc))
123131
return EXIT_USAGE_ERROR
124-
workspace = (
125-
normalize_path(
126-
first_positional(
127-
[token for token in clean_args if not token.startswith("-")]
128-
)
129-
)
130-
or "."
131-
)
132+
workspace = require_workspace_arg(clean_args, "config-auto")
133+
if not workspace:
134+
return EXIT_USAGE_ERROR
132135
# Détection auto de l'entrypoint + mise à jour config workspace.
133136
payload = workspace_config_auto_payload(workspace, entrypoint=entrypoint)
134137
if as_json:
@@ -165,14 +168,9 @@ def run_workspace_apply(args: list[str]) -> int:
165168
error(str(exc))
166169
return EXIT_USAGE_ERROR
167170

168-
workspace = (
169-
normalize_path(
170-
first_positional(
171-
[token for token in clean_args if not token.startswith("-")]
172-
)
173-
)
174-
or "."
175-
)
171+
workspace = require_workspace_arg(clean_args, "workspace apply")
172+
if not workspace:
173+
return EXIT_USAGE_ERROR
176174

177175
payload = workspace_apply_payload(
178176
workspace,

0 commit comments

Comments
 (0)