Skip to main content

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