support_kit/
args.rs

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    /// The verbosity level to use. Defaults to off.
18    #[arg(short, long, action = clap::ArgAction::Count, global = true)]
19    pub verbose: u8,
20
21    /// The host to bind to.
22    #[arg(short = 'H', long, global = true)]
23    pub host: Option<String>,
24
25    /// The port to bind to.
26    #[arg(short = 'P', long, global = true)]
27    pub port: Option<i32>,
28
29    /// The environment to use.
30    #[arg(short, long, global = true)]
31    pub environment: Option<Environment>,
32
33    /// The service label to use. Defaults to the binary name.
34    #[clap(long, short, global = true)]
35    pub name: Option<ServiceName>,
36
37    /// The kind of service manager to use. Defaults to system native.
38    #[clap(long, value_enum, global = true)]
39    pub service_manager: Option<ServiceManagerKind>,
40
41    /// Install system-wide. If not set, attempts to install for the current user.
42    #[clap(long, global = true)]
43    pub system: bool,
44
45    /// Color output.
46    #[clap(long, global = true, default_value = "auto")]
47    pub color: Color,
48
49    /// The path to the configuration file.
50    #[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}