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}