lisi/
lib.rs

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  /// Stores a snippet in the internal database
28  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  /// Get the snippet with the name `name`
48  pub fn get(&self, name: &str) -> Option<&Snippet> {
49    self.snippets.get(name)
50  }
51
52  /// Get the snippet with the name `name` and
53  /// remove it from the snippet database
54  pub fn pop(&mut self, name: &str) -> Option<Snippet> {
55    self.snippets.remove(name)
56  }
57
58  /// Get iterator over all snippets
59  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  /// List of all keys the snippet depends on
79  /// before it can be processed
80  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  /// Gets recursively all snippets from an element
189  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(); // TODO Vielleicht Datei + Zeile?
216
217        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  /// Builds the dependency tree for topological sorting
315  pub fn calculate_snippet_ordering(&mut self, snippets: &SnippetDB) {
316    for (key, snippet) in snippets.iter() {
317      // TODO Vielleicht sollten nur `save` und `eval` snippets
318      // unabhängig von dependencies aufgenommen werden?
319      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  /// Saves a Snippet to a file
333  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    // TODO Allow directory prefix from options
339
340    self.env.write(path, &content)?;
341
342    Ok(())
343  }
344
345  /// Run a snippet in an interpreter
346  pub fn eval(&mut self, interpreter: String, content: String) -> Result<(), Error> {
347
348    let (success, out, err) = self.env.eval(&interpreter, &content)?;
349
350    // TODO in den Asciidoc AST einbinden
351    if success {
352      info!("{}", out); // TODO entfernen
353    } else {
354      error!("External command failed:\n {}", err) // TODO entfernen
355    }
356
357    Ok(())
358  }
359
360  /// Use a snippet to manipulate the db instead of using it directly
361  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  /// Gets all snippets from the ast
396  pub fn extract_ast(&mut self, input: &AST) -> Result<SnippetDB, Error> {
397    let snippets = SnippetDB::new();
398
399    // extract snippets from all inner elements
400    input.elements.iter().try_fold(snippets, |snippets, element| {
401      self.extract(snippets, element)
402    })
403  }
404
405  /// Build all snippets (Runs the vm)
406  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              // TODO Fehlermeldung im AST. Ein Snippet sollte zu
442              // diesem Zeitpunkt immer bereits erstellt sein.
443              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}