Skip to main content

plan_issue_cli/
lib.rs

1pub mod cli;
2pub mod commands;
3mod completion;
4mod execute;
5mod github;
6mod issue_body;
7pub mod output;
8mod render;
9mod task_spec;
10
11use std::ffi::OsString;
12
13use clap::Parser;
14use serde_json::json;
15
16use crate::cli::Cli;
17use crate::commands::Command;
18
19pub const EXIT_SUCCESS: i32 = 0;
20pub const EXIT_FAILURE: i32 = 1;
21pub const EXIT_USAGE: i32 = 2;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum BinaryFlavor {
25    PlanIssue,
26    PlanIssueLocal,
27}
28
29impl BinaryFlavor {
30    pub fn binary_name(self) -> &'static str {
31        match self {
32            Self::PlanIssue => "plan-issue",
33            Self::PlanIssueLocal => "plan-issue-local",
34        }
35    }
36
37    pub fn execution_mode(self) -> &'static str {
38        match self {
39            Self::PlanIssue => "live",
40            Self::PlanIssueLocal => "local",
41        }
42    }
43}
44
45#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct ValidationError {
47    pub code: &'static str,
48    pub message: String,
49}
50
51impl ValidationError {
52    pub fn new(code: &'static str, message: impl Into<String>) -> Self {
53        Self {
54            code,
55            message: message.into(),
56        }
57    }
58}
59
60#[derive(Debug, Clone, PartialEq, Eq)]
61pub struct CommandError {
62    pub code: &'static str,
63    pub message: String,
64    pub exit_code: i32,
65}
66
67impl CommandError {
68    pub fn new(code: &'static str, message: impl Into<String>, exit_code: i32) -> Self {
69        Self {
70            code,
71            message: message.into(),
72            exit_code,
73        }
74    }
75
76    pub fn runtime(code: &'static str, message: impl Into<String>) -> Self {
77        Self::new(code, message, EXIT_FAILURE)
78    }
79
80    pub fn usage(code: &'static str, message: impl Into<String>) -> Self {
81        Self::new(code, message, EXIT_USAGE)
82    }
83}
84
85pub fn run(binary: BinaryFlavor) -> i32 {
86    run_with_args(binary, std::env::args_os())
87}
88
89pub fn run_with_args<I, T>(binary: BinaryFlavor, args: I) -> i32
90where
91    I: IntoIterator<Item = T>,
92    T: Into<OsString> + Clone,
93{
94    let cli = match Cli::try_parse_from(args) {
95        Ok(cli) => cli,
96        Err(err) => {
97            let code = if err.use_stderr() {
98                EXIT_USAGE
99            } else {
100                EXIT_SUCCESS
101            };
102            let _ = err.print();
103            return code;
104        }
105    };
106
107    if let Command::Completion(args) = &cli.command {
108        return completion::run(binary, args.shell);
109    }
110
111    let output_format = match cli.resolve_output_format() {
112        Ok(format) => format,
113        Err(err) => {
114            eprintln!("error: {}", err.message);
115            return EXIT_USAGE;
116        }
117    };
118
119    if let Err(err) = cli.validate() {
120        let schema_version = cli.command.schema_version();
121        if let Err(render_err) = output::emit_error(
122            output_format,
123            &schema_version,
124            cli.command.command_id(),
125            err.code,
126            &err.message,
127        ) {
128            eprintln!("error: {render_err}");
129        }
130        return EXIT_FAILURE;
131    }
132
133    let execution_result = match execute::execute(binary, &cli) {
134        Ok(result) => result,
135        Err(err) => {
136            let schema_version = cli.command.schema_version();
137            if let Err(render_err) = output::emit_error(
138                output_format,
139                &schema_version,
140                cli.command.command_id(),
141                err.code,
142                &err.message,
143            ) {
144                eprintln!("error: {render_err}");
145            }
146            return err.exit_code;
147        }
148    };
149
150    let schema_version = cli.command.schema_version();
151    let payload = json!({
152        "binary": binary.binary_name(),
153        "execution_mode": binary.execution_mode(),
154        "dry_run": cli.dry_run,
155        "repo": cli.repo,
156        "arguments": cli.command.payload(),
157        "result": execution_result,
158    });
159
160    if let Err(err) = output::emit_success(
161        output_format,
162        &schema_version,
163        cli.command.command_id(),
164        &payload,
165    ) {
166        eprintln!("error: {err}");
167        return EXIT_FAILURE;
168    }
169
170    EXIT_SUCCESS
171}