novel_cli/
config.rs

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