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}