Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev mode and template sources #395

Merged
merged 19 commits into from Dec 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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