novel_cli/utils/
mod.rs

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}