Skip to main content

amagi/cli/
mod.rs

1//! Parse CLI arguments and convert them into runtime configuration values.
2
3mod args;
4mod i18n;
5mod map;
6mod resolve;
7
8use std::ffi::OsString;
9
10use clap::{CommandFactory, FromArgMatches};
11
12use crate::config::AppConfig;
13use crate::env::dotenv_values;
14use crate::error::AppError;
15
16pub(crate) use i18n::resolve_runtime_language;
17
18#[cfg(feature = "server")]
19pub use args::ServeArgs;
20pub use args::{
21    BilibiliArgs, BilibiliCommand, Cli, Command, DouyinArgs, DouyinCommand, KuaishouArgs,
22    KuaishouCommand, RunArgs, RunTaskArgs, TwitterArgs, TwitterCommand, XiaohongshuArgs,
23    XiaohongshuCommand,
24};
25
26/// Parse CLI arguments from the current process environment.
27///
28/// Invalid arguments terminate the process with clap's formatted diagnostic.
29pub fn parse_env() -> AppConfig {
30    parse_from(std::env::args_os())
31}
32
33/// Parse CLI arguments after resolving values from process env and layered
34/// dotenv files.
35///
36/// Invalid arguments terminate the process with clap's formatted diagnostic.
37///
38/// # Errors
39///
40/// Returns an error when any discovered dotenv file cannot be read or contains
41/// invalid values.
42pub fn try_parse_env() -> Result<AppConfig, AppError> {
43    let dotenv = dotenv_values()?;
44    resolve::parse_process_args_with_dotenv(std::env::args_os(), &dotenv)
45}
46
47/// Parse CLI arguments from a custom iterator.
48///
49/// Invalid arguments terminate the process with clap's formatted diagnostic.
50///
51/// # Examples
52///
53/// ```rust
54/// use amagi::cli::parse_from;
55/// use amagi::config::CommandConfig;
56///
57/// let config = parse_from(["amagi", "run", "douyin", "emoji-list"]);
58///
59/// match config.command {
60///     CommandConfig::Run(run) => assert!(!run.quiet),
61///     _ => unreachable!("expected run"),
62/// }
63/// ```
64pub fn parse_from<I, T>(args: I) -> AppConfig
65where
66    I: IntoIterator<Item = T>,
67    T: Into<OsString> + Clone,
68{
69    let args = args.into_iter().map(Into::into).collect::<Vec<OsString>>();
70    let lang = i18n::resolve_cli_language(&args, None);
71    let matches = match i18n::localize_command(Cli::command(), lang)
72        .color(i18n::default_cli_color())
73        .try_get_matches_from(args)
74    {
75        Ok(matches) => matches,
76        Err(error) => {
77            let rendered = i18n::render_clap_error(&error, lang);
78            if error.use_stderr() {
79                eprint!("{rendered}");
80            } else {
81                print!("{rendered}");
82            }
83            std::process::exit(error.exit_code());
84        }
85    };
86    let cli = Cli::from_arg_matches(&matches)
87        .expect("clap matches should deserialize into the generated CLI type");
88    AppConfig::from(cli)
89}
90
91impl From<Cli> for AppConfig {
92    fn from(value: Cli) -> Self {
93        resolve::app_config_from_plain_cli(value)
94    }
95}