Skip to content

Commit

Permalink
Merge pull request #395 from sunng87/feature/template-source
Browse files Browse the repository at this point in the history
Dev mode and template sources
  • Loading branch information
sunng87 committed Dec 11, 2020
2 parents 0a0391f + 6bf6607 commit 996ada2
Show file tree
Hide file tree
Showing 17 changed files with 436 additions and 312 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Expand Up @@ -2,9 +2,16 @@

## [Unreleased](https://github.com/sunng87/handlebars-rust/compare/3.4.0...Unreleased) - ReleaseDate

* [Added] `dev_mode` for registry: templates and scripts loaded from file are always
reloaded when dev mode enabled [#395]
* [Added] Registry is now `Clone` [#395]
* [Changed] `#each` helper now renders else block for non-iterable data [#380]
* [Changed] `TemplateError` and `ScriptError` is now a cause of `RenderError` [#395]
* [Fixed] reference starts with `null`, `true` and `false` were parsed incorrectly [#382]
* [Fixed] dir source path separator bug on windows [#389]
* [Removed] option to disable source map is removed [#395]
* [Removed] `TemplateFileError` and `TemplateRenderError` are removed and merged into
`TemplateError` and `RenderError` [#395]

## [3.4.0](https://github.com/sunng87/handlebars-rust/compare/3.3.0...3.4.0) - 2020-08-14

Expand Down
4 changes: 3 additions & 1 deletion Cargo.toml
Expand Up @@ -28,14 +28,16 @@ pest_derive = "2.1.0"
serde = "1.0.0"
serde_json = "1.0.39"
walkdir = { version = "2.2.3", optional = true }
rhai = { version = "0.19.4", optional = true, features = ["sync", "serde"] }
rhai = { version = "0.19.6", optional = true, features = ["sync", "serde"] }

[dev-dependencies]
env_logger = "0.7.1"
maplit = "1.0.0"
serde_derive = "1.0.75"
tempfile = "3.0.0"
criterion = "0.3"
warp = "0.2"
tokio = { version = "0.2", features = ["macros"] }

[target.'cfg(unix)'.dev-dependencies]
pprof = { version = "0.3.13", features = ["flamegraph", "protobuf"] }
Expand Down
8 changes: 8 additions & 0 deletions README.md
Expand Up @@ -66,6 +66,9 @@ Examples are provided in source tree to demo usage of various api.
just like using javascript for handlebarsjs
* [error](https://github.com/sunng87/handlebars-rust/blob/master/examples/error.rs)
simple case for error
* [dev_mode](https://github.com/sunng87/handlebars-rust/blob/master/examples/dev_mode.rs)
a web server hosts handlebars in `dev_mode`, you can edit the template and see the change
without restarting your server.

## Minimum Rust Version Policy

Expand Down Expand Up @@ -163,6 +166,11 @@ embed your page into this parent.
You can find a real example of template inheritance in
`examples/partials.rs` and templates used by this file.

#### Auto-reload in dev mode

By turning on `dev_mode`, handlebars auto reloads any template and scripts that
loaded from files or directory. This can be handy for template development.

#### WebAssembly compatible

Handlebars 3.0 can be used in WebAssembly projects.
Expand Down
27 changes: 27 additions & 0 deletions examples/dev_mode.rs
@@ -0,0 +1,27 @@
use std::sync::Arc;

use handlebars::Handlebars;
use serde_json::json;
use warp::{self, Filter};

#[tokio::main]
async fn main() {
let mut reg = Handlebars::new();
// enable dev mode for template reloading
reg.set_dev_mode(true);
// register a template from the file
// modified the file after the server starts to see things changing
reg.register_template_file("tpl", "./examples/dev_mode/template.hbs")
.unwrap();

let hbs = Arc::new(reg);
let route = warp::get().map(move || {
let result = hbs
.render("tpl", &json!({"model": "t14s", "brand": "Thinkpad"}))
.unwrap_or_else(|e| e.to_string());
warp::reply::html(result)
});

println!("Edit ./examples/dev_mode/template.hbs and request http://localhost:3030 to see the change on the run.");
warp::serve(route).run(([127, 0, 0, 1], 3030)).await;
}
8 changes: 8 additions & 0 deletions examples/dev_mode/template.hbs
@@ -0,0 +1,8 @@
<html>
<head>
<title>My Laptop</title>
</head>
<body>
<p>My current laptop is {{brand}}: <b>{{model}}</b></p>
</body>
</html>
7 changes: 5 additions & 2 deletions examples/render_file.rs
Expand Up @@ -129,9 +129,12 @@ fn main() -> Result<(), Box<dyn Error>> {

let data = make_data();

let mut source_template = File::open(&"./examples/render_file/template.hbs")?;
handlebars
.register_template_file("template", "./examples/render_file/template.hbs")
.unwrap();

let mut output_file = File::create("target/table.html")?;
handlebars.render_template_source_to_write(&mut source_template, &data, &mut output_file)?;
handlebars.render_to_write("template", &data, &mut output_file)?;
println!("target/table.html generated");
Ok(())
}
2 changes: 1 addition & 1 deletion examples/script/goals.rhai
@@ -1,3 +1,3 @@
let goals = params[0];

len(goals)
goals.len()
18 changes: 9 additions & 9 deletions src/context.rs
Expand Up @@ -36,7 +36,7 @@ fn parse_json_visitor<'a, 'reg>(
relative_path: &[PathSeg],
block_contexts: &'a VecDeque<BlockContext<'reg>>,
always_for_absolute_path: bool,
) -> Result<ResolvedPath<'a>, RenderError> {
) -> ResolvedPath<'a> {
let mut path_context_depth: i64 = 0;
let mut with_block_param = None;
let mut from_root = false;
Expand Down Expand Up @@ -65,7 +65,7 @@ fn parse_json_visitor<'a, 'reg>(
match with_block_param {
Some((BlockParamHolder::Value(ref value), _)) => {
merge_json_path(&mut path_stack, &relative_path[1..]);
Ok(ResolvedPath::BlockParamValue(path_stack, value))
ResolvedPath::BlockParamValue(path_stack, value)
}
Some((BlockParamHolder::Path(ref paths), base_path)) => {
extend(&mut path_stack, base_path);
Expand All @@ -74,7 +74,7 @@ fn parse_json_visitor<'a, 'reg>(
}
merge_json_path(&mut path_stack, &relative_path[1..]);

Ok(ResolvedPath::AbsolutePath(path_stack))
ResolvedPath::AbsolutePath(path_stack)
}
None => {
if path_context_depth > 0 {
Expand All @@ -90,24 +90,24 @@ fn parse_json_visitor<'a, 'reg>(
}
}
merge_json_path(&mut path_stack, relative_path);
Ok(ResolvedPath::AbsolutePath(path_stack))
ResolvedPath::AbsolutePath(path_stack)
} else if from_root {
merge_json_path(&mut path_stack, relative_path);
Ok(ResolvedPath::AbsolutePath(path_stack))
ResolvedPath::AbsolutePath(path_stack)
} else if always_for_absolute_path {
if let Some(base_value) = block_contexts.front().and_then(|blk| blk.base_value()) {
merge_json_path(&mut path_stack, relative_path);
Ok(ResolvedPath::LocalValue(path_stack, base_value))
ResolvedPath::LocalValue(path_stack, base_value)
} else {
if let Some(base_path) = block_contexts.front().map(|blk| blk.base_path()) {
extend(&mut path_stack, base_path);
}
merge_json_path(&mut path_stack, relative_path);
Ok(ResolvedPath::AbsolutePath(path_stack))
ResolvedPath::AbsolutePath(path_stack)
}
} else {
merge_json_path(&mut path_stack, relative_path);
Ok(ResolvedPath::RelativePath(path_stack))
ResolvedPath::RelativePath(path_stack)
}
}
}
Expand Down Expand Up @@ -170,7 +170,7 @@ impl Context {
block_contexts: &VecDeque<BlockContext<'reg>>,
) -> Result<ScopedJson<'reg, 'rc>, RenderError> {
// always use absolute at the moment until we get base_value lifetime issue fixed
let resolved_visitor = parse_json_visitor(&relative_path, block_contexts, true)?;
let resolved_visitor = parse_json_visitor(&relative_path, block_contexts, true);

// debug logging
debug!("Accessing context value: {:?}", resolved_visitor);
Expand Down
107 changes: 36 additions & 71 deletions src/error.rs
Expand Up @@ -69,13 +69,26 @@ impl From<ParseIntError> for RenderError {
}
}

impl From<TemplateError> for RenderError {
fn from(e: TemplateError) -> RenderError {
RenderError::from_error("Error with parsing template.", e)
}
}

#[cfg(feature = "script_helper")]
impl From<Box<EvalAltResult>> for RenderError {
fn from(e: Box<EvalAltResult>) -> RenderError {
RenderError::from_error("Error on converting data to Rhai dynamic.", e)
}
}

#[cfg(feature = "script_helper")]
impl From<ScriptError> for RenderError {
fn from(e: ScriptError) -> RenderError {
RenderError::from_error("Error loading rhai script.", e)
}
}

impl RenderError {
pub fn new<T: AsRef<str>>(desc: T) -> RenderError {
RenderError {
Expand All @@ -95,17 +108,6 @@ impl RenderError {
RenderError::new(&msg)
}

#[deprecated]
pub fn with<E>(cause: E) -> RenderError
where
E: Error + Send + Sync + 'static,
{
let mut e = RenderError::new(cause.to_string());
e.cause = Some(Box::new(cause));

e
}

pub fn from_error<E>(error_kind: &str, cause: E) -> RenderError
where
E: Error + Send + Sync + 'static,
Expand All @@ -119,7 +121,7 @@ impl RenderError {

quick_error! {
/// Template parsing error
#[derive(PartialEq, Debug, Clone)]
#[derive(Debug)]
pub enum TemplateErrorReason {
MismatchingClosedHelper(open: String, closed: String) {
display("helper {:?} was opened, but {:?} is closing",
Expand All @@ -138,11 +140,18 @@ quick_error! {
NestedSubexpression {
display("nested subexpression is not supported")
}
IoError(err: IOError, name: String) {
display("Template \"{}\": {}", name, err)
}
#[cfg(feature = "dir_source")]
WalkdirError(err: WalkdirError) {
display("Walk dir error: {}", err)
}
}
}

/// Error on parsing template.
#[derive(Debug, PartialEq)]
#[derive(Debug)]
pub struct TemplateError {
pub reason: TemplateErrorReason,
pub template_name: Option<String>,
Expand Down Expand Up @@ -177,6 +186,20 @@ impl TemplateError {

impl Error for TemplateError {}

impl From<(IOError, String)> for TemplateError {
fn from(err_info: (IOError, String)) -> TemplateError {
let (e, name) = err_info;
TemplateError::of(TemplateErrorReason::IoError(e, name))
}
}

#[cfg(feature = "dir_source")]
impl From<WalkdirError> for TemplateError {
fn from(e: WalkdirError) -> TemplateError {
TemplateError::of(TemplateErrorReason::WalkdirError(e))
}
}

fn template_segment(template_str: &str, line: usize, col: usize) -> String {
let range = 3;
let line_start = if line >= range { line - range } else { 0 };
Expand Down Expand Up @@ -223,64 +246,6 @@ impl fmt::Display for TemplateError {
}
}

quick_error! {
/// A combined error type for `TemplateError` and `IOError`
#[derive(Debug)]
pub enum TemplateFileError {
TemplateError(err: TemplateError) {
from()
source(err)
display("{}", err)
}
IOError(err: IOError, name: String) {
source(err)
display("Template \"{}\": {}", name, err)
}
}
}

#[cfg(feature = "dir_source")]
impl From<WalkdirError> for TemplateFileError {
fn from(error: WalkdirError) -> TemplateFileError {
let path_string: String = error
.path()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_default();
TemplateFileError::IOError(IOError::from(error), path_string)
}
}

quick_error! {
/// A combined error type for `TemplateError`, `IOError` and `RenderError`
#[derive(Debug)]
pub enum TemplateRenderError {
TemplateError(err: TemplateError) {
from()
source(err)
display("{}", err)
}
RenderError(err: RenderError) {
from()
source(err)
display("{}", err)
}
IOError(err: IOError, name: String) {
source(err)
display("Template \"{}\": {}", name, err)
}
}
}

impl TemplateRenderError {
pub fn as_render_error(&self) -> Option<&RenderError> {
if let TemplateRenderError::RenderError(ref e) = *self {
Some(&e)
} else {
None
}
}
}

#[cfg(feature = "script_helper")]
quick_error! {
#[derive(Debug)]
Expand Down
5 changes: 2 additions & 3 deletions src/helpers/block_util.rs
@@ -1,10 +1,9 @@
use crate::block::BlockContext;
use crate::error::RenderError;
use crate::json::value::PathAndJson;

pub(crate) fn create_block<'reg: 'rc, 'rc>(
param: &'rc PathAndJson<'reg, 'rc>,
) -> Result<BlockContext<'reg>, RenderError> {
) -> BlockContext<'reg> {
let mut block = BlockContext::new();

if let Some(new_path) = param.context_path() {
Expand All @@ -14,5 +13,5 @@ pub(crate) fn create_block<'reg: 'rc, 'rc>(
block.set_base_value(param.value().clone());
}

Ok(block)
block
}
4 changes: 2 additions & 2 deletions src/helpers/helper_each.rs
Expand Up @@ -81,7 +81,7 @@ impl HelperDef for EachHelper {
match template {
Some(t) => match *value.value() {
Json::Array(ref list) if !list.is_empty() => {
let block_context = create_block(&value)?;
let block_context = create_block(&value);
rc.push_block(block_context);

let len = list.len();
Expand Down Expand Up @@ -109,7 +109,7 @@ impl HelperDef for EachHelper {
Ok(())
}
Json::Object(ref obj) if !obj.is_empty() => {
let block_context = create_block(&value)?;
let block_context = create_block(&value);
rc.push_block(block_context);

let mut is_first = true;
Expand Down
2 changes: 1 addition & 1 deletion src/helpers/helper_with.rs
Expand Up @@ -25,7 +25,7 @@ impl HelperDef for WithHelper {
.ok_or_else(|| RenderError::new("Param not found for helper \"with\""))?;

if param.value().is_truthy(false) {
let mut block = create_block(&param)?;
let mut block = create_block(&param);

if let Some(block_param) = h.block_param() {
let mut params = BlockParams::new();
Expand Down

0 comments on commit 996ada2

Please sign in to comment.