markdown_toc/
lib.rs

1extern crate percent_encoding;
2
3use percent_encoding::{percent_encode, CONTROLS};
4use std::path::PathBuf;
5use std::str::FromStr;
6
7fn slugify(text: &str) -> String {
8    percent_encode(
9        text.replace(" ", "-").to_lowercase().as_bytes(),
10        CONTROLS,
11    )
12    .to_string()
13}
14
15pub struct Heading {
16    pub depth: usize,
17    pub title: String,
18}
19
20impl FromStr for Heading {
21    type Err = ();
22
23    fn from_str(s: &str) -> Result<Self, Self::Err> {
24        let trimmed = s.trim_end();
25        if trimmed.starts_with("#") {
26            let mut depth = 0usize;
27            let title = trimmed
28                .chars()
29                .skip_while(|c| {
30                    if *c == '#' {
31                        depth += 1;
32                        true
33                    } else {
34                        false
35                    }
36                })
37                .collect::<String>()
38                .trim_start()
39                .to_owned();
40            Ok(Heading {
41                depth: depth - 1,
42                title,
43            })
44        } else {
45            Err(())
46        }
47    }
48}
49
50impl Heading {
51    pub fn format(&self, config: &Config) -> Option<String> {
52        if self.depth >= config.min_depth
53            && config.max_depth.map(|d| self.depth <= d).unwrap_or(true)
54        {
55            Some(format!(
56                "{}{} {}",
57                " ".repeat(config.indent)
58                    .repeat(self.depth - config.min_depth),
59                &config.bullet,
60                if config.no_link {
61                    self.title.clone()
62                } else {
63                    format!("[{}](#{})", &self.title, slugify(&self.title))
64                }
65            ))
66        } else {
67            None
68        }
69    }
70}
71
72pub enum InputFile {
73    Path(PathBuf),
74    StdIn,
75}
76
77// enum Inline {
78//     None,
79//     Inline,
80//     InlineAndReplace,
81// }
82
83pub struct Config {
84    pub input_file: InputFile,
85    pub bullet: String,
86    pub indent: usize,
87    pub max_depth: Option<usize>,
88    pub min_depth: usize,
89    pub header: Option<String>,
90    pub no_link: bool,
91    // inline: Inline,
92}
93
94impl Default for Config {
95    fn default() -> Self {
96        Config {
97            input_file: InputFile::StdIn,
98            bullet: String::from("1."),
99            indent: 4,
100            max_depth: None,
101            min_depth: 0,
102            no_link: false,
103            header: Some(String::from("## Table of Contents")),
104            // inline: Inline::None,
105        }
106    }
107}