Skip to main content

wekan_cli/
lib.rs

1pub mod app;
2pub mod cli;
3pub mod client;
4pub mod command_result;
5pub mod commands;
6pub mod config;
7pub mod credentials;
8pub mod error;
9pub mod exit_code;
10pub mod input;
11pub mod output;
12pub mod redaction;
13mod update;
14
15use std::{
16    env,
17    ffi::OsString,
18    io::{self, Write},
19    process::ExitCode,
20};
21
22use clap::{CommandFactory, Parser, error::ErrorKind};
23
24use crate::{
25    app::App,
26    cli::Cli,
27    config::ServerSelection,
28    error::AppError,
29    output::{OutputFormat, render_error, write_success},
30};
31
32/// Parse the process arguments, execute one command, render its result, and
33/// return the stable process exit code.
34pub fn run() -> impl std::future::Future<Output = ExitCode> {
35    // Keep the complete command/result state off the process entry future's stack.
36    // Nesting it in Tokio's main future exceeds the Windows startup stack once
37    // the native response models grow, even for help and argument errors.
38    Box::pin(run_command())
39}
40
41async fn run_command() -> ExitCode {
42    let args: Vec<OsString> = env::args_os().collect();
43    let cli = match Cli::try_parse_from(&args) {
44        Ok(cli) => cli,
45        Err(error)
46            if matches!(
47                error.kind(),
48                ErrorKind::DisplayHelp | ErrorKind::DisplayVersion
49            ) =>
50        {
51            let code = error.exit_code();
52            let _ = error.print();
53            return ExitCode::from(code as u8);
54        }
55        Err(error) => {
56            let requested_output = OutputFormat::detect_from_args(&args);
57            let app_error = AppError::invalid_input(error.to_string());
58            eprintln!("{}", render_error(requested_output, &app_error));
59            return app_error.exit_code().into();
60        }
61    };
62
63    let output_format = cli.output;
64    if let Err(error) = cli.command.validate_output(output_format) {
65        eprintln!("{}", render_error(output_format, &error));
66        return error.exit_code().into();
67    }
68    let app = App::production_with_confirmation_interactivity(
69        ServerSelection::new(cli.server, cli.profile_name, cli.allow_insecure_http),
70        output_format != OutputFormat::Json,
71    );
72    match app.check_for_update(output_format, &cli.command).await {
73        Ok(outcome) => {
74            if let Err(error) = output::write_update_notice(&outcome, &mut io::stderr()) {
75                eprintln!("{}", render_error(output_format, &error));
76                return error.exit_code().into();
77            }
78            if outcome.should_exit() {
79                return ExitCode::SUCCESS;
80            }
81        }
82        Err(error) => {
83            eprintln!("{}", render_error(output_format, &error));
84            return error.exit_code().into();
85        }
86    }
87
88    match app.execute(cli.command).await {
89        Ok(success) => {
90            let mut stdout = io::stdout().lock();
91            let mut stderr = io::stderr().lock();
92            match write_success(output_format, success, &mut stdout, &mut stderr).await {
93                Ok(exit_code) => exit_code,
94                Err(error) => {
95                    let _ = writeln!(stderr, "{}", render_error(output_format, &error));
96                    error.exit_code().into()
97                }
98            }
99        }
100        Err(error) => {
101            eprintln!("{}", render_error(output_format, &error));
102            error.exit_code().into()
103        }
104    }
105}
106
107/// Return the generated Clap command for completion and contract tests.
108pub fn command() -> clap::Command {
109    Cli::command()
110}