1pub mod builder;
2pub mod split;
3
4use std::{cmp::max, process};
5
6use crate::args::builder::{print_doc, print_help, CommandLineOption};
7
8#[derive(Debug)]
10pub struct Args {
11 pub input: String,
13
14 pub debug: bool,
16
17 pub flag_colon: bool,
19
20 pub flag_enclose_table: bool,
22
23 pub flag_extract_links: bool,
25
26 pub flag_extract_links_without_anchor: bool,
28
29 pub flag_google_list_style: bool,
32
33 pub flag_semicolon: bool,
35
36 pub flag_parenthesis: bool,
38
39 pub flag_pretty_table: bool,
41
42 pub flag_strip_enclosed_url: bool,
44
45 pub flag_whitespace_around_bold_font: bool,
47
48 pub flag_italic_to_bold_font: bool,
50
51 pub print_config: bool,
53
54 pub compact_output: bool,
56
57 pub debug_markdown_tag_pair: bool,
59}
60
61impl Args {
62 fn new() -> Self {
63 Self {
64 input: String::from(""),
65 debug: false,
66 flag_colon: false,
67 flag_enclose_table: false,
68 flag_semicolon: false,
69 flag_parenthesis: false,
70 flag_pretty_table: false,
71 flag_extract_links: false,
72 flag_extract_links_without_anchor: false,
73 flag_whitespace_around_bold_font: false,
74 flag_italic_to_bold_font: false,
75 flag_strip_enclosed_url: false,
76 print_config: false,
77 compact_output: false,
78 flag_google_list_style: false,
79 debug_markdown_tag_pair: false,
80 }
81 }
82
83 pub fn parse(args: Vec<String>) -> Option<Self> {
88 if args.len() > 1 {
89 let mut val = Self::new();
90 val.input = args.last().unwrap().to_string();
91
92 let mut options = vec![];
93
94 for arg in args {
95 let mut should_explain = true;
96
97 match arg.as_str() {
98 "--help" => {
100 print_help_info(Some(true), Some(options));
101 process::exit(0)
102 }
103 "-h" => {
104 print_help_info(None, None);
105 process::exit(0)
106 }
107 "--print" | "-p" => val.print_config = true,
108 "--print-compact" => {
109 val.compact_output = true;
110 val.print_config = true
111 }
112 "--colon" => val.flag_colon = true,
114 "--extract-links" => val.flag_extract_links = true,
115 "--enclose-table" => val.flag_enclose_table = true,
116 "--extract-no-anchor" => val.flag_extract_links_without_anchor = true,
117 "--google-list-style" => val.flag_google_list_style = true,
118 "--italic-to-bold" => val.flag_italic_to_bold_font = true,
119 "--parenthesis" => val.flag_parenthesis = true,
120 "--semicolon" => val.flag_semicolon = true,
121 "--pretty-table" => {
122 val.flag_pretty_table = true;
123 panic!("Unimplemented style")
124 }
125 "--whitespace-around-bold" => val.flag_whitespace_around_bold_font = true,
126 "--strip-enclosed-url" => val.flag_strip_enclosed_url = true,
127 "--flavor-hugging-face-wechat" => val.set_flavor_hugging_face_wechat(),
129 "--debug-markdown-tag-pair" => val.debug_markdown_tag_pair = true,
131
132 _ => {
133 if arg.starts_with("-") {
134 panic!("无法识别的选项 `{}`", arg);
135 } else {
136 should_explain = false;
137 }
138 }
139 }
140
141 if should_explain {
142 options.push(arg.clone());
143 }
144 }
145 Some(val)
146 } else {
147 None
148 }
149 }
150
151 pub fn set_flavor_hugging_face_wechat(&mut self) {
153 self.flag_colon = true;
154 self.flag_enclose_table = true;
155 self.flag_semicolon = true;
156 self.flag_parenthesis = true;
157 self.flag_whitespace_around_bold_font = true;
158 self.flag_italic_to_bold_font = false; self.flag_extract_links = false; self.flag_extract_links_without_anchor = false; self.flag_strip_enclosed_url = true;
162 self.flag_google_list_style = false;
163 }
164
165 pub fn print_config(&self) {
167 println!("渲染选项启用状态");
168 let mut results: Vec<(&str, bool)> = vec![];
169 let mut max_width = 0;
170 results.push((
171 "Colon conversion",
172 if self.flag_colon { true } else { false },
173 ));
174 results.push((
175 "Extract links",
176 if self.flag_extract_links { true } else { false },
177 ));
178 results.push((
179 "Extract links without anchor",
180 if self.flag_extract_links_without_anchor {
181 true
182 } else {
183 false
184 },
185 ));
186 results.push((
187 "Semicolon conversion",
188 if self.flag_semicolon { true } else { false },
189 ));
190 results.push((
191 "List using Google style",
192 if self.flag_google_list_style {
193 true
194 } else {
195 false
196 },
197 ));
198 results.push((
199 "Parenthesis conversion",
200 if self.flag_parenthesis { true } else { false },
201 ));
202 results.push((
203 "Prettify table",
204 if self.flag_pretty_table { true } else { false },
205 ));
206
207 results.push((
208 "Italic to bold font conversion",
209 if self.flag_italic_to_bold_font {
210 true
211 } else {
212 false
213 },
214 ));
215 results.push((
216 "Whitespace around bold font",
217 if self.flag_whitespace_around_bold_font {
218 true
219 } else {
220 false
221 },
222 ));
223 results.push((
224 "Strip whitespace around enclosed URL",
225 if self.flag_strip_enclosed_url {
226 true
227 } else {
228 false
229 },
230 ));
231 results.sort_by(|item_a, item_b| {
232 max_width = max(max_width, max(item_a.0.len(), item_b.0.len()));
233 if item_a.1 == item_b.1 {
234 item_a.0.cmp(&item_b.0)
235 } else {
236 if item_a.1 {
237 std::cmp::Ordering::Less
238 } else {
239 std::cmp::Ordering::Greater
240 }
241 }
242 });
243 let mut counter: usize = 1;
244 let digits = if results.len() < 10 {
245 1
246 } else if results.len() < 100 {
247 2
248 } else if results.len() < 1000 {
249 3
250 } else {
251 4
252 };
253 println!(
254 "┌─{}──{}┬───┐",
255 "─".repeat(digits),
256 "─".repeat(max_width + 1)
257 );
258 results.iter().for_each(|res| {
259 if !self.compact_output && counter > 1 {
260 println!(
261 "├─{}──{}┼───┤",
262 "─".repeat(digits),
263 "─".repeat(max_width + 1)
264 );
265 }
266 println!(
267 "│ {:digits$}. {:max_width$} │ {} │",
268 counter,
269 res.0,
270 if res.1 { "Y" } else { "N" }
271 );
272 counter += 1;
273 });
274 println!(
275 "└─{}──{}┴───┘",
276 "─".repeat(digits),
277 "─".repeat(max_width + 1)
278 );
279 }
280}
281
282fn print_help_info(
283 long_help: Option<bool>,
284 filtered: Option<Vec<String>>,
285) -> Vec<CommandLineOption> {
286 println!("命令用法: md-fmt [选项] <Markdown 文件名>\n");
287
288 let mut commands = vec![];
289
290 commands.push(
292 CommandLineOption::new("--help", "输出当前的帮助信息 (--help 更详细)", "")
293 .set_condition(builder::CommandLineOptionCondition::Standard)
294 .set_short("-h"),
295 );
296
297 commands.push(
299 CommandLineOption::new(
300 "--colon",
301 "中文冒号转为英文冒号+空格",
302 "比如: “:” => “: ”。如果在行末,则会生成“:”,即删除末尾的空格。",
303 )
304 .set_condition(builder::CommandLineOptionCondition::Format),
305 );
306 commands.push(
307 CommandLineOption::new(
308 "--enclose-table",
309 "表格的每一行两端使用 |",
310 "比如表格的对齐行: “:-- | :--” => “| :-- | :-- |”。",
311 )
312 .set_condition(builder::CommandLineOptionCondition::Format),
313 );
314 commands.push(
315 CommandLineOption::new("--extract-links", "从段落中提取超链接", "比如:\n“这句话包含了一个[超链接](https://dongs.xyz/)” => “这句话包含了一个[超链接](https://dongs.xyz/)\\n\\n超链接:\\n<url>https://dongs.xyz/</url>”")
316 .set_condition(builder::CommandLineOptionCondition::Format),
317 );
318 commands.push(
319 CommandLineOption::new("--extract-no-anchor", "提取链接时不包括锚点部分", "")
320 .set_condition(builder::CommandLineOptionCondition::Format),
321 );
322 commands.push(
323 CommandLineOption::new("--google-list-style", "使用 Google 风格的列表样式", "")
324 .set_condition(builder::CommandLineOptionCondition::Format),
325 );
326 commands.push(
327 CommandLineOption::new(
328 "--italic-to-bold",
329 "斜体字更改为加粗文字",
330 "比如: “a*b*c” => “a**b**c”",
331 )
332 .set_condition(builder::CommandLineOptionCondition::Format),
333 );
334 commands.push(
335 CommandLineOption::new(
336 "--parenthesis",
337 "中文括号转为英文括号+空格",
338 "比如:\n“()” => “ () ”\n最终结果将根据上下文去除多余的空格。",
339 )
340 .set_condition(builder::CommandLineOptionCondition::Format),
341 );
342 commands.push(
343 CommandLineOption::new("--pretty-table", "以相同列宽展示表格中的每一列", "")
344 .set_condition(builder::CommandLineOptionCondition::Format),
345 );
346 commands.push(
347 CommandLineOption::new(
348 "--semicolon",
349 "中文分号转为英文分号+空格",
350 "比如: “;” => “; ”\n最终结果将根据上下文去除多余的空格。",
351 )
352 .set_condition(builder::CommandLineOptionCondition::Format),
353 );
354 commands.push(
355 CommandLineOption::new(
356 "--strip-enclosed-url",
357 "去除 <url> 内部的非必要空格",
358 "比如: “<url> https://dongs.xyz/ </url>” => “<url>https://dongs.xyz/</url>”",
359 )
360 .set_condition(builder::CommandLineOptionCondition::Format),
361 );
362 commands.push(
363 CommandLineOption::new(
364 "--whitespace-around-bold",
365 "加粗文字前后使用空格",
366 "比如:\n“a**b**c” => “a **b** c”;\n“a **b**c” => “a **b** c”。\n最终结果将根据上下文去除多余的空格。",
367 )
368 .set_condition(builder::CommandLineOptionCondition::Format),
369 );
370
371 commands.push(
373 CommandLineOption::new("--print", "在控制台输出当前启用的调整", "")
374 .set_condition(builder::CommandLineOptionCondition::Additional),
375 );
376 commands.push(
377 CommandLineOption::new("--print-compact", "窄行距输出当前启用的调整", "")
378 .set_condition(builder::CommandLineOptionCondition::Additional),
379 );
380
381 commands.push(
383 CommandLineOption::new(
384 "--debug-markdown-tag-pair",
385 "执行 Markdown 模块的函数时输出成对的 Tag (仅适用于 `debug` feature)",
386 "",
387 )
388 .set_condition(builder::CommandLineOptionCondition::Debug),
389 );
390
391 commands.push(
393 CommandLineOption::new(
394 "--flavor-hugging-face-wechat",
395 "使用 Hugging Face 微信公众号风格来调整输入内容",
396 "",
397 )
398 .set_condition(builder::CommandLineOptionCondition::Flavor),
399 );
400
401 use termsize_alt::{get as get_termsize, Size};
404 let Size { rows: _, cols } = get_termsize().unwrap_or(Size {
405 rows: 40,
406 cols: 120,
407 });
408
409 let line_width = Some(cols.into());
410
411 match long_help {
412 Some(true) => print_doc(commands.clone(), filtered, line_width),
413 _ => print_help(commands.clone(), line_width),
414 }
415
416 commands
417}
418
419#[cfg(test)]
420mod test {
421 use crate::args::Args;
422 #[test]
423 fn test_argument_parsing() {
424 let mut options: Vec<String> = vec!["--colon", "--parenthesis", "test.md"]
425 .iter()
426 .map(|s| s.to_string())
427 .collect();
428 if let Some(args) = Args::parse(options) {
429 assert_eq!(args.flag_colon, true);
430 assert_eq!(args.flag_parenthesis, true);
431 };
432
433 options = vec!["--flavor-hugging-face-wechat", "test.md"]
434 .iter()
435 .map(|s| s.to_string())
436 .collect();
437 if let Some(args) = Args::parse(options) {
438 assert_eq!(args.flag_colon, true);
439 assert_eq!(args.flag_parenthesis, true);
440 assert_eq!(args.flag_italic_to_bold_font, true);
441 assert_eq!(args.flag_whitespace_around_bold_font, true);
442 };
443 }
444
445 #[test]
446 fn test_malformed_options() {
447 let options = vec!["--flavor-hugging-face-wechat"]
448 .iter()
449 .map(|s| s.to_string())
450 .collect();
451
452 Args::parse(options);
453 }
454}