From 4c0388d11e1bafb85250b649ff610d3f9c7f9452 Mon Sep 17 00:00:00 2001
From: René Kijewski <kijewski@library.vetmed.fu-berlin.de>
Date: Thu, 10 Mar 2022 08:44:35 +0100
Subject: Reduce askama_derive to a single re-export

All the hard work in askama_derive was actually done in askama_shared.
This PR removes the back-and-forth interaction between the two crates.
Now askama_derive is a single re-export of `#[derive(Template)]` which
has to be done in a proc_macro crate.

This most likely means that askama_derive is "final", unless another
derive template needs to be introduced in the future.
---
 askama_shared/src/derive.rs | 105 ++++++++++++++++++++++++++++++++++++++++++++
 askama_shared/src/lib.rs    |   2 +
 2 files changed, 107 insertions(+)
 create mode 100644 askama_shared/src/derive.rs

(limited to 'askama_shared/src')

diff --git a/askama_shared/src/derive.rs b/askama_shared/src/derive.rs
new file mode 100644
index 0000000..04b3ba0
--- /dev/null
+++ b/askama_shared/src/derive.rs
@@ -0,0 +1,105 @@
+use std::collections::HashMap;
+use std::path::PathBuf;
+
+use proc_macro2::TokenStream;
+
+use crate::heritage::{Context, Heritage};
+use crate::input::{Print, Source, TemplateInput};
+use crate::parser::{parse, Expr, Node};
+use crate::{generator, get_template_source, read_config_file, CompileError, Config};
+
+/// The actual implementation for askama_derive::Template
+pub fn derive_template(input: TokenStream) -> TokenStream {
+    let ast: syn::DeriveInput = syn::parse2(input).unwrap();
+    match build_template(&ast) {
+        Ok(source) => source.parse().unwrap(),
+        Err(e) => e.into_compile_error(),
+    }
+}
+
+/// Takes a `syn::DeriveInput` and generates source code for it
+///
+/// Reads the metadata from the `template()` attribute to get the template
+/// metadata, then fetches the source from the filesystem. The source is
+/// parsed, and the parse tree is fed to the code generator. Will print
+/// the parse tree and/or generated source according to the `print` key's
+/// value as passed to the `template()` attribute.
+fn build_template(ast: &syn::DeriveInput) -> Result<String, CompileError> {
+    let config_toml = read_config_file()?;
+    let config = Config::new(&config_toml)?;
+    let input = TemplateInput::new(ast, &config)?;
+    let source: String = match input.source {
+        Source::Source(ref s) => s.clone(),
+        Source::Path(_) => get_template_source(&input.path)?,
+    };
+
+    let mut sources = HashMap::new();
+    find_used_templates(&input, &mut sources, source)?;
+
+    let mut parsed = HashMap::new();
+    for (path, src) in &sources {
+        parsed.insert(path.as_path(), parse(src, input.syntax)?);
+    }
+
+    let mut contexts = HashMap::new();
+    for (path, nodes) in &parsed {
+        contexts.insert(*path, Context::new(input.config, path, nodes)?);
+    }
+
+    let ctx = &contexts[input.path.as_path()];
+    let heritage = if !ctx.blocks.is_empty() || ctx.extends.is_some() {
+        Some(Heritage::new(ctx, &contexts))
+    } else {
+        None
+    };
+
+    if input.print == Print::Ast || input.print == Print::All {
+        eprintln!("{:?}", parsed[input.path.as_path()]);
+    }
+
+    let code = generator::generate(&input, &contexts, heritage.as_ref())?;
+    if input.print == Print::Code || input.print == Print::All {
+        eprintln!("{}", code);
+    }
+    Ok(code)
+}
+
+fn find_used_templates(
+    input: &TemplateInput<'_>,
+    map: &mut HashMap<PathBuf, String>,
+    source: String,
+) -> Result<(), CompileError> {
+    let mut dependency_graph = Vec::new();
+    let mut check = vec![(input.path.clone(), source)];
+    while let Some((path, source)) = check.pop() {
+        for n in parse(&source, input.syntax)? {
+            match n {
+                Node::Extends(Expr::StrLit(extends)) => {
+                    let extends = input.config.find_template(extends, Some(&path))?;
+                    let dependency_path = (path.clone(), extends.clone());
+                    if dependency_graph.contains(&dependency_path) {
+                        return Err(format!(
+                            "cyclic dependecy in graph {:#?}",
+                            dependency_graph
+                                .iter()
+                                .map(|e| format!("{:#?} --> {:#?}", e.0, e.1))
+                                .collect::<Vec<String>>()
+                        )
+                        .into());
+                    }
+                    dependency_graph.push(dependency_path);
+                    let source = get_template_source(&extends)?;
+                    check.push((extends, source));
+                }
+                Node::Import(_, import, _) => {
+                    let import = input.config.find_template(import, Some(&path))?;
+                    let source = get_template_source(&import)?;
+                    check.push((import, source));
+                }
+                _ => {}
+            }
+        }
+        map.insert(path, source);
+    }
+    Ok(())
+}
diff --git a/askama_shared/src/lib.rs b/askama_shared/src/lib.rs
index 8a336bb..5a8facd 100644
--- a/askama_shared/src/lib.rs
+++ b/askama_shared/src/lib.rs
@@ -13,9 +13,11 @@ use proc_macro2::{Span, TokenStream};
 #[cfg(feature = "serde")]
 use serde::Deserialize;
 
+pub use crate::derive::derive_template;
 pub use crate::input::extension_to_mime_type;
 pub use askama_escape::MarkupDisplay;
 
+mod derive;
 mod error;
 pub use crate::error::{Error, Result};
 pub mod filters;
-- 
cgit