1use std::collections::BTreeMap;
4
5use anyhow::Result;
6use clap::{Parser, Subcommand};
7
8use crate::{auth, verbs};
9
10enum ExitCode {
15 Success,
17 RuntimeError,
19 UsageError,
21}
22
23impl From<ExitCode> for i32 {
24 fn from(code: ExitCode) -> Self {
25 match code {
26 ExitCode::Success => 0,
27 ExitCode::RuntimeError => 1,
28 ExitCode::UsageError => 2,
29 }
30 }
31}
32
33#[derive(Debug, Parser)]
34#[command(
35 name = "hm cloud",
36 about = "Talk to the Harmont cloud API",
37 disable_help_subcommand = true
38)]
39struct CloudCli {
40 #[command(subcommand)]
41 command: CloudCommand,
42}
43
44#[derive(Debug, Clone, Subcommand)]
45pub enum CloudCommand {
46 Login {
48 #[arg(long)]
50 paste: bool,
51 },
52 Logout,
54 Whoami,
56 #[command(subcommand)]
58 Org(OrgCommand),
59 #[command(subcommand)]
61 Pipeline(PipelineCommand),
62 #[command(subcommand)]
64 Build(BuildCommand),
65 #[command(subcommand)]
67 Job(JobCommand),
68 #[command(subcommand)]
70 Billing(BillingCommand),
71 Run(verbs::run::RunArgs),
73}
74
75#[derive(Debug, Clone, Subcommand)]
76pub enum OrgCommand {
77 Switch {
79 slug: String,
81 },
82}
83
84#[derive(Debug, Clone, Subcommand)]
85pub enum PipelineCommand {
86 List,
88 Show { slug: String },
90}
91
92#[derive(Debug, Clone, Subcommand)]
93pub enum BuildCommand {
94 List {
96 #[arg(short, long)]
97 pipeline: String,
98 },
99 Show {
101 #[arg(short, long)]
102 pipeline: String,
103 number: i64,
104 },
105 Cancel {
107 #[arg(short, long)]
108 pipeline: String,
109 number: i64,
110 },
111 Watch {
113 #[arg(short, long)]
114 pipeline: String,
115 number: i64,
116 },
117}
118
119#[derive(Debug, Clone, Subcommand)]
120pub enum JobCommand {
121 List {
123 #[arg(short, long)]
124 pipeline: String,
125 #[arg(short, long)]
126 build: i64,
127 },
128 Show {
130 #[arg(short, long)]
131 pipeline: String,
132 #[arg(short, long)]
133 build: i64,
134 job_id: String,
135 },
136 Log {
138 #[arg(short, long)]
139 pipeline: String,
140 #[arg(short, long)]
141 build: i64,
142 job_id: String,
143 },
144}
145
146#[derive(Debug, Clone, Subcommand)]
147pub enum BillingCommand {
148 Balance,
150 Transactions {
152 #[arg(long, default_value = "100")]
153 limit: u32,
154 },
155 Usage {
157 #[arg(long)]
158 from: Option<String>,
159 #[arg(long)]
160 to: Option<String>,
161 },
162 Topup {
164 amount_usd: u32,
165 #[arg(long)]
166 no_browser: bool,
167 },
168 Redeem { code: String },
170}
171
172pub async fn dispatch(argv: Vec<String>, env: BTreeMap<String, String>) -> Result<i32> {
175 let mut full: Vec<String> = vec!["hm cloud".to_string()];
176 full.extend(argv.into_iter().skip(1));
177 let parsed = match CloudCli::try_parse_from(&full) {
178 Ok(p) => p,
179 Err(e) => {
180 use clap::error::ErrorKind;
181 let msg = e.to_string();
182 return match e.kind() {
183 ErrorKind::DisplayHelp | ErrorKind::DisplayVersion => {
184 #[allow(clippy::print_stdout)]
185 {
186 use std::io::Write;
187 std::io::stdout().write_all(msg.as_bytes()).ok();
188 }
189 Ok(ExitCode::Success.into())
190 }
191 _ => {
192 #[allow(clippy::print_stderr)]
193 {
194 use std::io::Write;
195 std::io::stderr().write_all(msg.as_bytes()).ok();
196 }
197 Ok(ExitCode::UsageError.into())
198 }
199 };
200 }
201 };
202 dispatch_command(parsed.command, env).await
203}
204
205pub async fn dispatch_command(command: CloudCommand, env: BTreeMap<String, String>) -> Result<i32> {
207 let result = match command {
208 CloudCommand::Login { paste } => auth::login::run(&env, paste).await,
209 CloudCommand::Logout => auth::logout::run(&env).await,
210 CloudCommand::Whoami => auth::whoami::run(&env).await,
211 CloudCommand::Org(cmd) => verbs::org::run(&env, cmd).await,
212 CloudCommand::Pipeline(cmd) => verbs::pipeline::run(&env, cmd).await,
213 CloudCommand::Build(cmd) => verbs::build::run(&env, cmd).await,
214 CloudCommand::Job(cmd) => verbs::job::run(&env, cmd).await,
215 CloudCommand::Billing(cmd) => verbs::billing::run(&env, cmd).await,
216 CloudCommand::Run(args) => verbs::run::run(&env, args).await,
217 };
218 match result {
219 Ok(()) => Ok(ExitCode::Success.into()),
220 Err(e) => {
221 tracing::error!("{e:#}");
222 Ok(ExitCode::RuntimeError.into())
223 }
224 }
225}