55
66"""Click-based CLI frontend for PyCompiler ARK."""
77
8+ import json
89from pathlib import Path
910import sys
1011
2627 doctor_payload ,
2728 emit_json ,
2829 engine_config_path_payload ,
30+ engine_config_reset_payload ,
31+ engine_config_set_payload ,
2932 engine_config_show_payload ,
3033 engine_doctor_payload ,
3134 engine_info_payload ,
3235 engine_list_payload ,
3336 scaffold_engine ,
3437 scaffold_plugin ,
38+ venv_install_requirements_payload ,
39+ venv_status_payload ,
40+ venv_use_system_payload ,
41+ venv_use_venv_payload ,
3542 workspace_config_auto_payload ,
43+ workspace_entrypoint_clear_payload ,
44+ workspace_entrypoint_set_payload ,
3645 workspace_init_payload ,
3746 workspace_inspect_payload ,
3847)
@@ -450,6 +459,72 @@ def engine_config_path(engine_id, workspace, as_json):
450459 raise click .ClickException ("Workspace is required" )
451460 plain (payload .get ("path" , "" ))
452461
462+ @engine_config .command ("set" )
463+ @click .argument ("engine_id" )
464+ @click .option ("-w" , "--workspace" , required = True , type = click .Path (exists = False ))
465+ @click .option ("--options-json" , type = str , help = "Inline JSON object with engine options" )
466+ @click .option ("--options-file" , type = click .Path (exists = True ), help = "Path to JSON file with engine options" )
467+ @click .option ("--replace" , is_flag = True , help = "Replace options instead of merge" )
468+ @click .option ("--json" , "as_json" , is_flag = True )
469+ def engine_config_set (engine_id , workspace , options_json , options_file , replace , as_json ):
470+ if bool (options_json ) == bool (options_file ):
471+ raise click .ClickException ("Provide exactly one of --options-json or --options-file" )
472+ try :
473+ if options_file :
474+ options = json .loads (Path (options_file ).read_text (encoding = "utf-8" ))
475+ else :
476+ options = json .loads (options_json )
477+ except Exception as exc :
478+ raise click .ClickException (f"Invalid JSON options: { exc } " )
479+ if not isinstance (options , dict ):
480+ raise click .ClickException ("Engine options must be a JSON object" )
481+ payload = engine_config_set_payload (
482+ engine_id ,
483+ workspace = _resolve_workspace_path (workspace ),
484+ options = options ,
485+ merge = not bool (replace ),
486+ )
487+ if as_json :
488+ if payload .get ("error" ) == "workspace is required" :
489+ _emit_and_exit (payload , EXIT_WORKSPACE_INVALID , as_json = True )
490+ if payload .get ("found" ) is False :
491+ _emit_and_exit (payload , EXIT_ENGINE_NOT_FOUND , as_json = True )
492+ if not payload .get ("saved" ):
493+ _emit_and_exit (payload , EXIT_PRECHECK_FAILED , as_json = True )
494+ _echo_payload (payload , as_json = True )
495+ return
496+ if payload .get ("found" ) is False :
497+ raise click .ClickException (f"Engine not found: { engine_id } " )
498+ if not payload .get ("saved" ):
499+ raise click .ClickException (payload .get ("error" , "Unable to save engine config" ))
500+ success (f"Engine config saved: { engine_id } " )
501+ plain (f" Path: { payload .get ('path' )} " )
502+
503+ @engine_config .command ("reset" )
504+ @click .argument ("engine_id" )
505+ @click .option ("-w" , "--workspace" , required = True , type = click .Path (exists = False ))
506+ @click .option ("--json" , "as_json" , is_flag = True )
507+ def engine_config_reset (engine_id , workspace , as_json ):
508+ payload = engine_config_reset_payload (
509+ engine_id ,
510+ workspace = _resolve_workspace_path (workspace ),
511+ )
512+ if as_json :
513+ if payload .get ("error" ) == "workspace is required" :
514+ _emit_and_exit (payload , EXIT_WORKSPACE_INVALID , as_json = True )
515+ if payload .get ("found" ) is False :
516+ _emit_and_exit (payload , EXIT_ENGINE_NOT_FOUND , as_json = True )
517+ if not payload .get ("reset" ):
518+ _emit_and_exit (payload , EXIT_PRECHECK_FAILED , as_json = True )
519+ _echo_payload (payload , as_json = True )
520+ return
521+ if payload .get ("found" ) is False :
522+ raise click .ClickException (f"Engine not found: { engine_id } " )
523+ if not payload .get ("reset" ):
524+ raise click .ClickException (payload .get ("error" , "Unable to reset engine config" ))
525+ success (f"Engine config reset: { engine_id } " )
526+ plain (f" Path: { payload .get ('path' )} " )
527+
453528 @engine .command ("dry-run" )
454529 @click .argument ("engine_id" )
455530 @click .argument ("file_path" , type = click .Path (exists = True ))
@@ -500,6 +575,83 @@ def engine_compile(engine_id, file_path, workspace, as_json):
500575 else :
501576 raise click .ClickException (result .get ("error" , "Compilation failed" ))
502577
578+ @cli .group ()
579+ def venv ():
580+ """Manage workspace virtual environment preferences."""
581+
582+ @venv .command ("status" )
583+ @click .argument ("workspace" , required = False , type = click .Path (exists = False ))
584+ @click .option ("--json" , "as_json" , is_flag = True )
585+ def venv_status (workspace , as_json ):
586+ payload = venv_status_payload (_resolve_workspace_path (workspace or "." ))
587+ if as_json :
588+ if not payload .get ("ok" ):
589+ _emit_and_exit (payload , EXIT_WORKSPACE_INVALID , as_json = True )
590+ _echo_payload (payload , as_json = True )
591+ return
592+ if not payload .get ("ok" ):
593+ raise click .ClickException (payload .get ("error" , "Unable to inspect venv status" ))
594+ plain (f"Workspace: { payload .get ('workspace' )} " )
595+ plain (f" Mode: { payload .get ('mode' )} " )
596+ plain (f" Venv: { payload .get ('venv_path' ) or '(none)' } " )
597+ plain (f" Pref file: { payload .get ('pref_path' )} " )
598+
599+ @venv .command ("use-system" )
600+ @click .argument ("workspace" , required = False , type = click .Path (exists = False ))
601+ @click .option ("--json" , "as_json" , is_flag = True )
602+ def venv_use_system (workspace , as_json ):
603+ payload = venv_use_system_payload (_resolve_workspace_path (workspace or "." ))
604+ if as_json :
605+ if not payload .get ("ok" ):
606+ _emit_and_exit (payload , EXIT_WORKSPACE_INVALID , as_json = True )
607+ _echo_payload (payload , as_json = True )
608+ return
609+ if not payload .get ("ok" ):
610+ raise click .ClickException (payload .get ("error" , "Unable to set system python mode" ))
611+ success ("Workspace venv mode set to system" )
612+
613+ @venv .command ("use-venv" )
614+ @click .argument ("workspace" , required = False , type = click .Path (exists = False ))
615+ @click .argument ("venv_path" , required = False , type = click .Path (exists = False ))
616+ @click .option ("--create" , is_flag = True , help = "Create .venv when no venv path is provided" )
617+ @click .option ("--json" , "as_json" , is_flag = True )
618+ def venv_use_venv (workspace , venv_path , create , as_json ):
619+ payload = venv_use_venv_payload (
620+ _resolve_workspace_path (workspace or "." ),
621+ venv_path = venv_path ,
622+ create_if_missing = bool (create ),
623+ )
624+ if as_json :
625+ if not payload .get ("ok" ):
626+ _emit_and_exit (payload , EXIT_PRECHECK_FAILED , as_json = True )
627+ _echo_payload (payload , as_json = True )
628+ return
629+ if not payload .get ("ok" ):
630+ raise click .ClickException (payload .get ("error" , "Unable to set venv mode" ))
631+ success ("Workspace venv mode set to venv" )
632+ plain (f" Venv: { payload .get ('venv_path' )} " )
633+
634+ @venv .command ("install-req" )
635+ @click .argument ("workspace" , required = False , type = click .Path (exists = False ))
636+ @click .option ("--force-pip" , is_flag = True , help = "Force pip-based install mode" )
637+ @click .option ("--json" , "as_json" , is_flag = True )
638+ def venv_install_req (workspace , force_pip , as_json ):
639+ payload = venv_install_requirements_payload (
640+ _resolve_workspace_path (workspace or "." ),
641+ force_pip = bool (force_pip ),
642+ )
643+ if as_json :
644+ if not payload .get ("ok" ):
645+ _emit_and_exit (payload , EXIT_PRECHECK_FAILED , as_json = True )
646+ _echo_payload (payload , as_json = True )
647+ return
648+ if not payload .get ("ok" ):
649+ raise click .ClickException (payload .get ("error" , "Requirements installation failed" ))
650+ if payload .get ("installed" ) is False :
651+ info (payload .get ("reason" , "No requirements installation needed" ))
652+ return
653+ success ("Requirements installed" )
654+
503655 # Enregistrement modulaire des commandes workspace.
504656 register_workspace_commands (
505657 cli = cli ,
@@ -510,6 +662,8 @@ def engine_compile(engine_id, file_path, workspace, as_json):
510662 workspace_init_emit = _workspace_init_emit ,
511663 workspace_config_auto_emit = _workspace_config_auto_emit ,
512664 workspace_inspect_payload = workspace_inspect_payload ,
665+ workspace_entrypoint_set_payload = workspace_entrypoint_set_payload ,
666+ workspace_entrypoint_clear_payload = workspace_entrypoint_clear_payload ,
513667 )
514668
515669 # Enregistrement modulaire des commandes qualité (doctor/check).
0 commit comments