1
0
mirror of https://github.com/danog/liquid.git synced 2024-11-26 21:14:45 +01:00
This commit is contained in:
Oliver Steele 2017-07-15 12:13:07 -04:00
parent 6b8f76ce9b
commit 29c902f6b3
11 changed files with 159 additions and 48 deletions

View File

@ -97,8 +97,8 @@ Please refer to the [contribution guidelines](./CONTRIBUTING.md).
| Package | Author | Description | License |
|-----------------------------------------------------|-----------------|-----------------------------------------|--------------------|
| [gopkg.in/yaml.v2](https://github.com/go-yaml/yaml) | Canonical | YAML support (for printing parse trees) | Apache License 2.0 |
| [Ragel](http://www.colm.net/open-source/ragel/) | Adrian Thurston | scanning expressions | MIT |
| [gopkg.in/yaml.v2](https://github.com/go-yaml/yaml) | Canonical | YAML support (for printing parse trees) | Apache License 2.0 |
Michael Hamrah's [Lexing with Ragel and Parsing with Yacc using Go](https://medium.com/@mhamrah/lexing-with-ragel-and-parsing-with-yacc-using-go-81e50475f88f) was essential to understanding `go yacc`.

View File

@ -19,32 +19,50 @@ import (
"github.com/osteele/liquid"
)
// for testing
var (
stderr = os.Stderr
stdout io.Writer = os.Stdout
stdin io.Reader = os.Stdin
exit = os.Exit
)
func main() {
args := os.Args[1:]
if err := run(os.Args[1:]); err != nil {
fmt.Fprint(stderr, err) // nolint: gas
exit(1)
}
}
func run(args []string) error {
switch {
case len(args) == 0:
buf := new(bytes.Buffer)
_, err := io.Copy(buf, os.Stdin)
exitIfErr(err)
if _, err := io.Copy(buf, stdin); err != nil {
return err
}
render(buf.Bytes(), "")
case args[0] == "-h" || args[0] == "--help":
usage(false)
case strings.HasPrefix(args[0], "-"):
usage(true)
os.Exit(1)
exit(1)
case len(args) == 1:
s, err := ioutil.ReadFile(args[0])
exitIfErr(err)
if err != nil {
return err
}
render(s, args[0])
default:
usage(true)
}
return nil
}
func exitIfErr(err error) {
if err != nil {
fmt.Fprint(os.Stdout, err) // nolint: gas
os.Exit(1)
fmt.Fprint(stdout, err) // nolint: gas
exit(1)
}
}
@ -53,12 +71,12 @@ func render(b []byte, filename string) {
exitIfErr(err)
out, err := tpl.Render(map[string]interface{}{})
exitIfErr(err)
os.Stdout.Write(out) // nolint: gas, errcheck
stdout.Write(out) // nolint: gas, errcheck
}
func usage(error bool) {
fmt.Printf("usage: %s [FILE]\n", os.Args[0])
if error {
os.Exit(1)
exit(1)
}
}

17
cmd/liquid/main_test.go Normal file
View File

@ -0,0 +1,17 @@
package main
import (
"bytes"
"testing"
"github.com/stretchr/testify/require"
)
func TestMain(t *testing.T) {
src := `{{ "Hello World" | downcase | split: " " | first | append: "!"}}`
stdin = bytes.NewBufferString(src)
buf := new(bytes.Buffer)
stdout = buf
require.NoError(t, run([]string{}))
require.Equal(t, "hello!", buf.String())
}

49
evaluator/compare_test.go Normal file
View File

@ -0,0 +1,49 @@
package evaluator
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
)
var eqTestObj = struct{ a, b int }{1, 2}
var eqArrayTestObj = [2]int{1, 2}
var eqTests = []struct {
a, b interface{}
expected bool
}{
{nil, nil, true},
{nil, 1, false},
{1, nil, false},
{false, false, true},
{false, true, false},
{0, 1, false},
{1, 1, true},
{1.0, 1.0, true},
{1, 1.0, true},
{1, 2.0, false},
{1.0, 1, true},
{"a", "b", false},
{"a", "a", true},
{int8(2), int16(2), true}, // TODO
// {uint8(2), int8(2), true}, // FIXME
{eqArrayTestObj, eqArrayTestObj[:], true},
{[]string{"a"}, []string{"a"}, true},
{[]string{"a"}, []string{"a", "b"}, false},
{[]string{"a", "b"}, []string{"a"}, false},
{[]string{"a", "b"}, []string{"a", "b"}, true},
{[]string{"a", "b"}, []string{"a", "c"}, false},
{[]interface{}{1.0, 2}, []interface{}{1, 2.0}, true},
{eqTestObj, eqTestObj, true},
}
func TestEqual(t *testing.T) {
for i, test := range eqTests {
t.Run(fmt.Sprintf("%02d", i+1), func(t *testing.T) {
value := Equal(test.a, test.b)
require.Equalf(t, test.expected, value, "%#v == %#v", test.a, test.b)
})
}
}

View File

@ -114,3 +114,12 @@ func TestConvert_map_to_array(t *testing.T) {
// require.NotNil(t, ptr)
// require.Equal(t, "ab", *ptr)
// }
func TestMustConvert(t *testing.T) {
typ := reflect.TypeOf("")
v := MustConvert(2, typ)
require.Equal(t, "2", v)
typ = reflect.TypeOf(2)
require.Panics(t, func() { MustConvert("xxx", typ) })
}

View File

@ -7,31 +7,6 @@ import (
"github.com/stretchr/testify/require"
)
var eqTests = []struct {
a, b interface{}
expected bool
}{
{nil, nil, true},
{nil, 1, false},
{1, nil, false},
{false, false, true},
{false, true, false},
{0, 1, false},
{1, 1, true},
{1.0, 1.0, true},
{1, 1.0, true},
{1, 2.0, false},
{1.0, 1, true},
{"a", "b", false},
{"a", "a", true},
{[]string{"a"}, []string{"a"}, true},
{[]string{"a"}, []string{"a", "b"}, false},
{[]string{"a", "b"}, []string{"a"}, false},
{[]string{"a", "b"}, []string{"a", "b"}, true},
{[]string{"a", "b"}, []string{"a", "c"}, false},
{[]interface{}{1.0, 2}, []interface{}{1, 2.0}, true},
}
var lessTests = []struct {
a, b interface{}
expected bool
@ -57,15 +32,6 @@ func TestContains(t *testing.T) {
require.False(t, Contains([]int{1, 2}, 3))
}
func TestEqual(t *testing.T) {
for i, test := range eqTests {
t.Run(fmt.Sprintf("%02d", i+1), func(t *testing.T) {
value := Equal(test.a, test.b)
require.Equalf(t, test.expected, value, "%#v == %#v", test.a, test.b)
})
}
}
func TestLess(t *testing.T) {
for i, test := range lessTests {
t.Run(fmt.Sprintf("%02d", i+1), func(t *testing.T) {

View File

@ -31,7 +31,7 @@ func (c *Config) AddFilter(name string, fn interface{}) {
panic(fmt.Errorf("a filter must be a function"))
case rf.Type().NumIn() < 1:
panic(fmt.Errorf("a filter function must have at least one input"))
case rf.Type().NumOut() > 2:
case rf.Type().NumOut() < 1 || 2 < rf.Type().NumOut():
panic(fmt.Errorf("a filter must be have one or two outputs"))
// case rf.Type().Out(1).Implements(…):
// panic(typeError("a filter's second output must be type error"))

View File

@ -7,6 +7,16 @@ import (
"github.com/stretchr/testify/require"
)
func TestContext_AddFilter(t *testing.T) {
cfg := NewConfig()
require.NotPanics(t, func() { cfg.AddFilter("f", func(int) int { return 0 }) })
require.NotPanics(t, func() { cfg.AddFilter("f", func(int) (a int, e error) { return }) })
require.Panics(t, func() { cfg.AddFilter("f", func() int { return 0 }) })
require.Panics(t, func() { cfg.AddFilter("f", func(int) {}) })
// require.Panics(t, func() { cfg.AddFilter("f", func(int) (a int, b int) { return }) })
require.Panics(t, func() { cfg.AddFilter("f", func(int) (a int, e error, b int) { return }) })
}
func TestContext_runFilter(t *testing.T) {
cfg := NewConfig()
constant := func(value interface{}) valueFn {

View File

@ -12,13 +12,15 @@ var parseTests = []struct {
expect interface{}
}{
{`a | filter: b`, 3},
// {`%assign a = 3`, nil},
// {`{%cycle 'a'`, []interface{}{"a"}},
// {`{%cycle 'a', 'b'`, []interface{}{"a", "b"}},
}
var parseErrorTests = []struct{ in, expected string }{
{"a syntax error", "parse error"},
{`%assign a`, "parse error"},
{`%assign a 3`, "parse error"},
{`%cycle 'a' 'b'`, "parse error"},
{`%loop a in in`, "parse error"},
{`%when a b`, "parse error"},
}
func TestParse(t *testing.T) {
@ -37,6 +39,36 @@ func TestParse(t *testing.T) {
}
}
func TestParseStatement(t *testing.T) {
stmt, err := ParseStatement(AssignStatementSelector, "a = b")
require.NoError(t, err)
require.Equal(t, "a", stmt.Assignment.Variable)
stmt, err = ParseStatement(CycleStatementSelector, "'a', 'b'")
require.NoError(t, err)
require.Equal(t, "", stmt.Cycle.Group)
require.Len(t, stmt.Cycle.Values, 2)
require.Equal(t, []string{"a", "b"}, stmt.Cycle.Values)
stmt, err = ParseStatement(CycleStatementSelector, "'g': 'a', 'b'")
require.NoError(t, err)
require.Equal(t, "g", stmt.Cycle.Group)
require.Len(t, stmt.Cycle.Values, 2)
require.Equal(t, []string{"a", "b"}, stmt.Cycle.Values)
stmt, err = ParseStatement(LoopStatementSelector, "x in array reversed offset: 2 limit: 3")
require.NoError(t, err)
require.Equal(t, "x", stmt.Loop.Variable)
require.True(t, stmt.Loop.Reversed)
require.Equal(t, 2, stmt.Loop.Offset)
require.NotNil(t, stmt.Loop.Limit)
require.Equal(t, 3, *stmt.Loop.Limit)
stmt, err = ParseStatement(WhenStatementSelector, "a, b")
require.NoError(t, err)
require.Len(t, stmt.When.Exprs, 2)
}
func TestParse_errors(t *testing.T) {
for i, test := range parseErrorTests {
t.Run(fmt.Sprintf("%02d", i+1), func(t *testing.T) {

View File

@ -25,6 +25,8 @@ var filterTests = []struct {
{`pages | map: 'category' | join`, "business, celebrities, <nil>, lifestyle, sports, <nil>, technology"},
{`pages | map: 'category' | compact | join`, "business, celebrities, lifestyle, sports, technology"},
{`"John, Paul, George, Ringo" | split: ", " | join: " and "`, "John and Paul and George and Ringo"},
// {`",John, Paul, George, Ringo" | split: ", " | join: " and "`, "John and Paul and George and Ringo"},
// {`"John, Paul, George, Ringo," | split: ", " | join: " and "`, "John and Paul and George and Ringo"},
{`animals | sort | join: ", "`, "Sally Snake, giraffe, octopus, zebra"},
{`sort_prop | sort: "weight" | inspect`, `[{"weight":null},{"weight":1},{"weight":3},{"weight":5}]`},
{`fruits | reverse | join: ", "`, "plums, peaches, oranges, apples"},
@ -67,6 +69,7 @@ var filterTests = []struct {
{`"website.com" | append: "/index.html"`, "website.com/index.html"},
{`"title" | capitalize`, "Title"},
{`"my great title" | capitalize`, "My great title"},
{`"" | capitalize`, ""},
{`"Parker Moore" | downcase`, "parker moore"},
{`"Have you read 'James & the Giant Peach'?" | escape`, "Have you read &#39;James &amp; the Giant Peach&#39;?"},
{`"1 < 2 & 3" | escape_once`, "1 &lt; 2 &amp; 3"},
@ -79,7 +82,10 @@ var filterTests = []struct {
{`"Liquid" | slice: 2`, "q"},
{`"Liquid" | slice: 2, 5`, "quid"},
{`"Liquid" | slice: -3, 2`, "ui"},
{`"Have <em>you</em> read <strong>Ulysses</strong>?" | strip_html`, "Have you read Ulysses?"},
{`string_with_newlines | strip_newlines`, "Hellothere"},
{`"Ground control to Major Tom." | truncate: 20`, "Ground control to..."},
{`"Ground control to Major Tom." | truncate: 25, ", and so on"`, "Ground control, and so on"},
{`"Ground control to Major Tom." | truncate: 20, ""`, "Ground control to Ma"},
@ -135,6 +141,7 @@ var filterTests = []struct {
{`5 | divided_by: 3`, 1},
{`20 | divided_by: 7`, 2},
{`20 | divided_by: 7.0`, 2.857142857142857},
{`20 | divided_by: 's'`, nil},
{`1.2 | round`, 1},
{`2.7 | round`, 3},
@ -143,6 +150,8 @@ var filterTests = []struct {
// Jekyll extensions; added here for convenient testing
// TODO add this just to the test environment
{`obj | inspect`, `{"a":1}`},
{`1 | type`, `int`},
{`"1" | type`, `string`},
}
func timeMustParse(s string) time.Time {

View File

@ -28,6 +28,7 @@ var renderTests = []struct{ in, out string }{
{`{{ "abc" }}`, "abc"},
{`{{ x }}`, "123"},
{`{{ page.title }}`, "Introduction"},
{`{{ array }}`, "firstsecondthird"},
{`{{ array[1] }}`, "second"},
}