1use clap::{Parser, Subcommand};
2
3use crate::{
4 Color, ConfigFile, Environment, ServiceCommand, ServiceConfig, ServiceManagerKind, ServiceName,
5};
6
7mod boilerplate_args;
8mod deployment_args;
9mod service_args;
10
11pub use boilerplate_args::*;
12pub use deployment_args::DeploymentArgs;
13pub use service_args::ServiceArgs;
14
15#[derive(Clone, Debug, Default, Parser)]
16pub struct Args {
17 #[arg(short, long, action = clap::ArgAction::Count, global = true)]
19 pub verbose: u8,
20
21 #[arg(short = 'H', long, global = true)]
23 pub host: Option<String>,
24
25 #[arg(short = 'P', long, global = true)]
27 pub port: Option<i32>,
28
29 #[arg(short, long, global = true)]
31 pub environment: Option<Environment>,
32
33 #[clap(long, short, global = true)]
35 pub name: Option<ServiceName>,
36
37 #[clap(long, value_enum, global = true)]
39 pub service_manager: Option<ServiceManagerKind>,
40
41 #[clap(long, global = true)]
43 pub system: bool,
44
45 #[clap(long, global = true, default_value = "auto")]
47 pub color: Color,
48
49 #[clap(long, short)]
51 pub config_file: Option<ConfigFile>,
52
53 #[command(subcommand)]
54 pub command: Option<Commands>,
55}
56
57#[derive(Clone, Debug, Subcommand, PartialEq)]
58#[clap(rename_all = "kebab-case")]
59pub enum Commands {
60 Service(ServiceArgs),
61 Deploy(DeploymentArgs),
62 Generate(BoilerplateArgs),
63 Container(DeploymentArgs),
64}
65
66impl From<ServiceArgs> for Commands {
67 fn from(args: ServiceArgs) -> Self {
68 Commands::Service(args)
69 }
70}
71
72impl From<ServiceCommand> for Commands {
73 fn from(command: ServiceCommand) -> Self {
74 ServiceArgs::from(command).into()
75 }
76}
77
78impl Args {
79 pub fn config(&self) -> ConfigFile {
80 let service_config = self.service();
81
82 self.config_file
83 .clone()
84 .unwrap_or_else(|| service_config.name().into())
85 }
86
87 pub fn service(&self) -> ServiceConfig {
88 ServiceConfig::builder()
89 .maybe_name(self.name.clone())
90 .maybe_service_manager(self.service_manager)
91 .system(self.system)
92 .build()
93 }
94}
95
96#[test]
97fn setting_verbosity_with_args() -> Result<(), Box<dyn std::error::Error>> {
98 use crate::{Configuration, Verbosity};
99
100 let expectations = [
101 ("app", Verbosity::Off),
102 ("app -v", Verbosity::Error),
103 ("app -vv", Verbosity::Warn),
104 ("app -vvv", Verbosity::Info),
105 ("app -vvvv", Verbosity::Debug),
106 ("app -vvvvv", Verbosity::Trace),
107 ];
108
109 for (input, expected) in expectations.iter() {
110 let args = Args::try_parse_from(input.split_whitespace())?;
111
112 assert_eq!(
113 Configuration::from(args),
114 Configuration::builder().verbosity(*expected).build()
115 );
116 }
117 Ok(())
118}
119
120#[test]
121fn config_file() -> Result<(), Box<dyn std::error::Error>> {
122 let expectations = [
123 ("app", "support-kit"),
124 ("app --config-file custom.config", "custom.config"),
125 ];
126
127 for (input, expected) in expectations {
128 let args = Args::try_parse_from(input.split_whitespace())?;
129
130 assert_eq!(args.config(), expected.into());
131 }
132
133 figment::Jail::expect_with(|jail| {
134 jail.set_env("CARGO_PKG_NAME", "custom-package");
135
136 let args = Args::try_parse_from("app".split_whitespace()).unwrap();
137
138 assert_eq!(args.config(), "custom-package".into());
139
140 Ok(())
141 });
142
143 Ok(())
144}
145
146#[test]
147fn setting_environment_with_args() -> Result<(), Box<dyn std::error::Error>> {
148 use crate::Configuration;
149
150 let expectations = [
151 ("app", None),
152 (
153 "app --environment development",
154 Some(Environment::Development),
155 ),
156 (
157 "app --environment production",
158 Some(Environment::Production),
159 ),
160 ("app --environment test", Some(Environment::Test)),
161 ];
162
163 for (input, expected) in expectations {
164 let args = Args::try_parse_from(input.split_whitespace())?;
165
166 assert_eq!(
167 Configuration::from(args),
168 Configuration::builder().maybe_environment(expected).build()
169 );
170 }
171 Ok(())
172}
173
174#[test]
175fn setting_color_with_args() -> Result<(), Box<dyn std::error::Error>> {
176 use crate::Configuration;
177
178 let expectations = [
179 ("app", Color::Auto),
180 ("app --color always", Color::Always),
181 ("app --color never", Color::Never),
182 ("app --color auto", Color::Auto),
183 ];
184
185 for (input, expected) in expectations {
186 let args = Args::try_parse_from(input.split_whitespace())?;
187
188 assert_eq!(
189 Configuration::from(args),
190 Configuration::builder().color(expected).build()
191 );
192 }
193 Ok(())
194}
195
196#[test]
197fn setting_server_with_args() -> Result<(), Box<dyn std::error::Error>> {
198 use crate::{Configuration, NetworkConfig};
199 let expectations = [
200 ("app", NetworkConfig::default()),
201 ("app -H localhost", NetworkConfig::from("localhost")),
202 ("app -P 8080", NetworkConfig::builder().port(8080).build()),
203 (
204 "app -H localhost -P 8080",
205 NetworkConfig::from(("localhost", 8080)),
206 ),
207 ];
208
209 for (input, expected) in expectations {
210 let args = Args::try_parse_from(input.split_whitespace())?;
211
212 assert_eq!(
213 Configuration::from(args),
214 Configuration::builder().server(expected.clone()).build()
215 );
216 }
217 Ok(())
218}