11import asyncio
22import json
33import logging
4+ import shutil
5+ import subprocess
6+ from pathlib import Path
47from typing import NoReturn
58
69import typer
1215from antares .errors import ConnectionError , SimulationError , SubscriptionError
1316from antares .logger import setup_logging
1417
15- app = typer .Typer (name = "antares" , help = "Antares CLI for ship simulation" , no_args_is_help = True )
18+ app = typer .Typer (name = "antares-cli " , help = "Antares CLI for ship simulation" , no_args_is_help = True )
1619console = Console (theme = Theme ({"info" : "green" , "warn" : "yellow" , "error" : "bold red" }))
1720
1821
19- def handle_error (message : str , code : int , json_output : bool = False ) -> NoReturn :
20- logger = logging .getLogger ("antares.cli" )
21- if json_output :
22- typer .echo (json .dumps ({"error" : message }), err = True )
23- else :
24- console .print (f"[error]{ message } " )
25- logger .error ("Exiting with error: %s" , message )
26- raise typer .Exit (code )
27-
28-
29- def build_client (config_path : str | None , verbose : bool , json_output : bool ) -> AntaresClient :
30- setup_logging (level = logging .DEBUG if verbose else logging .INFO )
31- logger = logging .getLogger ("antares.cli" )
22+ @app .command ()
23+ def start (
24+ executable : str = typer .Option ("antares" , help = "Path to the Antares executable" ),
25+ config : str | None = typer .Option (None , help = "Path to the TOML configuration file" ),
26+ verbose : bool = typer .Option (False , "--verbose" , "-v" , help = "Enable verbose output" ),
27+ json_output : bool = typer .Option (False , "--json" , help = "Output in JSON format" ),
28+ ) -> None :
29+ """
30+ Start the Antares simulation engine in the background.
31+
32+ This command attempts to locate and launch the Antares executable either from the system's PATH
33+ or from the provided path using the --executable option. If a config path is provided, it is
34+ passed to the executable via --config.
35+
36+ This command does not use the Python client and directly invokes the native binary.
37+ """
38+ # Locate executable (either absolute path or in system PATH)
39+ path = shutil .which (executable ) if not Path (executable ).exists () else executable
40+ if path is None :
41+ msg = f"Executable '{ executable } ' not found in PATH or at specified location."
42+ console .print (f"[error]{ msg } " )
43+ raise typer .Exit (1 )
44+
45+ # Prepare command
46+ command = [path ]
47+ if config :
48+ command += ["--config" , config ]
49+
50+ if verbose :
51+ console .print (f"[info]Starting Antares with command: { command } " )
3252
3353 try :
34- settings = load_config (config_path )
35- if verbose :
36- console .print (f"[info]Using settings: { settings .model_dump ()} " )
37- logger .debug ("Loaded settings: %s" , settings .model_dump ())
38- return AntaresClient (
39- base_url = settings .base_url ,
40- tcp_host = settings .tcp_host ,
41- tcp_port = settings .tcp_port ,
42- timeout = settings .timeout ,
43- auth_token = settings .auth_token ,
44- )
54+ process = subprocess .Popen (command , stdout = subprocess .DEVNULL , stderr = subprocess .DEVNULL )
4555 except Exception as e :
46- handle_error (f"Failed to load configuration: { e } " , code = 1 , json_output = json_output )
56+ msg = f"Failed to start Antares: { e } "
57+ if json_output :
58+ typer .echo (json .dumps ({"error" : msg }), err = True )
59+ else :
60+ console .print (f"[error]{ msg } " )
61+ raise typer .Exit (2 ) from e
62+
63+ msg = f"Antares started in background with PID { process .pid } "
64+ if json_output :
65+ typer .echo (json .dumps ({"message" : msg , "pid" : process .pid }))
66+ else :
67+ console .print (f"[success]{ msg } " )
4768
4869
4970@app .command ()
5071def reset (
51- config : str = typer .Option (None ),
52- verbose : bool = typer .Option (False , "--verbose" , "-v" ),
72+ config : str = typer .Option (None , help = "Path to the TOML configuration file" ),
73+ verbose : bool = typer .Option (False , "--verbose" , "-v" , help = "Enable verbose output" ),
5374 json_output : bool = typer .Option (False , "--json" , help = "Output in JSON format" ),
5475) -> None :
76+ """
77+ Reset the current simulation state.
78+ """
5579 client = build_client (config , verbose , json_output )
5680 try :
5781 client .reset_simulation ()
@@ -65,10 +89,13 @@ def reset(
6589def add_ship (
6690 x : float = typer .Option (..., help = "X coordinate of the ship" ),
6791 y : float = typer .Option (..., help = "Y coordinate of the ship" ),
68- config : str = typer .Option (None , help = "Path to the configuration file" ),
92+ config : str = typer .Option (None , help = "Path to the TOML configuration file" ),
6993 verbose : bool = typer .Option (False , "--verbose" , "-v" , help = "Enable verbose output" ),
7094 json_output : bool = typer .Option (False , "--json" , help = "Output in JSON format" ),
7195) -> None :
96+ """
97+ Add a ship to the simulation with the specified parameters.
98+ """
7299 client = build_client (config , verbose , json_output )
73100 try :
74101 ship = ShipConfig (initial_position = (x , y ))
@@ -81,11 +108,14 @@ def add_ship(
81108
82109@app .command ()
83110def subscribe (
84- config : str = typer .Option (None ),
85- verbose : bool = typer .Option (False , "--verbose" , "-v" ),
111+ config : str = typer .Option (None , help = "Path to the TOML configuration file" ),
112+ verbose : bool = typer .Option (False , "--verbose" , "-v" , help = "Enable verbose output" ),
86113 json_output : bool = typer .Option (False , "--json" , help = "Output in JSON format" ),
87114 log_file : str = typer .Option ("antares.log" , help = "Path to log file" ),
88115) -> None :
116+ """
117+ Subscribe to simulation events and print them to the console.
118+ """
89119 setup_logging (log_file = log_file , level = logging .DEBUG if verbose else logging .INFO )
90120 logger = logging .getLogger ("antares.cli" )
91121
@@ -103,3 +133,36 @@ async def _sub() -> None:
103133 handle_error (str (e ), code = 3 , json_output = json_output )
104134
105135 asyncio .run (_sub ())
136+
137+
138+ def handle_error (message : str , code : int , json_output : bool = False ) -> NoReturn :
139+ """
140+ Handle errors by logging and printing them to the console.
141+ """
142+ logger = logging .getLogger ("antares.cli" )
143+ if json_output :
144+ typer .echo (json .dumps ({"error" : message }), err = True )
145+ else :
146+ console .print (f"[error]{ message } " )
147+ logger .error ("Exiting with error: %s" , message )
148+ raise typer .Exit (code )
149+
150+
151+ def build_client (config_path : str | None , verbose : bool , json_output : bool ) -> AntaresClient :
152+ """
153+ Build the Antares client using the provided configuration file.
154+ """
155+
156+ try :
157+ settings = load_config (config_path )
158+ if verbose :
159+ console .print (f"[info]Using settings: { settings .model_dump ()} " )
160+ return AntaresClient (
161+ host = settings .host ,
162+ http_port = settings .http_port ,
163+ tcp_port = settings .tcp_port ,
164+ timeout = settings .timeout ,
165+ auth_token = settings .auth_token ,
166+ )
167+ except Exception as e :
168+ handle_error (f"Failed to load configuration: { e } " , code = 1 , json_output = json_output )
0 commit comments