1mod check;
2mod concurrency;
3mod convert;
4mod current_dir;
5mod image;
6mod login;
7mod markdown;
8mod novel;
9mod novel_info;
10mod progress;
11mod unicode;
12mod writer;
13
14use std::collections::HashMap;
15use std::fs;
16use std::path::{Path, PathBuf};
17
18pub use check::*;
19use color_eyre::eyre::{self, Result};
20pub use concurrency::*;
21use console::{Alignment, Emoji};
22pub use convert::*;
23pub use current_dir::*;
24use fluent_templates::Loader;
25use fluent_templates::fluent_bundle::FluentValue;
26pub use login::*;
27pub use markdown::*;
28pub use novel::*;
29pub use novel_info::*;
30pub use progress::*;
31use sanitize_filename::Options;
32pub use unicode::*;
33pub use writer::*;
34
35pub use self::image::*;
36use crate::cmd::Convert;
37use crate::{LANG_ID, LOCALES};
38
39#[inline]
40#[must_use]
41pub fn num_to_str(num: u16) -> String {
42 if num < 10 {
43 format!("00{num}")
44 } else if num < 100 {
45 format!("0{num}")
46 } else {
47 num.to_string()
48 }
49}
50
51pub fn remove_file_or_dir<T>(path: T) -> Result<()>
52where
53 T: AsRef<Path>,
54{
55 remove_file_or_dir_all(&[path])
56}
57
58pub fn remove_file_or_dir_all<T>(paths: &[T]) -> Result<()>
59where
60 T: AsRef<Path>,
61{
62 for path in paths {
63 let path = path.as_ref();
64
65 if !path.try_exists()? {
66 eyre::bail!("The item does not exist: `{}`", path.display());
67 }
68
69 let path = dunce::canonicalize(path)?;
70 if path.is_file() {
71 tracing::info!("File `{}` will be deleted", path.display());
72 } else if path.is_dir() {
73 tracing::info!("Directory `{}` will be deleted", path.display());
74 } else {
75 eyre::bail!("The item is neither a file nor a folder");
76 }
77 }
78
79 if let Err(error) = trash::delete_all(paths) {
80 tracing::error!("Failed to put file or folder into Trash: {}", error);
81 }
82
83 for path in paths {
84 let path = path.as_ref();
85
86 if path.try_exists()? {
87 tracing::error!(
88 "Failed to put file or folder into Trash: {}",
89 path.display()
90 );
91
92 if path.is_file() {
93 fs::remove_file(path)?;
94 } else {
95 fs::remove_dir_all(path)?;
96 }
97 }
98 }
99
100 Ok(())
101}
102
103pub fn lang<T>(convert: T) -> Lang
104where
105 T: AsRef<[Convert]>,
106{
107 if convert.as_ref().contains(&Convert::S2T) {
108 Lang::ZhHant
109 } else {
110 Lang::ZhHans
111 }
112}
113
114#[must_use]
115pub fn emoji<T>(str: T) -> String
116where
117 T: AsRef<str>,
118{
119 let emoji = Emoji(str.as_ref(), ">").to_string();
120 console::pad_str(&emoji, 2, Alignment::Left, None).to_string()
121}
122
123#[must_use]
124pub fn locales<T, E>(name: T, emoji: E) -> String
125where
126 T: AsRef<str>,
127 E: AsRef<str>,
128{
129 let args = {
130 let mut map = HashMap::new();
131 map.insert(
132 "emoji".into(),
133 FluentValue::String(self::emoji(emoji).into()),
134 );
135 map
136 };
137
138 LOCALES.lookup_with_args(&LANG_ID, name.as_ref(), &args)
139}
140
141#[must_use]
142pub fn locales_with_arg<T, E, F>(name: T, emoji: E, arg: F) -> String
143where
144 T: AsRef<str>,
145 E: AsRef<str>,
146 F: AsRef<str>,
147{
148 let args = {
149 let mut map = HashMap::new();
150 map.insert(
151 "emoji".into(),
152 FluentValue::String(self::emoji(emoji).into()),
153 );
154 map.insert("arg".into(), FluentValue::String(arg.as_ref().into()));
155 map
156 };
157
158 LOCALES.lookup_with_args(&LANG_ID, name.as_ref(), &args)
159}
160
161#[must_use]
162pub fn to_novel_dir_name<T>(novel_name: T) -> PathBuf
163where
164 T: AsRef<str>,
165{
166 if !sanitize_filename::is_sanitized(&novel_name) {
167 tracing::warn!("The output file name is invalid and has been modified");
168 }
169
170 do_sanitize(novel_name)
171}
172
173#[must_use]
174pub fn to_markdown_file_name<T>(novel_name: T) -> PathBuf
175where
176 T: AsRef<str>,
177{
178 do_sanitize(novel_name).with_extension("md")
179}
180
181#[must_use]
182pub fn to_epub_file_name<T>(novel_name: T) -> PathBuf
183where
184 T: AsRef<str>,
185{
186 do_sanitize(novel_name).with_extension("epub")
187}
188
189fn do_sanitize<T>(novel_name: T) -> PathBuf
190where
191 T: AsRef<str>,
192{
193 let option = Options {
194 replacement: " ",
195 ..Default::default()
196 };
197
198 PathBuf::from(sanitize_filename::sanitize_with_options(novel_name, option))
199}
200
201pub fn read_markdown_to_epub_file_name<T>(markdown_path: T) -> Result<PathBuf>
202where
203 T: AsRef<Path>,
204{
205 let metadata = get_metadata_from_file(markdown_path)?;
206 Ok(to_epub_file_name(metadata.title))
207}