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