summaryrefslogtreecommitdiffstats
path: root/src/generator.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/generator.rs')
-rw-r--r--src/generator.rs209
1 files changed, 209 insertions, 0 deletions
diff --git a/src/generator.rs b/src/generator.rs
new file mode 100644
index 0000000..0ab4511
--- /dev/null
+++ b/src/generator.rs
@@ -0,0 +1,209 @@
1use crate::entities::{
2 Described, Description, EntitiesManager, EntitiesManagerComponents, Entity, Usr,
3};
4
5use anyhow::{Context, Result};
6use thiserror::Error;
7use threadpool::ThreadPool;
8
9use std::collections::HashMap;
10use std::path::Path;
11use std::sync::{mpsc::channel, Arc};
12
13pub(crate) fn generate(base_dir: &Path, manager: EntitiesManager) -> Result<()> {
14 let EntitiesManagerComponents {
15 toplevel_entities,
16 descriptions,
17 } = manager.decompose();
18
19 let md_output_dir = base_dir.join("markdown");
20 let html_output_dir = base_dir.join("html");
21
22 std::fs::create_dir_all(&md_output_dir)
23 .context("Failed to create the markdown output directory")?;
24 std::fs::create_dir_all(&html_output_dir)
25 .context("Failed to create the HTML output directory")?;
26
27 let pool = ThreadPool::new(num_cpus::get());
28
29 let descriptions = Arc::new(descriptions);
30
31 let (tx, rx) = channel::<()>();
32
33 for (usr, entity) in toplevel_entities {
34 generate_recursively(
35 usr,
36 entity,
37 pool.clone(),
38 tx.clone(),
39 descriptions.clone(),
40 &md_output_dir,
41 &html_output_dir,
42 );
43 }
44
45 drop(tx);
46
47 // This is not really idiomatic, but iter returns None when every Sender is destroyed, so just
48 // by passing around Senders in generate_recursively, we wait for every job.
49 rx.iter().for_each(drop);
50
51 Ok(())
52}
53
54fn generate_recursively(
55 usr: Usr,
56 entity: Entity,
57 pool: ThreadPool,
58 tx: std::sync::mpsc::Sender<()>,
59 descriptions: Arc<HashMap<Usr, Description>>,
60 md_output_dir: impl AsRef<Path>,
61 html_output_dir: impl AsRef<Path>,
62) {
63 let descriptions = descriptions.clone();
64 let md_output_dir = md_output_dir.as_ref().to_owned();
65 let html_output_dir = html_output_dir.as_ref().to_owned();
66
67 let pool2 = pool.clone();
68 pool.execute(move || {
69 trace!("Trying to generate {}", usr.0);
70
71 let entity = Described::<Entity> {
72 entity,
73 description: descriptions.get(&usr).unwrap().clone(),
74 };
75
76 let leftovers = generate_single(
77 &usr,
78 entity,
79 &descriptions,
80 &md_output_dir,
81 &html_output_dir,
82 )
83 .unwrap();
84
85 for (usr, entity) in leftovers {
86 let pool = pool2.clone();
87 let tx = tx.clone();
88 generate_recursively(
89 usr,
90 entity,
91 pool,
92 tx,
93 descriptions.clone(),
94 md_output_dir.clone(),
95 html_output_dir.clone(),
96 );
97 }
98 });
99}
100
101fn generate_single(
102 usr: &Usr,
103 entity: Described<Entity>,
104 descriptions: &HashMap<Usr, Description>,
105 md_output_dir: impl AsRef<Path>,
106 html_output_dir: impl AsRef<Path>,
107) -> Result<HashMap<Usr, Entity>> {
108 use std::io::prelude::*;
109 use std::process::{Command, Stdio};
110
111 let md_output_file = md_output_dir.as_ref().join(&usr.0);
112 let html_output_file = html_output_dir.as_ref().join(&usr.0);
113
114 let mut command = Command::new("pandoc");
115
116 command
117 .stdin(Stdio::piped())
118 .stdout(Stdio::piped())
119 .stderr(Stdio::piped())
120 .args(&[
121 "--from=json",
122 "--to=markdown",
123 "--output",
124 md_output_file
125 .to_str()
126 .context("Entity name is not valid UTF-8")?,
127 ]);
128
129 debug!("Launching command: {:?}", command);
130
131 let mut pandoc = command
132 .spawn()
133 .context("Failed to execute Pandoc command")?;
134
135 let (pandoc_ast, leftovers) = entity.into_pandoc(&descriptions);
136
137 if log_enabled!(log::Level::Trace) {
138 let json =
139 serde_json::to_string(&pandoc_ast).context("Failed to convert Pandoc AST to JSON")?;
140 trace!("Sending json: {}", json);
141 write!(
142 pandoc.stdin.as_mut().expect("Pandoc stdin not captured"),
143 "{}",
144 &json,
145 )
146 .context("Failed to convert Pandoc AST to JSON")?;
147 } else {
148 serde_json::to_writer(
149 pandoc.stdin.as_mut().expect("Pandoc stdin not captured"),
150 &pandoc_ast,
151 )
152 .context("Failed to convert Pandoc AST to JSON")?;
153 }
154
155 let output = pandoc
156 .wait_with_output()
157 .expect("Pandoc command wasn't running");
158
159 if !output.status.success() {
160 Err(CommandError {
161 status: output.status,
162 stderr: String::from_utf8(output.stderr).expect("Pandoc outputted invalid UTF-8"),
163 })
164 .context("Pandoc command failed")?;
165 }
166
167 let mut command = Command::new("pandoc");
168 command
169 .stdin(Stdio::piped())
170 .stdout(Stdio::piped())
171 .stderr(Stdio::piped())
172 .args(&[
173 "--from=markdown",
174 "--to=html",
175 "--css=res/style.css",
176 "--standalone",
177 "--self-contained",
178 md_output_file
179 .to_str()
180 .context("Entity name is not valid UTF-8")?,
181 "--output",
182 html_output_file
183 .to_str()
184 .context("Entity name is not valid UTF-8")?,
185 ]);
186
187 debug!("Launching command: {:?}", command);
188
189 let output = command
190 .output()
191 .context("Failed to execute Pandoc command")?;
192
193 if !output.status.success() {
194 Err(CommandError {
195 status: output.status,
196 stderr: String::from_utf8(output.stderr).expect("Pandoc outputted invalid UTF-8"),
197 })
198 .context("Pandoc command failed")?;
199 }
200
201 Ok(leftovers)
202}
203
204#[derive(Debug, Clone, Error)]
205#[error("Command returned status {:?} and stderr {:?}", status, stderr)]
206struct CommandError {
207 status: std::process::ExitStatus,
208 stderr: String,
209}