mdbook_to_github_wiki/
lib.rs

1use std::io::{BufReader, Read, Write};
2
3pub struct Builder {
4    name: String,
5    source: String,
6}
7
8impl Builder {
9    pub fn new() -> Builder {
10        Builder {
11            name: "wiki".to_string(),
12            source: "book".to_string(),
13        }
14    }
15
16    pub fn set_name(mut self, name: &str) -> Self {
17        self.name = name.to_string();
18        self
19    }
20    pub fn set_source(mut self, source: &str) -> Self {
21        self.source = source.to_string();
22        self
23    }
24
25    pub fn run(self) -> Result<(), std::io::Error> {
26        std::fs::create_dir_all(format!("{}", self.name))?;
27        let mut sidebar_md = std::fs::File::create(format!("{}/_Sidebar.md", self.name))?;
28        let mut lines: Vec<String> = vec!["[Home](home)\n".to_string()];
29
30        let summary_md = std::fs::File::open(format!("{}/src/SUMMARY.md", self.source))?;
31        let mut reader = BufReader::new(summary_md);
32        let mut summary_contents = "".to_string();
33        let _ = reader.read_to_string(&mut summary_contents);
34        let mut summary_lines: Vec<String> = summary_contents
35            .split("\n")
36            .filter(|n| n.contains("(") && n.contains("["))
37            .map(|n| n.to_string())
38            .collect();
39
40        #[derive(Debug)]
41        enum StringOrVec {
42            String(String),
43            Vec(Vec<StringOrVec>),
44        }
45
46        fn create_branch(lines: &mut Vec<String>, current_level: i32) -> Vec<StringOrVec> {
47            let mut branch: Vec<StringOrVec> = vec![];
48            while lines.len() > 0 {
49                let value = lines.remove(0);
50
51                let mut x: Vec<String> = value.split("").map(|n| n.to_string()).collect();
52                let mut spaces: Vec<String> = vec![];
53                if x[0] == "" {
54                    x.remove(0);
55                }
56                while x[0] == " " {
57                    spaces.push(x.remove(0));
58                }
59
60                let new_level = (spaces.len() / 2) as i32;
61
62                if current_level == new_level {
63                    branch.push(StringOrVec::String(value));
64                } else if current_level < new_level {
65                    lines.insert(0, value);
66                    branch.push(StringOrVec::Vec(create_branch(lines, new_level)));
67                } else if current_level > new_level {
68                    lines.insert(0, value);
69                    return branch;
70                }
71            }
72            branch
73        }
74        let tree = create_branch(&mut summary_lines, 0);
75
76        fn add_lines(lines: &mut Vec<String>, tree: &Vec<StringOrVec>, source: String) {
77            for entry in tree {
78                match entry {
79                    StringOrVec::String(s) => {
80                        lines.push(format!(
81                            "{}",
82                            s.replace("./", "").replace("/", "-").replace(".md", "")
83                        ));
84                    }
85                    StringOrVec::Vec(v) => {
86                        add_lines(lines, v, source.to_string());
87                    }
88                }
89            }
90        }
91        add_lines(&mut lines, &tree, self.source.clone());
92
93        lines.push("".to_string());
94        let contents = lines.join("\n");
95
96        sidebar_md.write_all(contents.as_bytes())?;
97
98        let _ = std::fs::copy("README.md", format!("{}/home.md", self.name))?;
99
100        let mut progression: Vec<String> = vec![];
101        for n in lines.iter().filter(|n| n.contains("- ")) {
102            let filename: String = n.split("(").collect::<Vec<&str>>()[1].replace(")", ".md");
103            progression.push(filename);
104        }
105
106        for entry in walkdir::WalkDir::new(format!("{}/src", self.source))
107            .min_depth(1)
108            .into_iter()
109            .filter(|n| n.is_ok())
110            .map(|n| n.unwrap())
111        {
112            if entry.file_name() == "SUMMARY.md" || entry.path().is_dir() {
113                continue;
114            }
115            let mut target = format!("{}", entry.path().to_str().unwrap());
116            target = target
117                .strip_prefix(format!("{}/src{}", self.source, std::path::MAIN_SEPARATOR).as_str())
118                .unwrap()
119                .to_string();
120            target = target.replace("\\", "-");
121            target = target.replace("/", "-");
122            target = format!("{}/{}", self.name, target);
123
124            let mut data = std::fs::read(entry.path())?;
125
126            let i = progression
127                .iter()
128                .position(|n| n.eq(&target.replace(format!("{}/", &self.name).as_str(), "")));
129            if i.is_some() {
130                let index = i.unwrap();
131                let p;
132                if index == 0 {
133                    p = None;
134                } else {
135                    p = progression.get(index - 1);
136                }
137                let n = progression.get(index + 1);
138                let previous;
139                let next;
140                // Next is something, previous is not
141                if n.is_some() && p.is_none() {
142                    next = n.unwrap().replace(".md", "");
143                    data.splice(
144                        0..0,
145                        format!(
146                            r#"<table>
147<tr>
148<td><a href="home">◀</a></td>
149<td width="9999" align="center"></td>
150<td><a href="{next}">▶</a></td>
151</tr>
152</table>
153
154"#
155                        )
156                        .bytes(),
157                    );
158                }
159                // Next is nothing, previous is something
160                else if n.is_none() && p.is_some() {
161                    previous = p.unwrap().replace(".md", "");
162                    data.splice(
163                        0..0,
164                        format!(
165                            r#"<table>
166<tr>
167<td><a href="{previous}">◀</a></td>
168<td width="9999" align="center"></td>
169</tr>
170</table>
171
172"#
173                        )
174                        .bytes(),
175                    );
176                }
177                // Both next and previous are something
178                else if n.is_some() && p.is_some() {
179                    previous = p.unwrap().replace(".md", "");
180                    next = n.unwrap().replace(".md", "");
181                    data.splice(
182                        0..0,
183                        format!(
184                            r#"<table>
185<tr>
186<td><a href="{previous}">◀</a></td>
187<td width="9999" align="center"></td>
188<td><a href="{next}">▶</a></td>
189</tr>
190</table>
191
192"#
193                        )
194                        .bytes(),
195                    );
196                }
197            }
198
199            let _ = std::fs::write(&target, data);
200        }
201        Ok(())
202    }
203}