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}