catalog_of_markdown/
lib.rs1mod regex_handler;
2
3use clap::Parser;
4use regex_handler::*;
5use std::io::prelude::*;
6use std::io::Result;
7
8#[derive(Default, Parser, Debug)]
10#[command(author = "ccQpein", version, about)]
11pub struct Args {
12 #[arg(short, long, default_value = "0")]
13 pub depth: usize,
14
15 #[arg(value_name = "file")]
16 pub md_file: String,
17
18 #[arg(short, long, default_value = "-")]
20 pub sym: String,
21}
22
23pub fn handle_file(filepath: &str, depth: usize, list_sym: &str) -> Result<Vec<String>> {
25 let mut buf = vec![];
26 let mut f: std::fs::File = std::fs::File::open(filepath)?;
27 f.read_to_end(&mut buf)?;
28 let mut buf = buf.as_slice();
29
30 let mut result = vec![];
31 let mut cache = String::new();
32 let mut escaped = false;
33 loop {
34 cache.clear();
35 match buf.read_line(&mut cache) {
36 Ok(0) | Err(_) => break,
37 Ok(_) => {
38 if let Some(ccache) = clean_line_content(&cache) {
39 escaped ^= check_if_escape(ccache);
40 if escaped {
41 continue;
42 }
43 match line_handler(ccache, &mut result, depth, list_sym) {
44 Ok(_) => (),
45 Err(_s) => (), };
47 } else {
48 continue;
49 }
50 }
51 }
52 }
53
54 Ok(result)
55}
56
57fn clean_line_content(s: &str) -> Option<&str> {
58 let ss = s.trim_start_matches(['\n', ' ']);
59 if ss.len() == 0 {
60 None
61 } else {
62 Some(ss)
63 }
64}
65
66fn line_handler(
67 s: &str,
68 bucket: &mut Vec<String>,
69 depth: usize,
70 list_sym: &str,
71) -> std::result::Result<(), String> {
72 match capture_title(s) {
73 Some(cap) => {
74 let m = pick_the_head(&cap)?;
75 let offset = head_count(&m) - 1;
76 let space_len = if offset >= depth {
77 offset - depth
78 } else {
79 return Ok(());
80 };
81 let content = pick_the_head_content(&cap)?;
82 let content = content.trim_end_matches([' ', '\n']);
83 let line = format!(
85 "{}{} [{}](#{})",
86 std::iter::repeat(" ").take(space_len).collect::<String>(),
87 list_sym,
88 content,
89 String::from_iter(
90 content
91 .to_lowercase()
92 .chars()
93 .filter(|&c| c.is_alphanumeric() || c.is_whitespace() || c == '-')
94 )
95 .split(" ")
96 .collect::<Vec<_>>()
97 .join("-")
98 );
99 bucket.push(line);
100 Ok(())
101 }
102 None => Err(format!("capture title failed: {}", s)),
103 }
104}
105
106fn check_if_escape(s: &str) -> bool {
107 s.starts_with("```")
108}
109
110#[cfg(test)]
111mod tests {
112 use crate::{check_if_escape, clean_line_content, line_handler};
113
114 #[test]
115 fn test_line_handler() -> Result<(), String> {
116 let mut bucket = vec![];
117
118 let case = "## level 2 ##";
119 line_handler(case, &mut bucket, 0, "-")?;
120 assert_eq!(bucket[0], " - [level 2](#level-2)".to_string());
121
122 let case = "# Level 1 ";
123 line_handler(case, &mut bucket, 0, "-")?;
124 assert_eq!(bucket[1], "- [Level 1](#level-1)".to_string());
125
126 let case = "# Level 1 ";
127 line_handler(case, &mut bucket, 0, "*")?;
128 assert_eq!(bucket[2], "* [Level 1](#level-1)".to_string());
129
130 Ok(())
131 }
132
133 #[test]
134 fn test_line_handler_special_char() -> Result<(), String> {
135 let mut bucket = vec![];
136
137 let case = clean_line_content(" ## level ,2 ##").unwrap();
138 line_handler(case, &mut bucket, 0, "-")?;
139 assert_eq!(bucket[0], " - [level ,2](#level-2)".to_string());
140
141 let case = "# level 1 & c ";
142 line_handler(case, &mut bucket, 0, "-")?;
143 assert_eq!(bucket[1], "- [level 1 & c](#level-1--c)".to_string());
144
145 let case = "## with-between words ##";
146
147 line_handler(case, &mut bucket, 0, "-")?;
148 assert_eq!(
149 bucket[2],
150 " - [with-between words](#with-between-words)".to_string()
151 );
152 Ok(())
153 }
154
155 #[test]
156 fn test_escape_logic() {
157 let mut escape = false;
158
159 escape ^= false;
160 assert!(!escape);
161
162 escape ^= true;
163 assert!(escape);
164
165 escape ^= false;
166 assert!(escape);
167
168 escape ^= true;
169 assert!(!escape);
170 }
171
172 #[test]
173 fn test_check_if_escape() {
174 let s = String::from("```rust");
175 assert!(check_if_escape(&s));
176
177 assert!(check_if_escape("```"));
178 }
179}