Skip to main content

gh_download/
cli.rs

1mod config;
2mod help;
3mod resolve;
4mod types;
5
6use std::env;
7use std::ffi::OsString;
8
9use clap::FromArgMatches;
10
11use crate::i18n::Language;
12
13pub use self::help::{command, command_for_language};
14pub(crate) use self::resolve::debug_token_source_label;
15pub(crate) use self::resolve::token_present;
16pub use self::resolve::{
17    pick_token, resolve_cli, resolve_debug, resolve_local_target, resolve_prefix_mode,
18    resolve_proxy_base,
19};
20pub(crate) use self::resolve::{resolve_cli_with_config, resolve_language};
21pub use self::types::{Cli, CliInvocation, PrefixProxyMode, ResolvedOptions};
22
23pub(crate) use self::config::{detect_language_from_args_env_and_config, load_active_config};
24
25pub fn parse_cli_from_env() -> Cli {
26    parse_cli_invocation_from_env().cli
27}
28
29pub fn parse_cli_invocation_from_env() -> CliInvocation {
30    let args: Vec<OsString> = env::args_os().collect();
31    let language = detect_language_from_args_env_and_config(
32        &args,
33        env::var("LC_ALL").ok().as_deref(),
34        env::var("LC_MESSAGES").ok().as_deref(),
35        env::var("LANG").ok().as_deref(),
36    );
37    let args = normalize_args_for_parsing(args);
38    parse_cli_invocation_from_args(args, language)
39}
40
41pub fn parse_cli_from_args<I, T>(args: I, language: Language) -> Cli
42where
43    I: IntoIterator<Item = T>,
44    T: Into<OsString> + Clone,
45{
46    parse_cli_invocation_from_args(args, language).cli
47}
48
49pub fn parse_cli_invocation_from_args<I, T>(args: I, language: Language) -> CliInvocation
50where
51    I: IntoIterator<Item = T>,
52    T: Into<OsString> + Clone,
53{
54    let args_vec: Vec<OsString> = args.into_iter().map(Into::into).collect();
55    let mut command = command_for_language(language);
56    let matches = command
57        .try_get_matches_from_mut(args_vec)
58        .unwrap_or_else(|error| error.exit());
59    self::types::ParsedCli::from_arg_matches(&matches)
60        .unwrap_or_else(|error| error.exit())
61        .into()
62}
63
64fn normalize_args_for_parsing(mut args: Vec<OsString>) -> Vec<OsString> {
65    if args.len() == 1 {
66        args.push(OsString::from("--help"));
67    }
68    args
69}
70
71#[cfg(test)]
72mod tests {
73    use std::ffi::OsString;
74
75    use clap::Parser;
76
77    use super::*;
78
79    #[test]
80    fn cli_parses_concurrency_json_overwrite_no_color_ref_prefix_mode_and_debug() {
81        let invocation = parse_cli_invocation_from_args(
82            [
83                "gh-download",
84                "owner/repo",
85                "src",
86                "./downloads",
87                "--config",
88                "./gh-download.toml",
89                "--ref",
90                "main",
91                "--prefix-mode",
92                "prefer",
93                "--api-base",
94                "https://ghe.example.com/api/v3",
95                "-c",
96                "8",
97                "--lang",
98                "zh",
99                "--overwrite",
100                "--json",
101                "--debug",
102                "--no-color",
103            ],
104            Language::En,
105        );
106        let cli = invocation.cli;
107
108        assert_eq!(
109            invocation.config_path.as_deref(),
110            Some(std::path::Path::new("./gh-download.toml"))
111        );
112        assert_eq!(cli.git_ref.as_deref(), Some("main"));
113        assert_eq!(cli.prefix_mode, Some(PrefixProxyMode::Prefer));
114        assert_eq!(
115            cli.api_base.as_deref(),
116            Some("https://ghe.example.com/api/v3")
117        );
118        assert_eq!(cli.concurrency, 8);
119        assert_eq!(invocation.explicit_concurrency, Some(8));
120        assert_eq!(cli.language, Some(Language::Zh));
121        assert!(cli.overwrite);
122        assert!(cli.json);
123        assert!(cli.debug);
124        assert!(cli.no_color);
125    }
126
127    #[test]
128    fn cli_rejects_zero_concurrency() {
129        let error = Cli::try_parse_from([
130            "gh-download",
131            "owner/repo",
132            "src",
133            "./downloads",
134            "--concurrency",
135            "0",
136        ])
137        .expect_err("zero concurrency should be rejected");
138
139        assert!(error.to_string().contains("at least 1"));
140    }
141
142    #[test]
143    fn cli_accepts_long_concurrency_flag() {
144        let cli = parse_cli_from_args(
145            [
146                "gh-download",
147                "owner/repo",
148                "src",
149                "./downloads",
150                "--concurrency",
151                "6",
152            ],
153            Language::En,
154        );
155
156        assert_eq!(cli.concurrency, 6);
157    }
158
159    #[test]
160    fn cli_defaults_concurrency_to_four() {
161        let invocation = parse_cli_invocation_from_args(
162            ["gh-download", "owner/repo", "src", "./downloads"],
163            Language::En,
164        );
165        let cli = invocation.cli;
166
167        assert_eq!(cli.concurrency, 4);
168        assert_eq!(invocation.explicit_concurrency, None);
169        assert!(!cli.overwrite);
170        assert!(!cli.json);
171    }
172
173    #[test]
174    fn empty_invocation_is_normalized_to_help() {
175        let args = normalize_args_for_parsing(vec![OsString::from("gh-download")]);
176
177        assert_eq!(
178            args,
179            vec![OsString::from("gh-download"), OsString::from("--help")]
180        );
181    }
182}