agda_tree/command/
build.rs1use html_parser::{Dom, Element, Node};
2use std::collections::VecDeque;
3use std::fs::{self, create_dir_all, read_to_string, File};
4use std::io::{self, Write};
5use std::iter::zip;
6use std::path::{Path, PathBuf};
7use std::process::Command;
8
9use crate::extract::extract_agda_code;
10use crate::tree::Tree;
11
12pub fn execute(working_dir: &PathBuf, output_dir: &PathBuf, skip_agda: bool) -> io::Result<()> {
13 let paths = fs::read_dir(working_dir)?
14 .filter_map(Result::ok)
15 .filter_map(|f| {
16 if let Ok(ft) = f.file_type() {
17 if ft.is_file() && f.path().to_str()?.ends_with(".lagda.tree") {
18 return Some(f.path());
19 }
20 }
21 None
22 })
23 .collect::<Vec<PathBuf>>();
24
25 let trees = generate_lagda_md(&paths)?;
26 let index_path = generate_index(working_dir, &paths)?;
27 if !skip_agda {
28 run_agda_build(working_dir, index_path)?;
29 }
30 collect_html(working_dir, output_dir, &paths, trees)
31}
32
33fn generate_lagda_md(paths: &Vec<PathBuf>) -> io::Result<Vec<Tree>> {
34 let mut r = vec![];
35 for path in paths {
36 let (tree, agda_blocks) = extract_agda_code(&path)?;
37 let mut middle = File::create(path.with_extension("md"))?;
38 for block in agda_blocks {
39 middle.write(block.as_bytes())?;
40 }
41 r.push(tree);
42 }
43 Ok(r)
44}
45
46fn run_agda_build(working_dir: &PathBuf, index_path: PathBuf) -> io::Result<()> {
47 let _ = Command::new("agda")
48 .current_dir(working_dir)
49 .args([
50 "--html",
51 index_path.into_os_string().into_string().unwrap().as_str(),
52 ])
53 .output()
54 .expect("failed to build agda htmls");
55 Ok(())
56}
57
58fn generate_index(working_dir: &PathBuf, paths: &Vec<PathBuf>) -> io::Result<PathBuf> {
59 let imports = paths
61 .into_iter()
62 .map(|path| format!("import {}", path.file_prefix().unwrap().to_str().unwrap()))
63 .collect::<Vec<String>>();
64 let index_path = working_dir.join("index.agda");
65 let mut index = File::create(&index_path)?;
66 for imp in imports {
67 index.write(imp.as_bytes())?;
68 }
69 Ok(index_path)
70}
71
72fn collect_html(
73 working_dir: &PathBuf,
74 output_dir: &PathBuf,
75 paths: &Vec<PathBuf>,
76 trees: Vec<Tree>,
77) -> io::Result<()> {
78 for (path, tree) in zip(paths.into_iter(), trees.into_iter()) {
79 let basename = path.file_prefix().unwrap().to_str().unwrap();
80 let agda_html = working_dir
81 .join("html")
82 .join(basename)
83 .with_extension("html");
84
85 let s = read_to_string(&agda_html)
86 .expect(format!("failed to open generated html file `{:?}`", agda_html).as_str());
87 let dom = Dom::parse(s.as_str()).unwrap();
88
89 let nodes = &dom.children[0].element().unwrap().children[1]
90 .element()
91 .unwrap()
92 .children[0]
93 .element()
94 .unwrap()
95 .children;
96
97 let forester_blocks = agda_html_blocks(working_dir, nodes);
98
99 let new_tree = tree.merge(forester_blocks);
100
101 create_dir_all(output_dir)?;
102 let output = File::create(output_dir.join(basename).with_extension("tree"))?;
103 new_tree.write(output);
104 }
105
106 Ok(())
107}
108
109fn agda_html_blocks(working_dir: &PathBuf, nodes: &Vec<Node>) -> VecDeque<String> {
110 let mut blocks = VecDeque::new();
111 let mut buffer = String::new();
112 let mut recording = false;
113 let mut line = line_of_symbol(nodes[0].element().unwrap());
114 let mut last_col_end = end_col_of_symbol(nodes[0].element().unwrap());
115
116 for node in nodes {
117 let elem = node.element().unwrap();
118 if is_block_start(elem) {
119 recording = true;
120 line = line_of_symbol(elem);
121 last_col_end = col_of_symbol(elem);
122 buffer.push_str("\\<html:pre>[class]{Agda}{\n");
123 } else if is_block_end(elem) {
124 buffer.push_str("}");
125 blocks.push_back(buffer);
126 recording = false;
127 buffer = String::new();
128 } else if recording {
129 if line_of_symbol(elem) > line {
130 for _ in 0..(line_of_symbol(elem) - line) {
131 buffer.push('\n');
132 }
133 last_col_end = 1;
134 }
135 if col_of_symbol(elem) > last_col_end {
136 for _ in 0..col_of_symbol(elem) - last_col_end {
137 buffer.push(' ');
138 }
139 }
140 last_col_end = end_col_of_symbol(elem);
141 line = line_of_symbol(elem);
142 buffer.push_str(symbol2forest(working_dir, elem).as_str());
143 }
144 }
145
146 blocks
147}
148
149fn is_block_start(elem: &Element) -> bool {
150 !elem.children.is_empty() && elem.children[0].text().unwrap().contains("```agda")
151}
152fn is_block_end(elem: &Element) -> bool {
153 !elem.children.is_empty() && elem.children[0].text().unwrap().contains("```")
154}
155
156fn line_of_symbol(elem: &Element) -> usize {
157 elem.source_span.end_line
158}
159fn col_of_symbol(elem: &Element) -> usize {
160 elem.source_span.start_column
161}
162fn end_col_of_symbol(elem: &Element) -> usize {
163 elem.source_span.end_column
164}
165
166fn symbol2forest(working_dir: &PathBuf, elem: &Element) -> String {
167 let mut s = format!("\\<html:{}>", elem.name);
168
169 if elem.id.is_some() {
170 s.push_str(format!("[id]{{{}}}", elem.id.clone().unwrap().as_str()).as_str());
171 }
172 if !elem.classes.is_empty() {
173 s.push_str(format!("[class]{{{}}}", elem.classes[0]).as_str());
174 }
175 for (k, v) in &elem.attributes {
176 let value = v.clone().unwrap();
177 let value = if k == "href" {
178 let split = value.split_terminator('#').collect::<Vec<&str>>();
183 let a_link = split[0];
184 let path = Path::new(a_link);
185 if working_dir.join(path).with_extension("lagda.tree").exists() {
186 let mut s = path.with_extension("xml").to_str().unwrap().to_owned();
187 s.push('#');
188 if split.len() == 2 {
189 let id_part = split[1];
190 s.push_str(id_part);
191 }
192 s
193 } else {
194 value
195 }
196 } else {
197 value
198 };
199 s.push_str(format!("[{}]{{{}}}", k, value).as_str());
200 }
201 if elem.children.is_empty() {
202 s.push_str("{}");
203 } else {
204 let childtext = elem.children[0].text().unwrap();
205 let childtext = if childtext.contains("'") {
207 childtext.replace("'", "'")
208 } else {
209 childtext.to_owned()
210 };
211 if childtext.contains('(')
212 || childtext.contains(')')
213 || childtext.contains('{')
214 || childtext.contains('}')
215 {
216 s.push_str(format!("{{\\startverb{}\\stopverb}}", childtext).as_str());
217 } else {
218 s.push_str(format!("{{{}}}", childtext).as_str());
219 }
220 }
221
222 s
223}