1pub mod bookshelf;
2pub mod build;
3pub mod check;
4pub mod completions;
5pub mod download;
6pub mod info;
7pub mod read;
8pub mod real_cugan;
9pub mod search;
10pub mod sign;
11pub mod template;
12pub mod transform;
13pub mod unzip;
14pub mod update;
15pub mod zip;
16
17use std::collections::HashMap;
18use std::path::Path;
19use std::sync::Arc;
20use std::{cmp, process};
21
22use clap::ValueEnum;
23use fluent_templates::Loader;
24use fluent_templates::fluent_bundle::FluentValue;
25use novel_api::Client;
26use ratatui::buffer::Buffer;
27use ratatui::layout::{Rect, Size};
28use ratatui::text::Text;
29use ratatui::widgets::block::Title;
30use ratatui::widgets::{Block, Paragraph, StatefulWidget, Widget, Wrap};
31use strum::AsRefStr;
32use tokio::signal;
33use tui_widgets::scrollview::{ScrollView, ScrollViewState};
34use url::Url;
35
36use crate::{LANG_ID, LOCALES};
37
38const DEFAULT_PROXY: &str = "http://127.0.0.1:8080";
39
40const DEFAULT_PROXY_SURGE: &str = "http://127.0.0.1:6152";
41
42#[must_use]
43#[derive(Clone, PartialEq, ValueEnum, AsRefStr)]
44pub enum Source {
45 #[strum(serialize = "sfacg")]
46 Sfacg,
47 #[strum(serialize = "ciweimao")]
48 Ciweimao,
49 #[strum(serialize = "ciyuanji")]
50 Ciyuanji,
51}
52
53#[must_use]
54#[derive(Clone, PartialEq, ValueEnum, AsRefStr)]
55pub enum Format {
56 Pandoc,
57 Mdbook,
58}
59
60#[must_use]
61#[derive(Clone, PartialEq, ValueEnum, AsRefStr)]
62pub enum Convert {
63 S2T,
64 T2S,
65 JP2T2S,
66 CUSTOM,
67}
68
69#[inline]
70#[must_use]
71fn default_cert_path() -> String {
72 novel_api::home_dir_path()
73 .unwrap()
74 .join(".mitmproxy")
75 .join("mitmproxy-ca-cert.pem")
76 .display()
77 .to_string()
78}
79
80fn set_options<T, E>(client: &mut T, proxy: &Option<Url>, no_proxy: &bool, cert: &Option<E>)
81where
82 T: Client,
83 E: AsRef<Path>,
84{
85 if let Some(proxy) = proxy {
86 client.proxy(proxy.clone());
87 }
88
89 if *no_proxy {
90 client.no_proxy();
91 }
92
93 if let Some(cert) = cert {
94 client.cert(cert.as_ref().to_path_buf())
95 }
96}
97
98fn handle_ctrl_c<T>(client: &Arc<T>)
99where
100 T: Client + Send + Sync + 'static,
101{
102 let client = Arc::clone(client);
103
104 tokio::spawn(async move {
105 signal::ctrl_c().await.unwrap();
106
107 tracing::warn!("Download terminated, login data will be saved");
108
109 client.shutdown().await.unwrap();
110 process::exit(128 + libc::SIGINT);
111 });
112}
113
114fn cert_help_msg() -> String {
115 let args = {
116 let mut map = HashMap::new();
117 map.insert(
118 "cert_path".into(),
119 FluentValue::String(default_cert_path().into()),
120 );
121 map
122 };
123
124 LOCALES.lookup_with_args(&LANG_ID, "cert", &args)
125}
126
127#[derive(Default, PartialEq)]
128enum Mode {
129 #[default]
130 Running,
131 Quit,
132}
133
134pub struct ScrollableParagraph<'a> {
135 title: Option<Title<'a>>,
136 text: Text<'a>,
137}
138
139impl<'a> ScrollableParagraph<'a> {
140 pub fn new<T>(text: T) -> Self
141 where
142 T: Into<Text<'a>>,
143 {
144 ScrollableParagraph {
145 title: None,
146 text: text.into(),
147 }
148 }
149
150 pub fn title<T>(self, title: T) -> Self
151 where
152 T: Into<Title<'a>>,
153 {
154 ScrollableParagraph {
155 title: Some(title.into()),
156 ..self
157 }
158 }
159}
160
161impl StatefulWidget for ScrollableParagraph<'_> {
162 type State = ScrollViewState;
163
164 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
165 let mut block = Block::bordered();
166 if self.title.is_some() {
167 block = block.title(self.title.as_ref().unwrap().clone());
168 }
169
170 let paragraph = Paragraph::new(self.text.clone()).wrap(Wrap { trim: false });
171 let mut scroll_view = ScrollView::new(Size::new(area.width - 1, area.height));
172 let mut block_area = block.inner(scroll_view.buf().area);
173
174 let scroll_height = cmp::max(
175 paragraph.line_count(block_area.width) as u16
176 + (scroll_view.buf().area.height - block_area.height),
177 area.height,
178 );
179
180 let scroll_width = if area.height >= scroll_height {
181 area.width
183 } else {
184 area.width - 1
185 };
186
187 scroll_view = ScrollView::new(Size::new(scroll_width, scroll_height));
188
189 let scroll_view_buf = scroll_view.buf_mut();
190 block_area = block.inner(scroll_view_buf.area);
191
192 Widget::render(block, scroll_view_buf.area, scroll_view_buf);
193 Widget::render(paragraph, block_area, scroll_view_buf);
194 StatefulWidget::render(scroll_view, area, buf, state);
195 }
196}