Skip to main content

catalog_of_markdown/
lib.rs

1mod regex_handler;
2
3use clap::Parser;
4use regex_handler::*;
5use std::io::prelude::*;
6use std::io::Result;
7
8/// Command Line Args
9#[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    //:= when it is the number, should be a bit different
19    #[arg(short, long, default_value = "-")]
20    pub sym: String,
21}
22
23/// handle the makedown file
24pub 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) => (), //return Err(Error::new(ErrorKind::InvalidData, s)),
46                    };
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            //:= the format here
84            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}