novel_cli/
config.rs

1use std::env;
2use std::path::PathBuf;
3
4use clap::builder::Styles;
5use clap::builder::styling::{AnsiColor, Style};
6use clap::{Parser, Subcommand, ValueEnum};
7use clap_verbosity_flag::Verbosity;
8use fluent_templates::Loader;
9use supports_color::Stream;
10
11use crate::cmd::bookshelf::Bookshelf;
12use crate::cmd::build::Build;
13use crate::cmd::check::Check;
14use crate::cmd::completions::Completions;
15use crate::cmd::download::Download;
16use crate::cmd::epub::Epub;
17use crate::cmd::info::Info;
18use crate::cmd::read::Read;
19use crate::cmd::real_cugan::RealCugan;
20use crate::cmd::search::Search;
21use crate::cmd::sign::Sign;
22use crate::cmd::template::Template;
23use crate::cmd::transform::Transform;
24use crate::cmd::unzip::Unzip;
25use crate::cmd::update::Update;
26use crate::cmd::zip::Zip;
27use crate::{LANG_ID, LOCALES};
28
29shadow_rs::shadow!(shadow_build);
30
31#[must_use]
32#[derive(Parser)]
33#[command(author, version = version_msg(), about = about_msg(),
34    long_about = None, propagate_version = true, styles = get_styles())]
35pub struct Config {
36    #[command(subcommand)]
37    pub command: Commands,
38
39    #[arg(long, value_enum, global = true,
40        help = LOCALES.lookup(&LANG_ID, "backtrace"))]
41    pub backtrace: Option<Backtrace>,
42
43    #[arg(long, global = true, conflicts_with = "quiet", default_value_t = false,
44        help = LOCALES.lookup(&LANG_ID, "output_log_to_file"))]
45    pub output_log_to_file: bool,
46
47    #[command(flatten)]
48    pub verbose: Verbosity,
49}
50
51#[must_use]
52#[derive(Subcommand)]
53pub enum Commands {
54    Sign(Sign),
55    Download(Download),
56    Search(Search),
57    Info(Info),
58    Read(Read),
59    Bookshelf(Bookshelf),
60    Template(Template),
61    Transform(Transform),
62    Check(Check),
63    Build(Build),
64    Epub(Epub),
65    Zip(Zip),
66    Unzip(Unzip),
67    RealCugan(RealCugan),
68    Update(Update),
69    Completions(Completions),
70}
71
72#[must_use]
73#[derive(Clone, PartialEq, ValueEnum)]
74pub enum Backtrace {
75    ON,
76    FULL,
77}
78
79macro_rules! TELEGRAM {
80    () => {
81        "https://t.me/TerakomariGandesblood"
82    };
83}
84
85#[must_use]
86const fn about_msg() -> &'static str {
87    concat!(
88        clap::crate_name!(),
89        " ",
90        clap::crate_version!(),
91        "\nAuthor: ",
92        clap::crate_authors!(),
93        "\nAuthor's Telegram: ",
94        TELEGRAM!(),
95        "\nProject home page: ",
96        env!("CARGO_PKG_HOMEPAGE"),
97    )
98}
99
100#[must_use]
101fn version_msg() -> String {
102    let version = clap::crate_version!();
103    let author = clap::crate_authors!();
104    let home_page = env!("CARGO_PKG_HOMEPAGE");
105
106    let commit_date = shadow_build::COMMIT_DATE;
107    let commit_hash = shadow_build::COMMIT_HASH;
108    let build_time = shadow_build::BUILD_TIME;
109    let build_target = shadow_build::BUILD_TARGET;
110
111    let os_info = os_info::get();
112    let architecture = os_info.architecture().unwrap_or("unknown");
113
114    let current_exe_path = env::current_exe()
115        .unwrap_or_else(|_| {
116            eprintln!("Unable to get current executable path");
117            PathBuf::from(clap::crate_name!())
118        })
119        .display()
120        .to_string();
121    let config_dir_path = novel_api::config_dir_path("some-source")
122        .unwrap()
123        .display()
124        .to_string();
125    let data_dir_path = novel_api::data_dir_path("some-source")
126        .unwrap()
127        .display()
128        .to_string();
129
130    format!(
131        "\
132{version}
133Author: {author}
134Author's Telegram: {}
135Project home page: {home_page}
136
137Commit date: {commit_date}
138Commit hash: {commit_hash}
139Build time: {build_time}
140Build target: {build_target}
141
142OS information: {os_info} [{architecture}]
143
144Executable path: {current_exe_path}
145Config directory: {config_dir_path}
146Data directory: {data_dir_path}",
147        TELEGRAM!(),
148    )
149}
150
151const HEADER: Style = AnsiColor::Green.on_default().bold();
152const USAGE: Style = AnsiColor::Green.on_default().bold();
153const LITERAL: Style = AnsiColor::Cyan.on_default().bold();
154const PLACEHOLDER: Style = AnsiColor::Cyan.on_default();
155const ERROR: Style = AnsiColor::Red.on_default().bold();
156const VALID: Style = AnsiColor::Cyan.on_default().bold();
157const INVALID: Style = AnsiColor::Yellow.on_default().bold();
158const HELP_STYLES: Styles = Styles::styled()
159    .header(HEADER)
160    .usage(USAGE)
161    .literal(LITERAL)
162    .placeholder(PLACEHOLDER)
163    .error(ERROR)
164    .valid(VALID)
165    .invalid(INVALID);
166
167fn get_styles() -> Styles {
168    if supports_color::on(Stream::Stdout).is_some() {
169        HELP_STYLES
170    } else {
171        Styles::plain()
172    }
173}
174
175#[cfg(test)]
176mod tests {
177    use clap::CommandFactory;
178
179    use super::*;
180
181    #[test]
182    fn verify_cli() {
183        Config::command().debug_assert()
184    }
185}