From 84a1960489c9c92ef898eb7617061035df39ac42 Mon Sep 17 00:00:00 2001
From: Dirkjan Ochtman <dirkjan@ochtman.nl>
Date: Wed, 29 Jan 2020 21:49:42 +0100
Subject: Move input module into askama_shared

---
 askama_shared/Cargo.toml   |   2 +
 askama_shared/src/input.rs | 221 +++++++++++++++++++++++++++++++++++++++++++++
 askama_shared/src/lib.rs   |   1 +
 3 files changed, 224 insertions(+)
 create mode 100644 askama_shared/src/input.rs

(limited to 'askama_shared')

diff --git a/askama_shared/Cargo.toml b/askama_shared/Cargo.toml
index 19d3274..f82f026 100644
--- a/askama_shared/Cargo.toml
+++ b/askama_shared/Cargo.toml
@@ -22,10 +22,12 @@ humansize = { version = "1.1.0", optional = true }
 # https://github.com/rust-lang/rust/issues/62146
 nom = { version = "5", default-features = false, features = ["std"] }
 num-traits = { version = "0.2.6", optional = true }
+quote = "1"
 serde = { version = "1.0", optional = true, features = ["derive"] }
 serde_derive = { version = "1.0", optional = true }
 serde_json = { version = "1.0", optional = true }
 serde_yaml = { version = "0.8", optional = true }
+syn = "1"
 toml = { version = "0.5", optional = true }
 
 [package.metadata.docs.rs]
diff --git a/askama_shared/src/input.rs b/askama_shared/src/input.rs
new file mode 100644
index 0000000..53718f0
--- /dev/null
+++ b/askama_shared/src/input.rs
@@ -0,0 +1,221 @@
+use crate::{Config, Syntax};
+
+use std::path::PathBuf;
+
+use quote::ToTokens;
+use syn;
+
+pub struct TemplateInput<'a> {
+    pub ast: &'a syn::DeriveInput,
+    pub config: &'a Config<'a>,
+    pub syntax: &'a Syntax<'a>,
+    pub source: Source,
+    pub print: Print,
+    pub escaper: &'a str,
+    pub ext: Option<String>,
+    pub parent: Option<&'a syn::Type>,
+    pub path: PathBuf,
+}
+
+impl<'a> TemplateInput<'a> {
+    /// Extract the template metadata from the `DeriveInput` structure. This
+    /// mostly recovers the data for the `TemplateInput` fields from the
+    /// `template()` attribute list fields; it also finds the of the `_parent`
+    /// field, if any.
+    pub fn new<'n>(ast: &'n syn::DeriveInput, config: &'n Config) -> TemplateInput<'n> {
+        // Check that an attribute called `template()` exists and that it is
+        // the proper type (list).
+        let meta = ast
+            .attrs
+            .iter()
+            .find_map(|attr| match attr.parse_meta() {
+                Ok(m) => {
+                    if m.path().is_ident("template") {
+                        Some(m)
+                    } else {
+                        None
+                    }
+                }
+                Err(e) => panic!("unable to parse attribute: {}", e),
+            })
+            .expect("no attribute 'template' found");
+
+        let meta_list = match meta {
+            syn::Meta::List(inner) => inner,
+            _ => panic!("attribute 'template' has incorrect type"),
+        };
+
+        // Loop over the meta attributes and find everything that we
+        // understand. Raise panics if something is not right.
+        // `source` contains an enum that can represent `path` or `source`.
+        let mut source = None;
+        let mut print = Print::None;
+        let mut escaping = None;
+        let mut ext = None;
+        let mut syntax = None;
+        for item in meta_list.nested {
+            let pair = match item {
+                syn::NestedMeta::Meta(syn::Meta::NameValue(ref pair)) => pair,
+                _ => panic!(
+                    "unsupported attribute argument {:?}",
+                    item.to_token_stream()
+                ),
+            };
+
+            if pair.path.is_ident("path") {
+                if let syn::Lit::Str(ref s) = pair.lit {
+                    if source.is_some() {
+                        panic!("must specify 'source' or 'path', not both");
+                    }
+                    source = Some(Source::Path(s.value()));
+                } else {
+                    panic!("template path must be string literal");
+                }
+            } else if pair.path.is_ident("source") {
+                if let syn::Lit::Str(ref s) = pair.lit {
+                    if source.is_some() {
+                        panic!("must specify 'source' or 'path', not both");
+                    }
+                    source = Some(Source::Source(s.value()));
+                } else {
+                    panic!("template source must be string literal");
+                }
+            } else if pair.path.is_ident("print") {
+                if let syn::Lit::Str(ref s) = pair.lit {
+                    print = s.value().into();
+                } else {
+                    panic!("print value must be string literal");
+                }
+            } else if pair.path.is_ident("escape") {
+                if let syn::Lit::Str(ref s) = pair.lit {
+                    escaping = Some(s.value());
+                } else {
+                    panic!("escape value must be string literal");
+                }
+            } else if pair.path.is_ident("ext") {
+                if let syn::Lit::Str(ref s) = pair.lit {
+                    ext = Some(s.value());
+                } else {
+                    panic!("ext value must be string literal");
+                }
+            } else if pair.path.is_ident("syntax") {
+                if let syn::Lit::Str(ref s) = pair.lit {
+                    syntax = Some(s.value())
+                } else {
+                    panic!("syntax value must be string literal");
+                }
+            } else {
+                panic!(
+                    "unsupported attribute key '{}' found",
+                    pair.path.to_token_stream()
+                )
+            }
+        }
+
+        // Validate the `source` and `ext` value together, since they are
+        // related. In case `source` was used instead of `path`, the value
+        // of `ext` is merged into a synthetic `path` value here.
+        let source = source.expect("template path or source not found in attributes");
+        let path = match (&source, &ext) {
+            (&Source::Path(ref path), None) => config.find_template(path, None),
+            (&Source::Source(_), Some(ext)) => PathBuf::from(format!("{}.{}", ast.ident, ext)),
+            (&Source::Path(_), Some(_)) => {
+                panic!("'ext' attribute cannot be used with 'path' attribute")
+            }
+            (&Source::Source(_), None) => {
+                panic!("must include 'ext' attribute when using 'source' attribute")
+            }
+        };
+
+        // Check to see if a `_parent` field was defined on the context
+        // struct, and store the type for it for use in the code generator.
+        let parent = match ast.data {
+            syn::Data::Struct(syn::DataStruct {
+                fields: syn::Fields::Named(ref fields),
+                ..
+            }) => fields
+                .named
+                .iter()
+                .find(|f| f.ident.as_ref().filter(|name| *name == "_parent").is_some())
+                .map(|f| &f.ty),
+            _ => None,
+        };
+
+        if parent.is_some() {
+            eprint!(
+                "   --> in struct {}\n   = use of deprecated field '_parent'\n",
+                ast.ident
+            );
+        }
+
+        // Validate syntax
+        let syntax = syntax.map_or_else(
+            || config.syntaxes.get(config.default_syntax).unwrap(),
+            |s| {
+                config
+                    .syntaxes
+                    .get(&s)
+                    .unwrap_or_else(|| panic!("attribute syntax {} not exist", s))
+            },
+        );
+
+        // Match extension against defined output formats
+
+        let extension = escaping.unwrap_or_else(|| {
+            path.extension()
+                .map(|s| s.to_str().unwrap())
+                .unwrap_or("")
+                .to_string()
+        });
+
+        let mut escaper = None;
+        for (extensions, path) in &config.escapers {
+            if extensions.contains(&extension) {
+                escaper = Some(path);
+                break;
+            }
+        }
+
+        let escaper = escaper.unwrap_or_else(|| {
+            panic!("no escaper defined for extension '{}'", extension);
+        });
+
+        TemplateInput {
+            ast,
+            config,
+            source,
+            print,
+            escaper,
+            ext,
+            parent,
+            path,
+            syntax,
+        }
+    }
+}
+
+pub enum Source {
+    Path(String),
+    Source(String),
+}
+
+#[derive(PartialEq)]
+pub enum Print {
+    All,
+    Ast,
+    Code,
+    None,
+}
+
+impl From<String> for Print {
+    fn from(s: String) -> Print {
+        use self::Print::*;
+        match s.as_ref() {
+            "all" => All,
+            "ast" => Ast,
+            "code" => Code,
+            "none" => None,
+            v => panic!("invalid value for print option: {}", v),
+        }
+    }
+}
diff --git a/askama_shared/src/lib.rs b/askama_shared/src/lib.rs
index b2f1271..fa4a9a0 100644
--- a/askama_shared/src/lib.rs
+++ b/askama_shared/src/lib.rs
@@ -16,6 +16,7 @@ mod error;
 pub use crate::error::{Error, Result};
 pub mod filters;
 pub mod helpers;
+pub mod input;
 pub mod parser;
 
 #[derive(Debug)]
-- 
cgit