LCOV - code coverage report
Current view: top level - dashql-cli/src - main.rs Coverage Total Hit
Test: DashQL 6e74948 Lines: 0.0 % 110 0
Test Date: 2026-11-06 17:46:50

            Line data    Source code
       1              : //! dashql: the command line tool of DashQL.
       2              : //! `dashql agent` reaches the DashQL session that is open in the browser or the desktop app through the local agent
       3              : //! server, which `dashql agent serve` runs. `dashql lint` lints SQL files on disk with the native dashql-core.
       4              : 
       5              : mod agent;
       6              : mod inputs;
       7              : mod lint;
       8              : mod lint_files;
       9              : mod report;
      10              : 
      11              : use agent::client::{self, CliEnvironment, EXIT_FAILED, EXIT_OK, EXIT_USAGE};
      12              : use agent::config::{agent_home, load_config};
      13              : use agent::server::{log_line, AgentServer, AgentServerOptions, AGENT_RUN_ENV};
      14              : use std::io::Write;
      15              : use std::process::ExitCode;
      16              : 
      17              : const USAGE: &str = "Usage: dashql <command> [options]
      18              : 
      19              : Commands:
      20              :   agent <command>                     Work with the DashQL session that is open in the browser or the desktop app,
      21              :                                       through the local agent server, see dashql agent --help
      22              :   lint <file.sql|dir|->...            Lint SQL files on disk with dashql-core, see dashql lint --help";
      23              : 
      24            0 : fn env_var(name: &str) -> Option<String> {
      25            0 :     std::env::var(name).ok()
      26            0 : }
      27              : 
      28              : /// Run the agent server in the foreground until it is interrupted, or until stdin closes with exit_with_stdin.
      29              : /// DashQL.app holds the other end of stdin, so the server stops with the app even when the app crashes.
      30            0 : fn serve(exit_with_stdin: bool) -> ExitCode {
      31              :     // An agent in a harness run may not start a server, whatever its harness allows
      32            0 :     if env_var(AGENT_RUN_ENV).is_some() {
      33            0 :         eprintln!("dashql agent serve cannot run inside an agent run");
      34            0 :         return ExitCode::from(EXIT_USAGE);
      35            0 :     }
      36            0 :     let home = agent_home(&env_var);
      37            0 :     let config = match load_config(&home, &env_var) {
      38            0 :         Ok(config) => config,
      39            0 :         Err(e) => {
      40            0 :             eprintln!("Cannot read the agent configuration: {e}");
      41            0 :             return ExitCode::from(EXIT_USAGE);
      42              :         }
      43              :     };
      44            0 :     let runtime = match tokio::runtime::Builder::new_current_thread().enable_all().build() {
      45            0 :         Ok(runtime) => runtime,
      46            0 :         Err(e) => {
      47            0 :             eprintln!("Cannot start the agent server: {e}");
      48            0 :             return ExitCode::from(EXIT_FAILED);
      49              :         }
      50              :     };
      51            0 :     runtime.block_on(async {
      52            0 :         let port = config.port;
      53            0 :         let mut options = AgentServerOptions::new(config, home);
      54              :         // Harness runs call this executable as dashql
      55            0 :         options.cli = std::env::current_exe().ok();
      56            0 :         options.log = true;
      57            0 :         let server = match AgentServer::listen(options).await {
      58            0 :             Ok(server) => server,
      59            0 :             Err(e) if e.kind() == std::io::ErrorKind::AddrInUse => {
      60            0 :                 eprintln!(
      61              :                     "Port {port} is taken. Is DashQL already running? Set DASHQL_AGENT_PORT to use another port."
      62              :                 );
      63            0 :                 return ExitCode::from(EXIT_FAILED);
      64              :             }
      65            0 :             Err(e) => {
      66            0 :                 eprintln!("Cannot start the agent server: {e}");
      67            0 :                 return ExitCode::from(EXIT_FAILED);
      68              :             }
      69              :         };
      70            0 :         let stdin_closed = async {
      71            0 :             if !exit_with_stdin {
      72            0 :                 return std::future::pending().await;
      73            0 :             }
      74            0 :             let (closed, on_closed) = tokio::sync::oneshot::channel::<()>();
      75            0 :             std::thread::spawn(move || {
      76            0 :                 let _ = std::io::copy(&mut std::io::stdin(), &mut std::io::sink());
      77            0 :                 let _ = closed.send(());
      78            0 :             });
      79            0 :             let _ = on_closed.await;
      80            0 :         };
      81            0 :         tokio::select! {
      82            0 :             _ = shutdown_signal() => log_line("Stopping: received a stop signal"),
      83            0 :             _ = stdin_closed => log_line("Stopping: stdin closed"),
      84              :         }
      85            0 :         server.close().await;
      86            0 :         ExitCode::from(EXIT_OK)
      87            0 :     })
      88            0 : }
      89              : 
      90            0 : async fn shutdown_signal() {
      91              :     #[cfg(unix)]
      92              :     {
      93              :         use tokio::signal::unix::{signal, SignalKind};
      94            0 :         if let Ok(mut terminate) = signal(SignalKind::terminate()) {
      95            0 :             tokio::select! {
      96            0 :                 _ = tokio::signal::ctrl_c() => {}
      97            0 :                 _ = terminate.recv() => {}
      98              :             }
      99            0 :             return;
     100            0 :         }
     101              :     }
     102            0 :     let _ = tokio::signal::ctrl_c().await;
     103            0 : }
     104              : 
     105              : /// Run `dashql agent` with the arguments after `agent`
     106            0 : fn agent(argv: &[String]) -> ExitCode {
     107            0 :     let options = match client::parse_args(argv) {
     108            0 :         Ok(options) => options,
     109            0 :         Err(e) => {
     110            0 :             eprintln!("{e}\n\n{}", client::usage());
     111            0 :             return ExitCode::from(EXIT_USAGE);
     112              :         }
     113              :     };
     114            0 :     if options.help || options.positionals.first().map(String::as_str) == Some("help") {
     115            0 :         println!("{}", client::usage());
     116            0 :         return ExitCode::SUCCESS;
     117            0 :     }
     118            0 :     if options.positionals == ["serve"] {
     119            0 :         return serve(options.exit_with_stdin);
     120            0 :     }
     121            0 :     let config = match load_config(&agent_home(&env_var), &env_var) {
     122            0 :         Ok(config) => config,
     123            0 :         Err(e) => {
     124            0 :             eprintln!("Cannot read the agent configuration: {e}");
     125            0 :             return ExitCode::from(EXIT_USAGE);
     126              :         }
     127              :     };
     128            0 :     let read_file = |file: &str| {
     129            0 :         let path = inputs::working_directory().map_err(|e| format!("working directory: {e}"))?.join(file);
     130            0 :         std::fs::read_to_string(&path).map_err(|e| format!("{}: {e}", path.display()))
     131            0 :     };
     132            0 :     let write_file = |file: &str, bytes: &[u8]| {
     133            0 :         let path = inputs::working_directory().map_err(|e| format!("working directory: {e}"))?.join(file);
     134            0 :         std::fs::write(&path, bytes).map_err(|e| format!("{}: {e}", path.display()))
     135            0 :     };
     136            0 :     let output = client::run_cli(
     137            0 :         &options,
     138              :         &CliEnvironment {
     139            0 :             session: env_var("DASHQL_SESSION"),
     140            0 :             read_stdin: &|| std::io::read_to_string(std::io::stdin()),
     141            0 :             read_file: &read_file,
     142            0 :             write_file: &write_file,
     143            0 :             request: &|method, path, body| client::request(&config, method, path, body),
     144              :         },
     145              :     );
     146            0 :     let _ = std::io::stdout().write_all(output.stdout.as_bytes());
     147            0 :     let _ = std::io::stderr().write_all(output.stderr.as_bytes());
     148            0 :     ExitCode::from(output.exit_code)
     149            0 : }
     150              : 
     151            0 : fn main() -> ExitCode {
     152            0 :     let argv: Vec<String> = std::env::args().skip(1).collect();
     153            0 :     match argv.first().map(String::as_str) {
     154            0 :         Some("agent") => agent(&argv[1..]),
     155            0 :         Some("lint") => lint_files::main(&argv[1..]),
     156            0 :         Some("help" | "-h" | "--help") => {
     157            0 :             println!("{USAGE}");
     158            0 :             ExitCode::SUCCESS
     159              :         }
     160            0 :         Some(command) => {
     161            0 :             eprintln!("Unknown command: {command}\n\n{USAGE}");
     162            0 :             ExitCode::from(EXIT_USAGE)
     163              :         }
     164              :         None => {
     165            0 :             eprintln!("{USAGE}");
     166            0 :             ExitCode::from(EXIT_USAGE)
     167              :         }
     168              :     }
     169            0 : }
        

Generated by: LCOV version 2.0-1