Giter VIP home page Giter VIP logo

liquid-rust's Introduction

liquid-rust

Liquid templating for Rust

Crates Status

Goals:

  1. Conformant. Incompatibilities with strict shopify/liquid are bugs to be fixed.
  2. Flexible. Liquid embraces variants for different domains and we want to follow in that spirit.
  3. Performant. Do the best we can within what is conformant.

Example applications using liquid-rust:

Usage

To include liquid in your project add the following to your Cargo.toml:

$ cargo add liquid

Example:

let template = liquid::ParserBuilder::with_stdlib()
    .build().unwrap()
    .parse("Liquid! {{num | minus: 2}}").unwrap();

let mut globals = liquid::object!({
    "num": 4f64
});

let output = template.render(&globals).unwrap();
assert_eq!(output, "Liquid! 2".to_string());

You can find a reference on Liquid syntax here.

Customizing Liquid

Language Variants

By default, liquid-rust has no filters, tags, or blocks. You can enable the default set or pick and choose which to add to suite your application.

Create your own filters

Creating your own filters is very easy. Filters are simply functions or closures that take an input Value and a Vec<Value> of optional arguments and return a Value to be rendered or consumed by chained filters.

See filters/ for what a filter implementation looks like. You can then register it by calling liquid::ParserBuilder::filter.

Create your own tags

Tags are made up of two parts, the initialization and the rendering.

Initialization happens when the parser hits a Liquid tag that has your designated name. You will have to specify a function or closure that will then return a Renderable object to do the rendering.

See include_tag.rs for what a tag implementation looks like. You can then register it by calling liquid::ParserBuilder::tag.

Create your own tag blocks

Blocks work very similar to Tags. The only difference is that blocks contain other markup, which is why block initialization functions take another argument, a list of Elements that are inside the specified block.

See comment_block.rs for what a block implementation looks like. You can then register it by calling liquid::ParserBuilder::block.

liquid-rust's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

liquid-rust's Issues

File cache support at compile time

With the include tag and the upcoming liquid-reuse, it would be great to have access to some sort of file cache inside Liquid during compile time. I hope we can do this in a way that gives plugin authors flexibility while not adding overhead to "simple" users of the API.

Extending a layout has incorrect indentation

Originally cobalt-org/cobalt.rs#141 and reported by @ebkalderon

When extending from a layout template, the resulting HTML has inconsistent indentation. Imagine you have an example layout file default.liquid which looks like this:

<!doctype html>
<html>
    <head> ... </head>
    <body>
        {% if is_post %}
            {% include '_layouts/post.liquid' %}
        {% else %}
            {{ content }}
        {% endif %}
    </body>
</html>

and also a page which extends the layout:

extends: default.liquid

---
<section id="hero">
    <!-- blah blah -->
</section>

One would expect the resulting HTML generated by Cobalt to look like:

<!doctype html>
<html>
    <head> ... </head>
    <body>
        <section id="hero">
            <!-- blah blah -->
        </section>
    </body>
</html>

But instead, Cobalt generates this:

<!doctype html>
<html>
    <head> ... </head>
    <body>

<section id="hero">
    <!-- blah blah -->
</section>

    </body>
</html>

Not only is the page content pasted directly into the layout it extends from without any indentation whatsoever, there are also extra newlines around where the Liquid markup used to be. This doesn't break the functionality of the page in any way, but it still looks pretty ugly.

Relicense under dual MIT/Apache-2.0

This issue was automatically generated. Feel free to close without ceremony if
you do not agree with re-licensing or if it is not possible for other reasons.
Respond to @cmr with any questions or concerns, or pop over to
#rust-offtopic on IRC to discuss.

You're receiving this because someone (perhaps the project maintainer)
published a crates.io package with the license as "MIT" xor "Apache-2.0" and
the repository field pointing here.

TL;DR the Rust ecosystem is largely Apache-2.0. Being available under that
license is good for interoperation. The MIT license as an add-on can be nice
for GPLv2 projects to use your code.

Why?

The MIT license requires reproducing countless copies of the same copyright
header with different names in the copyright field, for every MIT library in
use. The Apache license does not have this drawback. However, this is not the
primary motivation for me creating these issues. The Apache license also has
protections from patent trolls and an explicit contribution licensing clause.
However, the Apache license is incompatible with GPLv2. This is why Rust is
dual-licensed as MIT/Apache (the "primary" license being Apache, MIT only for
GPLv2 compat), and doing so would be wise for this project. This also makes
this crate suitable for inclusion and unrestricted sharing in the Rust
standard distribution and other projects using dual MIT/Apache, such as my
personal ulterior motive, the Robigalia project.

Some ask, "Does this really apply to binary redistributions? Does MIT really
require reproducing the whole thing?" I'm not a lawyer, and I can't give legal
advice, but some Google Android apps include open source attributions using
this interpretation. Others also agree with
it
.
But, again, the copyright notice redistribution is not the primary motivation
for the dual-licensing. It's stronger protections to licensees and better
interoperation with the wider Rust ecosystem.

How?

To do this, get explicit approval from each contributor of copyrightable work
(as not all contributions qualify for copyright) and then add the following to
your README:

## License

Licensed under either of
 * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
 * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.

### Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any
additional terms or conditions.

and in your license headers, use the following boilerplate (based on that used in Rust):

// Copyright (c) 2016 liquid-rust developers
//
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. All files in the project carrying such notice may not be copied,
// modified, or distributed except according to those terms.

Be sure to add the relevant LICENSE-{MIT,APACHE} files. You can copy these
from the Rust repo for a plain-text
version.

And don't forget to update the license metadata in your Cargo.toml to:

license = "MIT/Apache-2.0"

I'll be going through projects which agree to be relicensed and have approval
by the necessary contributors and doing this changes, so feel free to leave
the heavy lifting to me!

Contributor checkoff

To agree to relicensing, comment with :

I license past and future contributions under the dual MIT/Apache-2.0 license, allowing licensees to chose either at their option

Or, if you're a contributor, you can check the box in this repo next to your
name. My scripts will pick this exact phrase up and check your checkbox, but
I'll come through and manually review this issue later as well.

Include tag proper template names

In Shopify/liquid the syntax for the include tag is different from my implementation.

  # Simply include another template:
  #
  #   {% include 'product' %}
  #
  # Include a template with a local variable:
  #
  #   {% include 'product' with products[0] %}
  #
  • With String literal
  • With variable

We should implement the Include tag similar. This issue is to keep track.

API Documentation

This is tracking the progress with thoroughly documenting the complete API so that we may add the #![deny(missing_docs)] label.

  • Liquid module
  • lexer
  • parser
  • Context
  • LiquidOptions
  • Template
  • Error
  • FilterError
  • Token
  • Value
  • Renderable
  • parse
  • Block
  • Tag

Assign does not support piped expressions

So I started working on an implmentation of split here.

The unit test passes just fine, but the resulting Array causes a problem downstream as you can see from the failed test:

---- split stdout ----
    thread 'split' panicked at 'called `Result::unwrap()` on an `Err` value: Lexer("\' is not a valid identifier")', src/libcore/result.rs:746
stack backtrace:
   1:        0x10bd959f8 - std::sys::backtrace::tracing::imp::write::hf68f1a220b61702c
   2:        0x10bd97c95 - std::panicking::default_hook::_$u7b$$u7b$closure$u7d$$u7d$::hb638acea7c29901b
   3:        0x10bd9781f - std::panicking::default_hook::h508c3dab3df347d6
   4:        0x10bd8c4d6 - std::sys_common::unwind::begin_unwind_inner::h17f9e42de6d55309
   5:        0x10bd8d28e - std::sys_common::unwind::begin_unwind_fmt::h039d18bd8498e1d0
   6:        0x10bd94cc7 - rust_begin_unwind
   7:        0x10bdbe510 - core::panicking::panic_fmt::h813eaa27a5810609
   8:        0x10bbc1ee8 - core::result::unwrap_failed::he9d1212db5110052
   9:        0x10bbc1828 - _<std..result..Result<T, E>>::unwrap::h7493e55a0acc9844
  10:        0x10bbcafcb - filters::split::h885acea6ebf1f10e
  11:        0x10bbe82cb - _<F as std..boxed..FnBox<A>>::call_box::h48dd10267ee7767d
  12:        0x10bbea79d - std::sys_common::unwind::try::try_fn::hc72e80a3e6bb322c
  13:        0x10bd94c5b - __rust_try
  14:        0x10bd94be3 - std::sys_common::unwind::inner_try::h74a189ca1fbe8e07
  15:        0x10bbeab84 - _<F as std..boxed..FnBox<A>>::call_box::h8f9b07ad31279db5
  16:        0x10bd96ee8 - std::sys::thread::Thread::new::thread_start::h40a33956f4cb7596
  17:     0x7fff9be2899c - _pthread_body
  18:     0x7fff9be28919 - _pthread_start

Since there are no other implementations of filters that return Value::Array, this may have just been overlooked in the filter scaffolding... Need some help here. Thanks!

Chaining filters ignores intermediate results

There appears to be an issue if you chain more than one filter together as shown in this sample program

extern crate liquid;
use liquid::{Renderable, Context, Value};

fn main() {
    println!("Run cargo test instead!");
}

#[test]
fn test_uppercase() {
    let template = liquid::parse("{{ s | upcase }}", Default::default()).unwrap();

    let mut context = Context::new();
    context.set_val("s", Value::Str(String::from("foofoo")));

    let output = template.render(&mut context);
    assert_eq!(output.unwrap(), Some("FOOFOO".to_string()));
}
#[test]
fn test_replace() {
    let template = liquid::parse("{{ s | replace:'foo','bar' }}", Default::default()).unwrap();

    let mut context = Context::new();
    context.set_val("s", Value::Str(String::from("foofoo")));

    let output = template.render(&mut context);
    assert_eq!(output.unwrap(), Some("barbar".to_string()));
}

#[test]
fn test_both() {
    let template = liquid::parse("{{ s | replace:'foo','bar' | upcase }}", Default::default()).unwrap();

    let mut context = Context::new();
    context.set_val("s", Value::Str(String::from("foofoo")));

    let output = template.render(&mut context);
    assert_eq!(output.unwrap(), Some("BARBAR".to_string()));
}

Here is the result:

% cargo test
     Running target/debug/loops-43680f964937768f

running 3 tests
test test_uppercase ... ok
test test_replace ... ok
test test_both ... FAILED

failures:

---- test_both stdout ----
thread 'test_both' panicked at 'assertion failed: `(left == right)` (left: `Some("FOOFOO")`, right: `Some("BARBAR")`)', src/main.rs:37
note: Run with `RUST_BACKTRACE=1` for a backtrace.

failures:
    test_both

test result: FAILED. 2 passed; 1 failed; 0 ignored; 0 measured

The intermediate filter result isn't passed as input to the next filter in the chain. Should have gotten BARBAR but instead for FOOFOO. It seems like the replace filter wasn't called, but I added some debugging println! and is IS being called, but its result isn't input to the next block in the chain.

Quick scan of the code seems that the issue is around in this area

Implement missing tags

https://github.com/Shopify/liquid/wiki/Liquid-for-Designers#tags

  • assign - Assigns some value to a variable
  • capture - Block tag that captures text into a variable
  • case - Block tag, its the standard case...when block
  • comment - Block tag, comments out the text in the block
  • cycle - Cycle is usually used within a loop to alternate between values, like colors or DOM classes.
  • for - For loop
  • break - Exits a for loop
  • continue Skips the remaining code in the current for loop and continues with the next loop
  • if - Standard if/else block
  • include - Includes another template; useful for partials
  • raw - temporarily disable tag processing to avoid syntax conflicts.
  • unless - Mirror of if statement

Escape filter results by default

So this is a pretty large change I think, but as far as I understand the original Liquid HTML escapes everything that goes into filters by default, or something. Someone would have to check what exactly gets escaped in Liquid and do the same escaping in liquid-rust. It just states

It needs to be non evaling and secure. Liquid templates are made so that users can edit them. You don't want to run code on your server which your users wrote.

which is pretty vague.

Could use https://github.com/skade/escapade

Display location information for errors

It would be much nicer if instead of [error] Liquid error: Rendering error: Tried to iterate over None, which is not supported. or something similarly unhelpful, cobalt would directly point to the problem.

This is for the liquid side of the work for cobalt-org/cobalt.rs#136

Implement missing filters

Feel free to grab any of those and implement! Creating filters is really straightforward, you can find more info in the README and look at the existing filters

  • append - append a string e.g. {{ 'foo' | append:'bar' }} #=> 'foobar'
  • capitalize - capitalize words in the input sentence
  • ceil -
  • date - reformat a date (syntax reference)
  • divided_by - integer division e.g. {{ 10 | divided_by:3 }} #=> 3
  • downcase - convert an input string to lowercase
  • escape - escape a string
  • escape_once - returns an escaped version of html without affecting existing escaped entities
  • first - get the first element of the passed in array
  • floor -
  • join - join elements of the array with certain character between them
  • last - get the last element of the passed in array
  • map - map/collect an array on a given property
  • minus - subtraction e.g. {{ 4 | minus:2 }} #=> 2
  • modulo - remainder, e.g. {{ 3 | modulo:2 }} #=> 1
  • newline_to_br - replace each newline (\n) with html break
  • pluralize - return the second word if the input is not 1, otherwise return the first word e.g. {{ 3 | pluralize: 'item', 'items' }} #=> 'items'
  • plus - addition e.g. {{ '1' | plus:'1' }} #=> 2, {{ 1 | plus:1 }} #=> 2
  • prepend - prepend a string e.g. {{ 'bar' | prepend:'foo' }} #=> 'foobar'
  • remove_first - remove the first occurrence e.g. {{ 'barbar' | remove_first:'bar' }} #=> 'bar'
  • remove - remove each occurrence e.g. {{ 'foobarfoobar' | remove:'foo' }} #=> 'barbar'
  • replace_first - replace the first occurrence e.g. {{ 'barbar' | replace_first:'bar','foo' }} #=> 'foobar'
  • replace - replace each occurrence e.g. {{ 'foofoo' | replace:'foo','bar' }} #=> 'barbar'
  • reverse - reverse sort the passed in array
  • round - rounds input to the nearest integer or specified number of decimals
  • size - return the size of an array or string
  • slice - slice a string. Takes an offset and length, e.g. {{ "hello" | slice: -3, 3 }} #=> llo
  • sort - sort elements of the array
  • split - split a string on a matching pattern e.g. {{ "ab" | split:"" }} #=> ['a','b']
  • strip_html - strip html from string
  • strip_newlines - strip all newlines (\n) from string
  • times - multiplication e.g {{ 5 | times:4 }} #=> 20
  • truncate - truncate a string down to x characters. It also accepts a second parameter that will append to the string e.g. {{ 'foobarfoobar' | truncate: 5, '.' }} #=> 'foob.'
  • truncatewords - truncate a string down to x words
  • upcase - convert an input string to uppercase
  • abs
  • compact
  • default
  • lstrip
  • rstrip
  • sort_natural
  • strip
  • uniq

Stabalize the Error type

As the guidelines point out, error types are a way to leak out unintended dependencies in a public API, possibly breaking people as you evolve it.

Types of errors

  • compiler
  • interpret
  • dependency
    • third party
    • compiler plugin error
    • interpreter plugin error
    • filter plugin error

Programmaticaly relevant information

  • high level error type
  • displayable error message
  • displayable API backtrace
  • displayable user backtrace

Include with block

@jespino suggested

Currently the layouts are on liquid have to been rewritten various times when you want to define different layouts. Could be nice to have a "extends" tag like in django templates or jinja. or something that allow to build a template from a base template.

base.liquid

<html>
  <body>
    {{include_block}}
  </body>
</html>

default.liquid

{% include "base.liquid" block_var="include_block" %}
  <h1>Posts</h1>
  <div>{{ content }}</div>
{% endinclude %}

It might be better to do something like {% include "base.liquid" with include_block %}.

Support for named arguments in filters

Depends on #301

Shopify/liquid supports named arguments for filters.

Example

{{ paginate | default_pagination: next: 'Older', previous: 'Newer' }}

Is this something liquid-rust aims to support? If yes, I'd be happy to make a PR for this; I already have part of a PR.

Alias for_loop to forloop in for loops

The liquid markup documentation at spotify indicates there is an object usable in for loops to get things like the index, test for first/last, etc. to be able to do things like this:

{% for product in collections.frontpage.products %}
    {% if forloop.first == true %}
        First time through!
    {% else %}
        Not the first time.
    {% endif %}
{% endfor %}

which results in output:

First time through!
Not the first time.
Not the first time.
Not the first time.
Not the first time.

I have a need to do something different depending on which item I'm on and there doesn't seem to be any other way to do this currently implemented.

Add benchamrking to the CI

I'm unsure what our options are but it'd at least be nice to look up the result of master builds over time or to see the impact of PRs on performance.

Parsing error

Using current cobalt (cobalt-org/cobalt.rs@1c7fb5b) I get this error:

Liquid error: Parsing error: Expected String Literal, found Some(Identifier("_layouts/head.liquid"))

head.liquid looks like this:

<head>
  <meta charset="utf-8">
  <title>{{ title }}</title>
  <meta name="viewport" content="width=device-width, initial-scale=1" />
    <!--[if lte IE 8]><script src="/assets/js/ie/html5shiv.js"></script><![endif]-->
    <link rel="stylesheet" href="/assets/css/main.css" />
    <!--[if lte IE 8]><link rel="stylesheet" href="/assets/css/ie8.css" /><![endif]-->
    <!--[if lte IE 9]><link rel="stylesheet" href="/assets/css/ie9.css" /><![endif]-->
</head>

And default.liquid like this:

<!DOCTYPE html>
<html>

  {% include _layouts/head.liquid %}

  <body>

    <div id="page-wrapper">

      {% include _layouts/header.liquid     %}

      <section class="wrapper style1">
        <div class="container">
          {% include _layouts/no_sidebar.liquid %}
        </div>
      </section>

      {% include _layouts/footer.liquid     %}

    </div>

    {% include _layouts/scripts.liquid %}

  </body>

</html>

Now where is the error?

Allow `value::Object` to hold complex objects

I think that objects held in a liquid context should be allowed to be more complex than HashMap.

Perhaps Object should be a trait that defines

trait Object { 
  fn get_val(&mut self, name: &str) {
  }
}

The reason is, some objects cannot or should not be loaded into memory if they're unused. A good example of this is Collection in Shopify. They can hold hundreds, if not thousands or millions of products. This makes lazy-loading of collections and products desirable. It also makes memoization desirable.

My Rust (or statically typed language) knowledge fails me here.

It would also be desirable to make it possible for tags to act upon the objects, which would mean that tags would need to check the type of the returned object.

An example of this is the paginate block.

The documentation for it is pretty poor, but the gist is that it bounds the collection such that for-loops will iterate over only a slice of the data.

ex: Given collection = [1, 2, 3, 4, 5, 6] and current_page = 2

{% paginate collection by 2 %}
  {% for num in collection %}
    {{ num }}
  {% endfor %}
{% endpaginate %}

The paginate block will bound collection to (2..4) (4 excluded). The result will then be

3
4

I'd be super happy to tackle this, but I'd need pointers

be multithread-friendly?

I wanted to parse many templates into Templates and store them in an Arc<Mutex<HashMap<String, Template>>> so that multiple threads could access the template to render various Contexts, to avoid re-parsing a given template each time it needs to be rendered. However, many things seem to be tangled up in local, non-'static references, which means that I can't simply specify Template<'static> or Box<Renderable + Sync + Send + 'static>. The parser in particular seems to revolve entirely around local references.

Would you guys be open to creating a template type that is "self-contained," so that it is implicitly Sync + Send + 'static, to allow us to send and share them between threads? I can do this with the handlebars crate, for example, because the Template type is like this. They also have a Registry type that makes it very easy to render a given template "by name," so I simply stick this type in an Arc<Mutex after I parse my templates and then various threads can use it to render separate documents.

The only alternative I have now, that I can think of, is to re-parse every template at the time and context that it needs to be rendered, which is a shame.

CI sporadically fails because of `clippy` build failures

Having clippy break the build is great for reviewers since we don't have to run it manually or check the logs. The problem is building clippy can sometimes fail.

I'd suggest making clippy builds not gate the CI but clippy runs would but then how do we know when we have coverage with clippy?

Filter arguments can not contain spaces and commas (and possibly some other chars)

I try to compile the following liquid template:

{{ title | replace:"hello world","hello, world" }}

I get the following error:

Liquid error: Parsing error: parse_output: Identifier("\"hello") not implemented

When I remove spaces and leave out comma:

{{ title | replace:"helloworld","hello,world" }}

I get the same error.

When I remove both spaces and comma:

{{ title | replace:"helloworld","helloWorld" }}

I get my example compiled.

It seems like filter arguments parser doesn't parse strings in quotes correctly.
I also tried to replace double quotes with single quotes to no avail.

Error using variables as filter arguments

When trying to use a variable as an argument to a filter I get an error from the parser.

Consider this example:

let input = "{{ \"test\" | prepend: myvar }}";
let parsed = match liquid::parse(input, Default::default()) {
    Ok(t) => t,
    Err(e) => { panic!("{}", e) }
};

Results in:

thread '<main>' panicked at 'Parsing error: Expected a comma or a pipe, found myvar'

RFC: API Refactor

Goals

Goals

  • Layered so people pay for what they get
  • Better separation of internals vs API
  • Do not get in the way of using liquid in parallel applications
  • Avoid copies with the context
    • cobalt has to copy in the vars initially
    • cobalt has reused vars that would be nice to have overlayed each other rather than copied.
  • Make it possible to support expressions for include, see #142

Secondary goals

  • Caching
    • Cache can be reused for cobalt's layouts/templates
      • Should this be left to clients?
    • Caching of snippets to avoid reading/parsing them
      • Is caching of top-level items good enough?

Proposal

  • ParserBuilder -> Parser (#149)
    • Takes in includes, tags, blocks, and filters
  • Parser -> Template (#149)
    • Takes in str
  • Template -> String (#149)
    • Takes in globals
  • Separate concerns (#149)
    • Top-level Parser, Template
    • Value
    • Interpreter
    • Syntax / Compile to interpreted state
  • Update interpreter / syntax to take in state by reference where applicable
  • Update interpreter / syntax to take in state by traits
    • Including allowing custom backing stores for globals
    • How well can Context be a trait when different tags (cycle, include, break, etc) need special support in it?
  • Update trait FilterValue to advertise the required/optional params
    • Must still support custom handling
    • Allow centralizing of error processing
    • Future: allow named parameters
  • Value API
    • Value should be an enum of Object, Array, Scalar, and Nil
    • Scalar is a struct wrapping an enum to hide implementation details
    • Scalar can coerce between different data types
  • Crate structure and APIs to connect them
    • liquid-value
    • liquid-interpreter
    • liquid-compiler
    • liquid
    • tags, blocks, and filters
      • Do these sit above liquid with some API (extension traits?) to make it easy to add them to a parser?
      • Or do these sit below liquid with it having built-in support for adding these?
      • Are they added by "liquid" or "not" (like they are now) or by category with features to enable extra featurs
      • Keep in mind I want to split out from cobalt liquid-codeblock and liquid-markdown
  • Should default implementations of Traits be moved out to leaf layers?
  • Explore alternatives to passing around contexts

State of template APIs

shopify/liquid

liquid-rust

  • LiquidOption owns
    • blocks
    • tags
    • file system access
  • parse takes
    • text
    • LiquidOptions
  • Template
    • render takes
      • Context
  • Context
    • filters (standard filters automatically added)
    • values
    • various internal execution context publicly available

Concerns

  • Seems like it should be restructured as builders
  • From the client perspective, it seems odd that bocks/tags are pre-parse while filters are on-render

tera

  • Tera is a template collection
    • new parses a directory (can add files to it one at a time)
    • register_filter for adding them to the template collection
    • register_tester for adding new conditional evaluations
    • render takes a template identifier and a Context

handlebars

expose Template?

Is there a reason that the Template type isn't exposed? The parse function returns a value of that type, but we can't annotate values using that type explicitly since it's not a public type. Is this an oversight, or is it deliberate?

get stable-compatible

Hi! You might be interested in going stable-compatible. This means that the project can't use any unstable language features. This is currently using:

#![feature(box_syntax)]
#![feature(unboxed_closures)]
#![feature(plugin)]
#![feature(test)]
#![feature(collections)]
#![feature(core)]

You can easily identify which parts of your code require which features by removing those attributes.

The box syntax feature is an easy one to do away with by replacing box someexpr with Box::new(someexpr).

The plugin feature is mostly required due to your use of regex_macros, a very nice package indeed but one which requires syntax extension support, which stable doesn't. This is easily remedied by changing regex!("pattern") to Regex::new("pattern") and checking for potential failure, or unwrapping if the pattern isn't dynamically generated.

The test feature is usually needed for benchmarking purposes.

The rest you can figure out by removing the given attribute and seeing what generates an error.

Common strategies for going stable include:

  • introducing cargo features, such as one called "nightly" and using the cfg(feature = "nightly") attribute to conditionally compile functionality that requires nightly features
  • ripping out implementations of nightly features from the rust std and embedding them into your code base directly

Bug: Array Indexes

liquid-rust version: 0.10.0
rust version: rustc 1.18.0 (03fc9d622 2017-06-06)
OS: macOS Sierra / 10.12.4

{% assign tags = "foo,bar" | split: "," %}
tags[0]: {{ tags[0] }}

running the code results in

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Parser("Expected |, found [")', sr
c/libcore/result.rs:859
note: Run with `RUST_BACKTRACE=1` for a backtrace.
{% for tag in tags %}
   {{ tag }}
{% endfor %}

Will build and display each tag.

I've tested using another liquid parser and this definitely a problem with liquid-rust.

Integration tests are not run

The tests in the tests folder are not run.

Because of the [[test]] in Cargo.toml cargo only run the test specified here. And this test is just an empty file. Because you want to use a non-default feature in a test, this means you cannot just remove this section.

I see multiple ways to solve this issue:

  1. Manually specify all the tests in Cargo.toml
  2. Include all other tests from liquid.rs (via mod) (this could be done automatically with code generation in build.rs)
  3. Remove the section from Cargo.toml but specify the feature on the command line each time (cargo test --features "serde")

Dot notation form of size not implemented?

liquid-rust version: 0.10.1
rust version: rustc 1.21.0 (3b72af97e 2017-10-09)
OS: Arch Linux

I'm using Cobalt (0.7.3) and it appears that the dot notation form of size doesn't work.

With the following in index.liquid and 10 posts:

{{ posts | size }}
x
{{ posts.size }}

the output is:

10
x

What I'm trying to do is show some text if there are more than 5 posts:

{% if posts.size > 5 %}
  more posts
{% endif %}

`divided_by` filter incorrectly rounds down when divisor is float

According to documentation, divided_by floors only when the divisor is an integer. Otherwise it should not round.

{{ 20 | divided_by: 7 }}
⇒ 2

{{ 20 | divided_by: 7.0 }}
⇒ 2.857142857142857

{% assign my_integer = 7 %}
{{ 20 | divided_by: my_integer }}
⇒ 2

{% assign my_integer = 7 %}
{% assign my_float = my_integer | times: 1.0 %}
{{ 20 | divided_by: my_float }}
⇒ 2.857142857142857

However, as of 0.9.1, all of these produce 2 as result since it floors unconditionally.

I think you may need to split Value::Num into two distinct variants to properly fix it.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.