1#[macro_use]
2extern crate pest_derive;
3
4mod codeblock_parser;
5
6use asciidoctrine::*;
7use std::collections::HashMap;
8use std::collections::hash_map;
9use topological_sort::TopologicalSort;
10use core::cell::RefCell;
11use std::rc::Rc;
12use asciidoctrine::util::Environment;
13#[macro_use]
14extern crate log;
15
16pub struct SnippetDB {
17 snippets: HashMap<String, Snippet>,
18}
19
20impl SnippetDB {
21 pub fn new() -> Self {
22 SnippetDB {
23 snippets: HashMap::default(),
24 }
25 }
26
27 pub fn store(&mut self, name: String, snippet: Snippet) {
29 let base = self.snippets.get_mut(&name);
30 match base {
31 Some(base) => {
32 if &base.children.len() < &1 {
33 let other = base.clone();
34 &base.children.push(other);
35 }
36 for dependency in snippet.depends_on.clone().into_iter() {
37 base.depends_on.push(dependency);
38 }
39 base.children.push(snippet);
40 }
41 None => {
42 self.snippets.insert(name, snippet);
43 }
44 }
45 }
46
47 pub fn get(&self, name: &str) -> Option<&Snippet> {
49 self.snippets.get(name)
50 }
51
52 pub fn pop(&mut self, name: &str) -> Option<Snippet> {
55 self.snippets.remove(name)
56 }
57
58 pub fn iter(&self) -> hash_map::Iter<String, Snippet> {
60 self.snippets.iter()
61 }
62}
63
64#[derive(Clone, Debug)]
65pub enum SnippetType {
66 Save(String),
67 Eval(String),
68 Pipe,
69 Plain,
70}
71
72#[derive(Clone, Debug)]
73pub struct Snippet {
74 pub kind: SnippetType,
75 pub content: String,
76 pub raw_content: String,
77 pub children: Vec<Snippet>,
78 pub depends_on: Vec<String>,
81 pub attributes: HashMap<String, String>,
82 pub raw: bool,
83}
84
85impl Snippet {
86 fn get_raw_content(&self, join_str: &str) -> String {
87 if self.children.len() > 0 {
88 let mut iter = self.children.iter();
89 let start = iter.next().unwrap().raw_content.clone();
90 iter.fold(start, |mut base, snippet| {
91 base.push_str(join_str);
92 base.push_str(&snippet.raw_content);
93 base
94 })
95 } else {
96 self.raw_content.to_string()
97 }
98 }
99}
100
101#[derive(Clone)]
102struct LisiWrapper {
103 pub snippets: Rc<RefCell<SnippetDB>>,
104}
105
106impl LisiWrapper {
107 pub fn store(&mut self, name: &str, content: &str) {
108 let mut snippets = self.snippets.borrow_mut();
109
110 snippets.pop(name);
111
112 snippets.store(
113 name.to_string(),
114 Snippet {
115 kind: SnippetType::Plain,
116 content: content.to_string(),
117 raw_content: content.to_string(),
118 children: Vec::new(),
119 depends_on: Vec::new(),
120 attributes: HashMap::default(),
121 raw: true,
122 },
123 );
124 }
125
126 pub fn get_snippet(&mut self, name: &str) -> rhai::Dynamic {
127 let snippets = self.snippets.borrow_mut();
128
129 match snippets.get(name) {
130 Some(snippet) => {
131 let mut attributes: HashMap<rhai::ImmutableString, rhai::Dynamic> = HashMap::default();
132 for (k,v) in snippet.attributes.clone().drain() {
133 attributes.insert(k.into(), v.into());
134 }
135
136 let mut out: HashMap<rhai::ImmutableString, rhai::Dynamic> = HashMap::default();
137 out.insert("content".into(), snippet.get_raw_content("\n").into());
138 out.insert("attrs".into(), attributes.into());
139
140 out.into()
141 },
142 None => rhai::Dynamic::from(()),
143 }
144 }
145
146 pub fn get_snippet_names(&mut self) -> rhai::Array {
147 let mut snippets = self.snippets.borrow_mut();
148
149 let mut out = rhai::Array::new();
150
151 let mut keys = snippets
152 .iter()
153 .map(|(key, _)| { key.to_string() })
154 .collect::<Vec<_>>();
155 keys.sort();
156 let out: rhai::Array = keys
157 .into_iter()
158 .map(|key| { key.into() })
159 .collect();
160
161 out
162 }
163}
164
165#[derive(thiserror::Error, Debug)]
166pub enum Error {
167 #[error("a nessessary attribute is missing")]
168 Missing,
169 #[error(transparent)]
170 Asciidoctrine(#[from] asciidoctrine::AsciidoctrineError),
171 #[error("io problem")]
172 Io(#[from] std::io::Error),
173}
174
175pub struct Lisi {
176 dependencies: TopologicalSort<String>,
177 env: asciidoctrine::util::Env,
178}
179
180impl Lisi {
181 pub fn new() -> Self {
182 Lisi {
183 dependencies: TopologicalSort::new(),
184 env: util::Env::Io(util::Io::new()),
185 }
186 }
187
188 pub fn extract(&mut self, mut snippets: SnippetDB, input: &ElementSpan) -> Result<SnippetDB, Error> {
190 match &input.element {
191 Element::TypedBlock {
192 kind: BlockType::Listing,
193 } => {
194 let args = &mut input.positional_attributes.iter();
195 if !(args.next() == Some(&AttributeValue::Ref("source"))) {
196 return Ok(snippets);
197 }
198 let mut interpreter = None;
199 if let Some(value) = args.next() {
200 match &value {
201 AttributeValue::Ref(value) => {
202 interpreter = Some(*value);
203 },
204 AttributeValue::String(value) => {
205 interpreter = Some(value.as_str());
206 }
207 }
208 }
209
210 let title = input.get_attribute("title");
211 let path = input.get_attribute("path").or(title);
212
213 let id = input.get_attribute("anchor").unwrap_or(
214 &format!("_id_{}_{}", input.start, input.end),
215 ).to_string(); let interpreter = input.get_attribute("interpreter").or(interpreter);
218 let mut raw = false;
219
220 let mut kind = SnippetType::Plain;
221
222 for argument in args {
223 match argument {
224 AttributeValue::Ref("save") => {
225 let path = path.ok_or(Error::Missing)?;
226 kind = SnippetType::Save(path.to_string());
227 }
228 AttributeValue::Ref("eval") => {
229 let interpreter = interpreter.clone().ok_or(Error::Missing)?;
230 kind = SnippetType::Eval(interpreter.to_string());
231 }
232 AttributeValue::Ref("pipe") => {
233 kind = SnippetType::Pipe;
234 }
235 AttributeValue::Ref("lisi-raw") => {
236 raw = true;
237 }
238 _ => (),
239 }
240 }
241
242 let mut attributes: HashMap<String, String> = HashMap::default();
243
244 for key in input.attributes.iter().map(|attr|{ attr.key.clone() }) {
245 attributes.insert(key.clone(), input.get_attribute(&key).unwrap().to_string());
246 }
247
248 let content = input
249 .get_attribute("content")
250 .unwrap_or(input.content);
251 let mut dependencies = Vec::new();
252 for dependency in codeblock_parser::get_dependencies(content).iter() {
253 dependencies.push(dependency.to_string());
254 }
255 snippets.store(
256 id.to_string(),
257 Snippet {
258 kind,
259 content: content.to_string(),
260 raw_content: content.to_string(),
261 children: Vec::new(),
262 depends_on: dependencies,
263 attributes,
264 raw,
265 },
266 );
267
268 Ok(snippets)
269 }
270 Element::Styled => {
271 let id = match input.get_attribute("anchor") {
272 Some(id) => id.to_string(),
273 None => { return Ok(snippets); },
274 };
275 let kind = SnippetType::Plain;
276 let raw = false;
277 let dependencies = Vec::new();
278 let mut attributes: HashMap<String, String> = HashMap::default();
279
280 for key in input.attributes.iter().map(|attr|{ attr.key.clone() }) {
281 attributes.insert(key.clone(), input.get_attribute(&key).unwrap().to_string());
282 }
283 let content = input
284 .get_attribute("content")
285 .unwrap_or(input.content);
286 snippets.store(
287 id.to_string(),
288 Snippet {
289 kind,
290 content: content.to_string(),
291 raw_content: content.to_string(),
292 children: Vec::new(),
293 depends_on: dependencies,
294 attributes,
295 raw,
296 },
297 );
298
299 Ok(snippets)
300 }
301 Element::IncludeElement(ast) => ast
302 .inner
303 .elements
304 .iter()
305 .try_fold(snippets, |snippets, element| {
306 self.extract(snippets, element)
307 }),
308 _ => input.children.iter().try_fold(snippets, |snippets, element| {
309 self.extract(snippets, element)
310 }),
311 }
312 }
313
314 pub fn calculate_snippet_ordering(&mut self, snippets: &SnippetDB) {
316 for (key, snippet) in snippets.iter() {
317 self.dependencies.insert(key);
320
321 for child in snippet.children.iter() {
322 for dependency in child.depends_on.iter() {
323 self.dependencies.add_dependency(dependency, key);
324 }
325 }
326 for dependency in snippet.depends_on.iter() {
327 self.dependencies.add_dependency(dependency, key);
328 }
329 }
330 }
331
332 pub fn save(&mut self, path: &str, content: &str) -> Result<(), Error> {
334 let content = content.lines()
335 .map(|line| { String::from(line.trim_end()) + "\n" })
336 .collect::<String>();
337
338 self.env.write(path, &content)?;
341
342 Ok(())
343 }
344
345 pub fn eval(&mut self, interpreter: String, content: String) -> Result<(), Error> {
347
348 let (success, out, err) = self.env.eval(&interpreter, &content)?;
349
350 if success {
352 info!("{}", out); } else {
354 error!("External command failed:\n {}", err) }
356
357 Ok(())
358 }
359
360 pub fn pipe(&mut self, content: &str, db: &Rc<RefCell<SnippetDB>>) -> Result<(), Error> {
362 let mut engine = rhai::Engine::new();
363
364 let mut scope = rhai::Scope::new();
365
366 let wrapper = LisiWrapper {
367 snippets: Rc::clone(&db)
368 };
369 scope.push_constant("lisi", wrapper);
370
371 engine.register_type_with_name::<LisiWrapper>("LisiType");
372 engine.register_fn("store", LisiWrapper::store);
373 engine.register_fn("get_snippet", LisiWrapper::get_snippet);
374 engine.register_fn("get_snippet_names", LisiWrapper::get_snippet_names);
375
376 engine.eval_with_scope::<()>(&mut scope, content)
377 .unwrap_or_else(|e| {
378 error!("Piping of snippet failed:\n {}", e);
379 });
380
381 Ok(())
382 }
383
384 pub fn from_env(env: util::Env) -> Self {
385 let mut base = Lisi::new();
386 base.env = env;
387
388 base
389 }
390
391 pub fn into_cache(self) -> Option<HashMap<String, String>> {
392 self.env.get_cache()
393 }
394
395 pub fn extract_ast(&mut self, input: &AST) -> Result<SnippetDB, Error> {
397 let snippets = SnippetDB::new();
398
399 input.elements.iter().try_fold(snippets, |snippets, element| {
401 self.extract(snippets, element)
402 })
403 }
404
405 pub fn generate_outputs(&mut self, snippets: SnippetDB, ast: &AST) -> Result<(), Error> {
407 let source = ast.get_attribute("source").unwrap_or("");
408 let db = Rc::new(RefCell::new(snippets));
409 let snippets = Rc::clone(&db);
410
411 loop {
412 let key = self.dependencies.pop();
413 let snippet = match &key {
414 Some(key) => {
415 let mut snippets = snippets.borrow_mut();
416 let snippet = snippets.pop(&key);
417
418 match snippet {
419 Some(mut snippet) => {
420 if !snippet.raw {
421 if snippet.children.len() > 0 {
422 let mut children = Vec::new();
423 for mut child in snippet.children.into_iter() {
424 let content = child.content;
425 let content = codeblock_parser::merge_dependencies(content.as_str(), &snippets, key);
426 child.content = content;
427 children.push(child);
428 }
429 snippet.children = children;
430 } else {
431 let content = snippet.content;
432 let content = codeblock_parser::merge_dependencies(content.as_str(), &snippets, key);
433 snippet.content = content;
434 }
435 };
436
437 snippets.store(key.to_string(), snippet.clone());
438 Some(snippet)
439 }
440 None => {
441 warn!("{}: Dependency `{}` nicht gefunden", source, key);
444 None
445 }
446 }
447 }
448 None => {
449 if !self.dependencies.is_empty() {
450 error!(
451 "Es ist ein Ring in den Abhängigkeiten ({:#?})",
452 self.dependencies
453 );
454 }
455 break;
456 }
457 };
458
459 if let Some(snippet) = snippet {
460 match &snippet.kind {
461 SnippetType::Eval(interpreter) => {
462 self.eval(interpreter.to_string(), snippet.content)?;
463 }
464 SnippetType::Plain => {}
465 SnippetType::Save(path) => {
466 self.save(path, &snippet.content)?;
467 }
468 SnippetType::Pipe => {
469 self.pipe(&snippet.content, &db)?;
470 }
471 }
472 }
473 }
474
475 Ok(())
476 }
477}
478
479impl Extension for Lisi {
480 fn transform<'a>(&mut self, input: AST<'a>) -> anyhow::Result<AST<'a>> {
481 let snippets = self.extract_ast(&input)?;
482
483 self.calculate_snippet_ordering(&snippets);
484
485 self.generate_outputs(snippets, &input)?;
486
487 Ok(input)
488 }
489}