|
| 1 | +import sys |
1 | 2 | import asyncio |
2 | | - |
| 3 | +import json |
3 | 4 | import typer |
| 5 | +import logging |
4 | 6 | from rich.console import Console |
5 | 7 | from rich.theme import Theme |
6 | 8 |
|
7 | | -from antares import AntaresClient, ShipConfig |
| 9 | +from antares import ShipConfig, AntaresClient |
8 | 10 | from antares.config_loader import load_config |
9 | | - |
10 | | -app = typer.Typer(help="Antares Simulation CLI") |
11 | | - |
12 | | -console = Console( |
13 | | - theme=Theme( |
14 | | - { |
15 | | - "info": "green", |
16 | | - "warn": "yellow", |
17 | | - "error": "bold red", |
18 | | - } |
19 | | - ) |
| 11 | +from antares.errors import ( |
| 12 | + ConnectionError, |
| 13 | + SimulationError, |
| 14 | + SubscriptionError |
20 | 15 | ) |
21 | | - |
22 | | - |
23 | | -def build_client(config_path: str | None, verbose: bool) -> AntaresClient: |
24 | | - settings = load_config(config_path) |
25 | | - |
26 | | - if verbose: |
27 | | - console.print(f"[info]Using settings: {settings.model_dump()}") |
28 | | - |
29 | | - return AntaresClient( |
30 | | - base_url=settings.base_url, |
31 | | - tcp_host=settings.tcp_host, |
32 | | - tcp_port=settings.tcp_port, |
33 | | - timeout=settings.timeout, |
34 | | - auth_token=settings.auth_token, |
35 | | - ) |
| 16 | +from antares.logger import setup_logging |
| 17 | + |
| 18 | +app = typer.Typer(name="antares", help="Antares CLI for ship simulation") |
| 19 | +console = Console(theme=Theme({ |
| 20 | + "info": "green", |
| 21 | + "warn": "yellow", |
| 22 | + "error": "bold red" |
| 23 | +})) |
| 24 | + |
| 25 | + |
| 26 | +def handle_error(message: str, code: int, json_output: bool = False): |
| 27 | + logger = logging.getLogger("antares.cli") |
| 28 | + if json_output: |
| 29 | + typer.echo(json.dumps({"error": message}), err=True) |
| 30 | + else: |
| 31 | + console.print(f"[error]{message}") |
| 32 | + logger.error("Exiting with error: %s", message) |
| 33 | + raise typer.Exit(code) |
| 34 | + |
| 35 | + |
| 36 | +def build_client(config_path: str | None, verbose: bool, json_output: bool) -> AntaresClient: |
| 37 | + setup_logging(level=logging.DEBUG if verbose else logging.INFO) |
| 38 | + logger = logging.getLogger("antares.cli") |
| 39 | + |
| 40 | + try: |
| 41 | + settings = load_config(config_path) |
| 42 | + if verbose: |
| 43 | + console.print(f"[info]Using settings: {settings.model_dump()}") |
| 44 | + logger.debug("Loaded settings: %s", settings.model_dump()) |
| 45 | + return AntaresClient( |
| 46 | + base_url=settings.base_url, |
| 47 | + tcp_host=settings.tcp_host, |
| 48 | + tcp_port=settings.tcp_port, |
| 49 | + timeout=settings.timeout, |
| 50 | + auth_token=settings.auth_token, |
| 51 | + ) |
| 52 | + except Exception as e: |
| 53 | + handle_error(f"Failed to load configuration: {e}", code=1, json_output=json_output) |
36 | 54 |
|
37 | 55 |
|
38 | 56 | @app.command() |
39 | 57 | def reset( |
40 | | - config: str = typer.Option(None, help="Path to config TOML file"), |
| 58 | + config: str = typer.Option(None), |
41 | 59 | verbose: bool = typer.Option(False, "--verbose", "-v"), |
| 60 | + json_output: bool = typer.Option(False, "--json", help="Output in JSON format") |
42 | 61 | ): |
43 | | - """Reset the simulation.""" |
44 | | - client = build_client(config, verbose) |
45 | | - client.reset_simulation() |
46 | | - console.print("[info]✅ Simulation reset.") |
| 62 | + client = build_client(config, verbose, json_output) |
| 63 | + try: |
| 64 | + client.reset_simulation() |
| 65 | + msg = "✅ Simulation reset." |
| 66 | + typer.echo(json.dumps({"message": msg}) if json_output else msg) |
| 67 | + except (ConnectionError, SimulationError) as e: |
| 68 | + handle_error(str(e), code=2, json_output=json_output) |
47 | 69 |
|
48 | 70 |
|
49 | 71 | @app.command() |
50 | 72 | def add_ship( |
51 | 73 | x: float, |
52 | 74 | y: float, |
53 | | - config: str = typer.Option(None, help="Path to config TOML file"), |
| 75 | + config: str = typer.Option(None), |
54 | 76 | verbose: bool = typer.Option(False, "--verbose", "-v"), |
| 77 | + json_output: bool = typer.Option(False, "--json", help="Output in JSON format") |
55 | 78 | ): |
56 | | - """Add a ship to the simulation at (x, y).""" |
57 | | - client = build_client(config, verbose) |
58 | | - ship = ShipConfig(initial_position=(x, y)) |
59 | | - client.add_ship(ship) |
60 | | - console.print(f"[info]🚢 Added ship at ({x}, {y})") |
| 79 | + client = build_client(config, verbose, json_output) |
| 80 | + try: |
| 81 | + ship = ShipConfig(initial_position=(x, y)) |
| 82 | + client.add_ship(ship) |
| 83 | + msg = f"🚢 Added ship at ({x}, {y})" |
| 84 | + typer.echo(json.dumps({"message": msg}) if json_output else msg) |
| 85 | + except (ConnectionError, SimulationError) as e: |
| 86 | + handle_error(str(e), code=2, json_output=json_output) |
61 | 87 |
|
62 | 88 |
|
63 | 89 | @app.command() |
64 | 90 | def subscribe( |
65 | | - config: str = typer.Option(None, help="Path to config TOML file"), |
| 91 | + config: str = typer.Option(None), |
66 | 92 | verbose: bool = typer.Option(False, "--verbose", "-v"), |
| 93 | + json_output: bool = typer.Option(False, "--json", help="Output in JSON format"), |
| 94 | + log_file: str = typer.Option("antares.log", help="Path to log file") |
67 | 95 | ): |
68 | | - """Subscribe to simulation data stream.""" |
69 | | - client = build_client(config, verbose) |
| 96 | + setup_logging(log_file=log_file, level=logging.DEBUG if verbose else logging.INFO) |
| 97 | + logger = logging.getLogger("antares.cli") |
| 98 | + |
| 99 | + client = build_client(config, verbose, json_output) |
70 | 100 |
|
71 | 101 | async def _sub(): |
72 | | - async for event in client.subscribe(): |
73 | | - console.print_json(data=event) |
| 102 | + try: |
| 103 | + async for event in client.subscribe(): |
| 104 | + if json_output: |
| 105 | + typer.echo(json.dumps(event)) |
| 106 | + else: |
| 107 | + console.print_json(data=event) |
| 108 | + logger.debug("Received event: %s", event) |
| 109 | + except SubscriptionError as e: |
| 110 | + handle_error(str(e), code=3, json_output=json_output) |
74 | 111 |
|
75 | 112 | asyncio.run(_sub()) |
76 | | - |
77 | | - |
78 | | -if __name__ == "__main__": |
79 | | - app() |
|
0 commit comments