Git Product home page Git Product logo

stick's Introduction

Stick

CircleCI GoDoc

A Go language port of the Twig templating engine.

Overview

This project is split across two parts.

Package github.com/tyler-sommer/stick is a Twig template parser and executor. It provides the core functionality and offers many of the same extension points as Twig like functions, filters, node visitors, etc.

Package github.com/tyler-sommer/stick/twig contains extensions to provide the most Twig-like experience for template writers. It aims to feature the same functions, filters, etc. to be closely Twig-compatible.

Current status

Stable, mostly feature complete

Stick itself is mostly feature-complete, with the exception of whitespace control, and better error handling in places.

Stick is made up of three main parts: a lexer, a parser, and a template executor. Stick's lexer and parser are complete. Template execution is under development, but essentially complete.

See the to do list for additional information.

Alternatives

These alternatives are worth checking out if you're considering using Stick.

Installation

Stick is intended to be used as a library. The recommended way to install the library is using go get.

go get -u github.com/tyler-sommer/stick

Usage

Execute a simple Stick template.

package main

import (
	"log"
	"os"
    
	"github.com/tyler-sommer/stick"
)

func main() {
	env := stick.New(nil)
	if err := env.Execute("Hello, {{ name }}!", os.Stdout, map[string]stick.Value{"name": "Tyler"}); err != nil {
		log.Fatal(err)
	}
}

See godoc for more information.

To do

Further

stick's People

Contributors

alex-pk avatar bluntelk avatar christianwilling avatar colinmo avatar dave avatar jrattue avatar mingwho avatar pdt256 avatar prasad83 avatar tjblackheart avatar tyler-sommer avatar

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

stick's Issues

Unable to use set tag to capture chunks of text

I'd like to use this format of the set tag, but it doesn't appear to be implemented (results in a parse error)

{% set foo %}
    <div id="pagination">
        ...
    </div>
{% endset %}

Any plans to implement this in the future?

Cannot evaluate variable values on the right side of operator

Hi Tyler, I was trying to do something like the following

package main

import (
	"github.com/tyler-sommer/stick"
	"os"
	"fmt"
)

func main() {
	env := stick.New(nil);

	p := map[string]stick.Value{"toothbrush": map[string]int{"cost":100}, "toothpaste":map[string]int{"cost":200},}

	err := env.Execute(" Hello, {% if toothbrush.cost == toothpaste.cost %} same price {% endif %}", os.Stdout, p)
	if err != nil {
		fmt.Println(err)
	}
}

It was throwing an error expected "TAG_CLOSE", got "PUNCTUATION" on line 1, column 43
This seems like a bug in the library?

Iris

Hi,
Is there any tools which integrate stick with golang iris?

Better error handling

There are several areas that could use some tweaking with regard to error handling in Stick. This issue will be used to track and explain the reasoning behind these changes.

Each section below describes a different category of improvement. Tasks to carry out these improvements are defined together at the end, grouped by category.

Missing default statements

Incomplete switch statements with no default handling may silently fail which could lead to frustration if a user encounters this behavior.

Places affected

  • Fixed in v1.0.6: func (s *state) evalExpr(node parse.Node) in exec.go in two places
    • when handling an unknown binary operator
    • when handling an unknown Expr
  • func (s *state) walkChild(node parse.Node) in exec.go

These cases are unlikely to encounter these silent failures in normal operation ("should never happen" barring the existence of a bug), but anyone hacking on stick itself may experience frustration when something isn't working and there is no hint as to what's going on.

Fixing these is technically a BC break as any templates relying on these silent failures will now fail to execute, returning an error instead. However, I don't think this is a real concern in practice and fixing these does not warrant a major version bump.

Impossible to return an error

User-defined additions to Stick do not currently support returning any error. This results in needing to return some "empty" value rather than trying to signal an error.

Places affected

  • user defined Funcs
  • user defined Tests
  • user defined Filters
    • twig_compat filters are littered with places that should have error handling
  • NodeVisitor Enter
  • NodeVisitor Leave

I'm a little bit on the fence on whether some of these should be fixed. I see two main considerations:

  • overly sensitive error handling (in cases where returning some "empty" value would be suitable instead)
  • and difficult-to-debug situations (where user-defined functionality fails silently and inexplicably).

Behavior in Twig itself is fairly loose and template execution generally tends to be forgiving of type mismatches and failed coercions. In Twig, user-defined functionality would need to throw an exception which would interrupt template execution. This can also be done in Go, if desired, with panic(). However, in many places it makes sense to just return some sensible "empty" value rather than halting template execution. Using panic() is also generally kind of smelly, though, so I hesitate to make that the go-to solution.

On the flip side, the current situation can lead to difficult to debug situations where user-defined functionality is not working as expected and there is no sign of what's going on because any error is being silently thrown away. Of course, logging could be used to provide some clue to the user.

One final factor to consider here is modifying the affected method signatures to return an error (arguably the best choice) would be a fairly major BC break, requiring all existing user-defined code to be updated. This would probably warrant a major version bump.

Tentative plan

  • For v1.0.x, in order to avoid breaking BC, prefer using panic() in user-defined code and convert it to an error using recover() during parsing (for NodeVisitors) and template execution.
    • "Silent" handling in many cases will still be preferred. As a workaround for silently ignoring errors, this could at least be logged so that feedback is available somewhere.
    • To enable logging from built-in user-defined functionality (ie. twig_compat), will probably add the ability to configure a Logger and somehow make that usable from existing filters and such. With that done, all silent error handling can at least be updated to log a message.
  • In some future (currently unplanned) v2.0.x, this would be fixed by modifying the affected types so they can return an error.
    • "Silent" handling may still be preferred in some places. In this case, the same log message workaround can be applied.

Inconsistent error messages

Error messages throughout Stick are inconsistent and could use some attention.

These are relatively minor, though any code expecting specific error messages may stop working when the messages change. I think this is unlikely and don't consider this reason enough to bump the major version, however.

Places affected

  • Prefixed log messages
    • All error messages in Stick should be prefixed with the method name where the error originated as well as (or at least) the package name.
    • Prefixes are great, but proper context added through specialized error types (and wrapping errors with those types) may be more suitable.
  • exec.go uses a mix of fmt.Sprintf and string concatenation
    • Stick should always use fmt.Sprintf or fmt.Errorf
  • many places are capitalized or awkwardly worded
    • Idiomatic Go suggests avoiding complete sentencies in error messages
    • Prefer "unsupported expression type" over "Unable to evaluate unsupported Expr type"
  • package parse has dedicated error types
    • This is the direction error handling should go for other places too.

Other improvements

  • Template execution errors currently provide no context about where in the template (or even which template) they occur.
    • These errors should be wrapped with an enriched type before being returned to the user, including filename, line, and offset.
  • Fixed in v1.0.6: Writing tests for errors during template execution is not possible
    • Modifying a parsed template Tree during test execution is necessary to trigger all possible error states
  • Tests should cover most (all) error cases

Tasks

Missing default case in switch statements

  • Add default case to walkExpr for unsupported expressions and binary operators (v1.0.6)
  • Add default case to walkChild in exec.go

User-defined functionality

  • Add recover() handlers to convert panics to errors during parsing and template execution.
  • Add ability to set a Logger on Env and pass that into state.
  • Update all existing built-in user-defined additions to panic on errors that warrant it, and log all others.
  • Update docs to explain panic() mechanism in user-defined functionality
  • Update docs with example on adding logging to a NodeVisitor

Consistent error messages

  • Update all string concatenation to prefer fmt.Sprintf or fmt.Errorf
  • Update all errors created by Stick itself during parsing and execution to follow idiomatic conventions, lowercase, no ending punctuation, and be prefixed with the method name.
  • Wrap all errors created by external code, adding the necessary context and the Stick method name where the error was first received.

Other improvements

  • Enable exec tests to make assertions about errors (v1.0.6)
  • Add ability for exec tests to manipulate parsed template Tree (v1.0.6)
  • Enrich template execution errors with template name, line, and offset of where the error occurred.
  • Add more tests that cover error states in template execution

Panic when parsing

I see a panic while parsing this:

{{ foo.bar() }}
panic: reflect: call of reflect.Value.Type on zero Value

... better would be an error.

Filesystemloader seems unable to process if statement

Say if we have a file test.twig like following

{% if 1 == 1 %}
1 equals 1
{% endif %}

Our script to render this template is as following

fsRoot, _ := os.Getwd()
env := stick.New(stick.NewFilesystemLoader(fsRoot))
p = map[string]stick.Value{"name": "World"}
env.Execute("test.twig", os.Stdout, p)

It should print "1 equals 1", but right now it is not printing anything.
Did I miss something here or is this a bug we need to fix?

Not parsing filters in {% for %} blocks

When trying to implement the batch twig filter I get a parse error.

Here is a small program to replicate the problem. It contains the test case and the expected output

package main

import (
    "github.com/tyler-sommer/stick"
    "os"
)

func main() {
    env := stick.NewEnv(nil)

    template := "{% for row in items|batch(3, 'No Item') %}{% for item in row %}{{ item }}.{% endfor %}.{% endfor %}"
    println("Expected: 1.2.3..4.5.6..7.8.No Item..")
    data := map[string]stick.Value{"items":[]int{1, 2, 3, 4, 5, 6, 7, 8}}

    print("Actual: ")
    err := env.Execute(template, os.Stdout, data)
    println()

    if nil != err {
        println(err.Error())
    }

}

The programs output is:

~/golang/src$ go run test-stick.go
Expected: 1.2.3..4.5.6..7.8.No Item..
Actual:
parse: expected "NUMBER", got "STRING_OPEN" on line 1, column 29

I have verified that this template syntax works with twig

Sep 27 2016 BC Break: Twig-compat moved to subpackage

I moved the Twig-compatibility related extensions to the subpackage github.com/tyler-sommer/stick/twig.

This impacts a couple of things:

  • Package stick is now solely a parser and template executor that aims to provide the same feature-set as the Twig language itself sans core extensions. To get a more Twig-like experience, including the expected auto-escaping, filters, functions, and tests, use github.com/tyler-sommer/stick/twig.
  • stick.New() now creates a completely bare executing environment.
  • twig.New() creates a more Twig-compatible environment with the expected filters and auto-escaping (and is under development)

It boils down to:

  • If you were using stick.New() before, switch to twig.New().
  • If you were using stick.NewEnv() before, use stick.New() instead.

Please open an issue if you run into any problems or have any feedback.

Error when accessing missing key in map

Hi there, (love this package, thank you soooo much for it!!)

I'm getting an error when I access a missing key in a map (originating here: https://github.com/tyler-sommer/stick/blob/master/value.go#L242) and I don't seem to find a way to suppress/avoid it.

In Twig, there is a strict_variables setting which causes this access to return null (https://twig.symfony.com/doc/3.x/templates.html#variables) and in twig I could check for the existence of the variable without triggering the error.

Specifically, I'm using this package to render arbitrary json data provided as map[string]interface{}, for example:

{ "data": { "foo": "bar" }}

and I'm trying to handle the expression {{ data.missing }} without running into the error. I would like it to just output empty string if missing is not defined on data.

Am I overlooking a way to do this? Thank you for your help!

Execute vs Render

Hi @tyler-sommer ,

First I would like to thank you for your good work.

I have a question why have you opted for Execute rather than Render ?

Doesn't render makes api vice very close to Twig itself ?

no issue, just thanks!

Just wanted to say thanks for building this lib, I really look forward to seeing it completed! Keep up the great work, sorry I can't contribute. Love twig in PHP and look forward to using it with Go!

Parse errors when accessing properties

parse: expected "ARRAY_CLOSE", got "PUNCTUATION" in 
{{ prices[item.ID] }}

parse: expected "TAG_CLOSE", got "PUNCTUATION" in 
{{ get_index(item.ID, prices)*item.Quantity }}

Twig comments don't work

The syntax for twig comments doesn't seem to work:

{# foo #}

Gives an error: Unknown node Text( foo )

OR operator evaluation bug

Hi Tyler it's me again :) It seems like if {% if true or false %} returns false
For example:

package main

import (
	"os"
	"github.com/tyler-sommer/stick"
)

func main() {
	env := stick.New(nil);    // A nil loader means stick will simply execute
                          // the string passed into env.Execute.

// Templates receive a map of string to any value.
p := map[string]stick.Value{"name": "World"}

// Substitute os.Stdout with any io.Writer.
env.Execute(`{% set item1 = "orange" %} {% set item2 = "apple" %} {% if item1 == "banana" or item2 == "apple" %} At least one item is correct {% else %} neither item is correct {% endif %}`, os.Stdout, p)
}

It will print neither item is correct.
I will have a look into this as well :)

Arrays don't work

The following...

{% set foo = ["bar", "baz"] %}

... gives an error:

parse: unexpected token "ARRAY_OPEN"

Revel question

Hi, is stick support Revel templates? Can I adapt stick into Revel without risk? Thanks

Extending a base template not working for me.

Something I do with twig in php like in the j2 files I have shown below. This doesn't seem to work in stick. I've narrowed it down to parent() being the culprit. Stick just kindof gives up after hitting that.

This library at least got further than some of the other's I've checked out for jinjas templates.

I'd be happy to contribute, but I would be learning from the ground up on this stuff. If no one else has time to really look into this, if you can point me in the right direction on learning this stuff from the ground up, I'm more than happy to put some of my free time into learning this a bit and throwing in a pull request when I patch it for myself.

templates/page1.j2

{% extends "base.j2" %}

{% block title %} - More to life than just fun!{% endblock %}
{% block head %}
	{{ parent() }}
{% endblock %}
{% block content %}
	Content goes here.... PAGE 1
	{{ a }}
	{{ b }}

{% endblock %}

templates/base.j2

<!doctype html>
<html lang="en">
<head>
	{% block head %}
	<title>{{ sitename }}{% block title %}{% endblock %}</title>
	{% endblock %}
</head>

<body>
	{% block content %}{% endblock %}
</body>
</html>

main.go

package main

import (
	"github.com/tyler-sommer/stick/twig"
	"github.com/tyler-sommer/stick"
	"os"
)

func main() {
	env := twig.New(stick.NewFilesystemLoader("./templates/"))
	env.Execute("page1.j2", os.Stdout, map[string]stick.Value{ "sitename": "Somesite!" })
}

If key of ctx has prefix "and" or "or" for function Execute, it errors


import (
	"log"
	"os"
    
	"github.com/tyler-sommer/stick"
)

func main() {
	env := stick.New(nil)
	if err := env.Execute("Hello, {{ order }}!", os.Stdout, map[string]stick.Value{"order": "ABCD"}); err != nil {
		log.Fatal(err)
	}
}

Something like this causes error parse: unexpected token "OPERATOR" on line 1, column 10

New tag version

We've been using it in production and since had the issue described in #44
Although we can build our project using the current HEAD, I would suggest creating a new v1.0.4 tag for it since it would include
this important fix.

Thanks for the awesome job!

Thank you!

Many thanks for this great package!

I really needed a simple and extensible stand alone template parser like this.
I've managed to create additional filters, I liked how easy I could accomplish this.

Mike

Test inside a ternary expression breaks parsing

A template like this: {{ x is defined ? 1 : 0 }}
results in parse: expected "PRINT_CLOSE", got "PUNCTUATION"

Similarly, this {% set y = x is defined ? 1 : 0 %}
results in parse: expected "TAG_CLOSE", got "PUNCTUATION"

PS. There's a chance I'll find the time and motivation to fix this (and other such issues I may run into), so any tips or pointers about the codebase would be welcome ๐Ÿ˜„

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.