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}