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}