Merge branch 'feature/include'

This commit is contained in:
Kogia-sima 2020-06-07 00:56:05 +09:00
commit f116941904
15 changed files with 256 additions and 35 deletions

View File

@ -13,3 +13,8 @@ sailfish-macros = { path = "../sailfish-macros" }
name = "simple"
path = "simple.rs"
test = false
[[bin]]
name = "include"
path = "include.rs"
test = false

19
examples/include.rs Normal file
View File

@ -0,0 +1,19 @@
#[macro_use]
extern crate sailfish_macros;
use sailfish::TemplateOnce;
#[derive(TemplateOnce)]
#[template(path = "include.stpl")]
struct Include {
title: String,
name: String
}
fn main() {
let ctx = Include {
title: "Website".to_owned(),
name: "Hanako".to_owned()
};
println!("{}", ctx.render_once().unwrap());
}

View File

@ -0,0 +1,5 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="format-detection" content="telephone=no">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<title><%= title %></title>

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<% include!("header.stpl"); %>
</head>
<body>
<h1><%= title %></h1>
Hello, <%= name %>!
</body>
</html>

View File

@ -0,0 +1,7 @@
1
2
INCLUDED: foo
3
4

View File

@ -0,0 +1,3 @@
1
<% include!("/includes/include-parent.stpl"); %>
4

View File

@ -0,0 +1,7 @@
INCLUDED: foo
INCLUDED: bar

View File

@ -0,0 +1,3 @@
<% for s in strs { %>
<% include!("included.stpl"); %>
<% } %>

View File

@ -0,0 +1 @@
INCLUDED: <%= s %>

View File

@ -0,0 +1,3 @@
2
<% include!("../included.stpl"); %>
3

View File

@ -73,3 +73,25 @@ struct CustomDelimiter;
fn custom_delimiter() {
assert_render("custom_delimiter", CustomDelimiter);
}
#[derive(TemplateOnce)]
#[template(path = "include.stpl")]
struct Include<'a> {
strs: &'a [&'a str],
}
#[test]
fn test_include() {
assert_render("include", Include { strs: &["foo", "bar"] });
}
#[derive(TemplateOnce)]
#[template(path = "include-nest.stpl")]
struct IncludeNest<'a> {
s: &'a str,
}
#[test]
fn test_include_nest() {
assert_render("include-nest", IncludeNest { s: "foo" });
}

View File

@ -27,7 +27,7 @@ quote = { version = "1.0.6", default-features = false }
[dependencies.syn]
version = "1.0.21"
default-features = false
features = ["parsing", "full", "visit-mut", "printing", "clone-impls"]
features = ["parsing", "full", "visit-mut", "printing", "clone-impls", "extra-traits"]
[dependencies.proc-macro2]
version = "1.0.10"

View File

@ -1,13 +1,14 @@
use quote::ToTokens;
use std::fs;
use std::path::Path;
use std::sync::Arc;
use crate::config::Config;
use crate::error::*;
use crate::optimizer::Optimizer;
use crate::parser::Parser;
use crate::resolver::Resolver;
use crate::translator::Translator;
use crate::translator::{Translator, TranslatedSource};
use crate::util::rustfmt_block;
#[derive(Default)]
@ -24,23 +25,32 @@ impl Compiler {
Self { config }
}
pub fn compile_file(&self, input: &Path, output: &Path) -> Result<(), Error> {
// TODO: introduce cache system
fn translate_file_contents(&self, input: &Path) -> Result<TranslatedSource, Error> {
let parser = Parser::new().delimiter(self.config.delimiter);
let translator = Translator::new().escape(self.config.escape);
let resolver = Resolver::new();
let content = fs::read_to_string(input)
.chain_err(|| format!("Failed to open template file: {:?}", input))?;
let stream = parser.parse(&*content);
translator.translate(stream)
}
pub fn compile_file(&self, template_dir: &Path, input: &Path, output: &Path) -> Result<(), Error> {
// TODO: introduce cache system
let input = input.canonicalize()?;
let include_handler = Arc::new(|child_file: &Path| -> Result<_, Error> {
Ok(self.translate_file_contents(&*child_file)?.ast)
});
let resolver = Resolver::new().include_handler(include_handler);
let optimizer = Optimizer::new();
let compile_file = |input: &Path, output: &Path| -> Result<(), Error> {
let content = fs::read_to_string(&*input)
.chain_err(|| format!("Failed to open template file: {:?}", input))?;
let mut tsource = self.translate_file_contents(input)?;
let stream = parser.parse(&*content);
let mut tsource = translator.translate(stream)?;
drop(content);
resolver.resolve(&mut tsource.ast)?;
resolver.resolve(template_dir, &*input, &mut tsource.ast)?;
optimizer.optimize(&mut tsource.ast);
if let Some(parent) = output.parent() {

View File

@ -113,6 +113,7 @@ struct TemplateStruct {
}
fn compile(
template_dir: &Path,
input_file: &Path,
output_file: &Path,
options: &DeriveTemplateOptions,
@ -126,7 +127,7 @@ fn compile(
}
let compiler = Compiler::with_config(config);
compiler.compile_file(input_file, &*output_file)
compiler.compile_file(template_dir, input_file, &*output_file)
}
fn derive_template_impl(tokens: TokenStream) -> Result<TokenStream, syn::Error> {
@ -138,15 +139,13 @@ fn derive_template_impl(tokens: TokenStream) -> Result<TokenStream, syn::Error>
all_options.merge(opt)?;
}
let mut template_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").expect(
"Internal error: environmental variable `CARGO_MANIFEST_DIR` is not set.",
));
template_dir.push("templates");
let input_file = match all_options.path {
Some(ref path) => {
let mut input = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").expect(
"Internal error: environmental variable `CARGO_MANIFEST_DIR` is not set.",
));
input.push("templates");
input.push(path.value());
input
}
Some(ref path) => template_dir.join(path.value()),
None => {
return Err(syn::Error::new(
Span::call_site(),
@ -178,7 +177,7 @@ fn derive_template_impl(tokens: TokenStream) -> Result<TokenStream, syn::Error>
output_file.push("templates");
output_file.push(filename);
compile(&*input_file, &*output_file, &all_options)
compile(&*template_dir, &*input_file, &*output_file, &all_options)
.map_err(|e| syn::Error::new(Span::call_site(), e))?;
let input_file_string = input_file.to_string_lossy();

View File

@ -1,19 +1,146 @@
use syn::Block;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use syn::visit_mut::VisitMut;
use syn::{Block, Expr, ExprBlock, LitStr};
use crate::error::*;
#[derive(Clone, Debug, Default)]
pub struct Resolver {}
macro_rules! matches_or_else {
($val:expr, $p:pat, $ok:expr, $else:expr) => {
match $val {
$p => $ok,
_ => $else,
}
};
}
impl Resolver {
#[inline]
pub fn new() -> Self {
Self {}
}
macro_rules! return_if_some {
($val:expr) => {
if $val.is_some() {
return;
}
};
}
#[inline]
pub fn resolve(&self, _ast: &mut Block) -> Result<(), Error> {
// not implemented yet
Ok(())
fn empty_block() -> Block {
Block {
brace_token: Default::default(),
stmts: Vec::new(),
}
}
struct ResolverImpl<'s, 'h> {
template_dir: &'s Path,
path_stack: Vec<PathBuf>,
deps: Vec<String>,
error: Option<Error>,
include_handler: Arc<dyn 'h + Fn(&Path) -> Result<Block, Error>>,
}
impl<'s, 'h> VisitMut for ResolverImpl<'s, 'h> {
fn visit_expr_mut(&mut self, i: &mut Expr) {
return_if_some!(self.error);
let em = matches_or_else!(*i, Expr::Macro(ref em), em, {
syn::visit_mut::visit_expr_mut(self, i);
return;
});
// check if path is `include`
if !em.mac.path.is_ident("include") {
syn::visit_mut::visit_expr_mut(self, i);
return;
}
let arg = match syn::parse2::<LitStr>(em.mac.tokens.clone()) {
Ok(l) => l.value(),
Err(e) => {
let mut e = Error::from(e);
e.chains.push(ErrorKind::AnalyzeError(
"invalid arguments for `include` macro".to_owned(),
));
self.error = Some(e);
return;
}
};
// ignore include! for rust file
if arg.ends_with(".rs") {
return;
}
// resolve the template file path
let input_file = if arg.starts_with('/') {
// absolute imclude
self.template_dir.join(&arg[1..])
} else {
// relative include
self.path_stack.last().unwrap().parent().unwrap().join(arg.clone())
};
// parse and translate the child template
let mut blk = match (*self.include_handler)(&*input_file) {
Ok(blk) => blk,
Err(mut e) => {
e.chains
.push(ErrorKind::Other(format!("Failed to include {}", arg)));
self.error = Some(e);
return;
}
};
self.path_stack.push(input_file);
self.deps.push(arg);
syn::visit_mut::visit_block_mut(self, &mut blk);
*i = Expr::Block(ExprBlock {
attrs: Vec::new(),
label: None,
block: blk,
});
}
}
#[derive(Clone)]
pub struct Resolver<'h> {
include_handler: Arc<dyn 'h + Fn(&Path) -> Result<Block, Error>>,
}
impl<'h> Resolver<'h> {
pub fn new() -> Self {
Self {
include_handler: Arc::new(|_| {
Err(make_error!(ErrorKind::AnalyzeError(
"You cannot use `include` macro inside templates".to_owned()
)))
}),
}
}
#[inline]
pub fn include_handler(
self,
new: Arc<dyn 'h + Fn(&Path) -> Result<Block, Error>>,
) -> Resolver<'h> {
Self {
include_handler: new,
}
}
#[inline]
pub fn resolve(&self, template_dir: &Path, input_file: &Path, ast: &mut Block) -> Result<(), Error> {
let mut child = ResolverImpl {
template_dir: template_dir,
path_stack: vec![input_file.to_owned()],
deps: Vec::new(),
error: None,
include_handler: Arc::clone(&self.include_handler)
};
child.visit_block_mut(ast);
if let Some(e) = child.error {
Err(e)
} else {
Ok(())
}
}
}