Skip to content

Commit

Permalink
Add more automation tooling, and cleanup lint (#13)
Browse files Browse the repository at this point in the history
* Add more automation tooling, and cleanup lint

* More cleanup

* Cleanup some stuff for govet

Co-authored-by: Chris Pride <[email protected]>
  • Loading branch information
telemenar and Chris Pride authored Jun 9, 2022
1 parent 250ea81 commit b6cec98
Show file tree
Hide file tree
Showing 8 changed files with 196 additions and 133 deletions.
17 changes: 17 additions & 0 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,20 @@ jobs:

- name: Test
run: go test -v ./...

golangci-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
submodules: true

- name: Install Go
uses: actions/setup-go@v3
with:
go-version: ${{ env.GO_VERSION }}

- name: golangci-lint
uses: golangci/[email protected]
with:
version: latest
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

[![godoc reference](https://godoc.org/github.com/observeinc/mustache?status.svg)](https://godoc.org/github.com/observeinc/mustache)
[![Tests Actions Status](https://github.com/observeinc/mustache/workflows/Go/badge.svg)](https://github.com/observeinc/mustache/actions)
[![Go Report Card](https://goreportcard.com/badge/github.com/observeinc/mustache)](https://goreportcard.com/report/github.com/observeinc/mustache)

This is an implementation of the mustache templating language in Go.

Expand Down
2 changes: 1 addition & 1 deletion doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ http://mustache.github.io.
There are several wrappers of Parse and Render to help with different input or
output types. It is quite common to need to write the output of the template to
an http.ResponseWriter. In this case the Render function is the most apropriate.
an http.ResponseWriter. In this case the Render function is the most appropriate.
import "net/http"
import "github.com/observeinc/mustache"
Expand Down
68 changes: 55 additions & 13 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,46 @@ import (

func ExampleTemplate_basic() {
template := New()
template.ParseString(`{{#foo}}{{bar}}{{/foo}}`)
parseErr := template.ParseString(`{{#foo}}{{bar}}{{/foo}}`)
if parseErr != nil {
fmt.Fprintf(os.Stderr, "failed to parse template: %s\n", parseErr)
}

context := map[string]interface{}{
"foo": true,
"bar": "bazinga!",
}

output, _ := template.RenderString(context)
output, err := template.RenderString(context)
fmt.Println(output)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to render template: %s\n", err)
}
// Output: bazinga!
}

func ExampleTemplate_partials() {
partial := New(Name("partial"))
partial.ParseString(`{{bar}}`)
parseErr := partial.ParseString(`{{bar}}`)
if parseErr != nil {
fmt.Fprintf(os.Stderr, "failed to parse template: %s\n", parseErr)
}

template := New(Partial(partial))
template.ParseString(`{{#foo}}{{>partial}}{{/foo}}`)
templateErr := template.ParseString(`{{#foo}}{{>partial}}{{/foo}}`)
if templateErr != nil {
fmt.Fprintf(os.Stderr, "failed to parse template: %s\n", templateErr)
}

context := map[string]interface{}{
"foo": true,
"bar": "bazinga!",
}

template.Render(os.Stdout, context)
err := template.Render(os.Stdout, context)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to render template: %s\n", err)
}
// Output: bazinga!
}

Expand All @@ -48,20 +63,31 @@ func ExampleTemplate_reader() {
if err != nil {
fmt.Fprintf(os.Stderr, "failed to parse template: %s\n", err)
}
t.Render(os.Stdout, nil)
err = t.Render(os.Stdout, nil)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to render template: %s\n", err)
}

}

func ExampleTemplate_http() {
writer := httptest.NewRecorder()
request, _ := http.NewRequest("GET", "http://example.com?foo=bar&bar=one&bar=two", nil)

template := New()
template.ParseString(`
err := template.ParseString(`
<ul>{{#foo}}<li>{{.}}</li>{{/foo}}</ul>
<ul>{{#bar}}<li>{{.}}</li>{{/bar}}</ul>`)

if err != nil {
fmt.Fprintf(os.Stderr, "failed to parse template: %s\n", err)
}

handler := func(w http.ResponseWriter, r *http.Request) {
template.Render(w, r.URL.Query())
err := template.Render(w, r.URL.Query())
if err != nil {
fmt.Fprint(w, err.Error())
}
}

handler(writer, request)
Expand All @@ -73,27 +99,43 @@ func ExampleTemplate_http() {
}

func ExampleOption() {
title := New(Name("header")) // instantiate and name the template
title.ParseString("{{title}}") // parse a template string
title := New(Name("header")) // instantiate and name the template
titleErr := title.ParseString("{{title}}") // parse a template string
// If there was an error do something with it.
if titleErr != nil {
fmt.Fprintf(os.Stderr, "failed to parse template: %s\n", titleErr)
}

body := New()
body.Option(Name("body")) // options can be defined after we instantiate too
body.ParseString("{{content}}")
parseErr := body.ParseString("{{content}}")
// If there was an error do something with it.
if parseErr != nil {
fmt.Fprintf(os.Stderr, "failed to parse template: %s\n", parseErr)
}

template := New(
Delimiters("|", "|"), // set the mustache delimiters to | instead of {{
SilentMiss(false), // return an error if a variable lookup fails
Partial(title), // register a partial
Partial(body)) // and another one...

template.ParseString("|>header|\n|>body|")
templateErr := template.ParseString("|>header|\n|>body|")
if templateErr != nil {
fmt.Fprintf(os.Stderr, "failed to parse template: %s\n", templateErr)
}

context := map[string]interface{}{
"title": "Mustache",
"content": "Logic less templates with Mustache!",
}

template.Render(os.Stdout, context)
renderErr := template.Render(os.Stdout, context)
// If there was an error do something with it.
if renderErr != nil {
fmt.Fprintf(os.Stderr, "failed to render template: %s\n", renderErr)
}

// Output: Mustache
// Logic less templates with Mustache!
}
5 changes: 2 additions & 3 deletions lex.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ type stateFn func(*lexer) stateFn

// lexer holds the state of the scanner.
type lexer struct {
name string // the name of the input; used only for error reports.
input string // the string being scanned.
leftDelim string // start of action.
rightDelim string // end of action.
Expand Down Expand Up @@ -244,7 +243,7 @@ func stateLeftDelim(l *lexer) stateFn {
if l.peek() == '=' {
// When the lexer encounters "{{=" it proceeds to the set delimiter
// state which alters the left and right delimiters. This operation is
// hidden from the parser and no tokens are emited.
// hidden from the parser and no tokens are emitted.
l.next()
return stateSetDelim
}
Expand Down Expand Up @@ -378,7 +377,7 @@ func stateIdentWithMode(exitState stateFn) stateFn {
l.next()
default:
// We've found presumably the closing bracket.
// backup by the ammount of the counted whitespace so as to not include it
// backup by the amount of the counted whitespace so as to not include it
// in the ident token.
//
// This whitespace will we add back will be ignored as part of the stateTag
Expand Down
106 changes: 66 additions & 40 deletions lookup.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,53 +36,26 @@ func lookup(name string, context ...interface{}) (interface{}, bool) {
// If the current context is a map, we'll look for a key in that map
// that matches the name.
case reflect.Map:
item := reflectValue.MapIndex(reflect.ValueOf(name))
if item.IsValid() {
return item.Interface(), truth(item)
val, ok, found := lookup_map(name, reflectValue)
if found {
return val, ok
}

// If the current context is a struct, we'll look for a property in that
// struct that matches the name. In the near future I'd like to add
// support for matching struct names to tags so we can use lower_case
// names in our templates which makes it more mustache like.
// struct that matches the name.
case reflect.Struct:
field := reflectValue.FieldByName(name)
if field.IsValid() && field.CanInterface() {
return field.Interface(), truth(field)
}
method := reflectValue.MethodByName(name)
if method.IsValid() && method.Type().NumIn() == 1 {
out := method.Call(nil)[0]
return out.Interface(), truth(out)
}

typ := reflectValue.Type()
for i := 0; i < typ.NumField(); i++ {
f := typ.Field(i)
if f.PkgPath != "" {
continue
}
tag := f.Tag.Get("mustache")
if tag == name {
field := reflectValue.Field(i)
if field.IsValid() {
return field.Interface(), truth(field)
}
}
val, ok, found := lookup_struct(name, reflectValue)
if found {
return val, ok
}

// If the current context is an array or slice, we'll try to find the current
// name as an index in the context.
case reflect.Array, reflect.Slice:
idx, err := strconv.Atoi(name)
if err != nil {
continue
}
if reflectValue.Len() <= idx || idx < 0 {
continue
}
field := reflectValue.Index(idx)
if field.IsValid() {
return field.Interface(), truth(field)
val, ok, found := lookup_array(name, reflectValue)
if found {
return val, ok
}

}
// If by this point no value was matched, we'll move up a step in the
// chain and try to match a value there.
Expand All @@ -92,6 +65,59 @@ func lookup(name string, context ...interface{}) (interface{}, bool) {
return nil, false
}

func lookup_map(name string, reflectValue reflect.Value) (value interface{}, ok bool, found bool) {
item := reflectValue.MapIndex(reflect.ValueOf(name))
if item.IsValid() {
return item.Interface(), truth(item), true
}
return nil, false, false

}

func lookup_struct(name string, reflectValue reflect.Value) (value interface{}, ok bool, found bool) {
field := reflectValue.FieldByName(name)
if field.IsValid() && field.CanInterface() {
return field.Interface(), truth(field), true
}
method := reflectValue.MethodByName(name)
if method.IsValid() && method.Type().NumIn() == 1 {
out := method.Call(nil)[0]
return out.Interface(), truth(out), true
}

typ := reflectValue.Type()
for i := 0; i < typ.NumField(); i++ {
f := typ.Field(i)
if f.PkgPath != "" {
continue
}
tag := f.Tag.Get("mustache")
if tag == name {
field := reflectValue.Field(i)
if field.IsValid() {
return field.Interface(), truth(field), true
}
}
}
return nil, false, false
}

func lookup_array(name string, reflectValue reflect.Value) (value interface{}, ok bool, found bool) {
idx, err := strconv.Atoi(name)
if err != nil {
return nil, false, false
}
if reflectValue.Len() <= idx || idx < 0 {
return nil, false, false
}
field := reflectValue.Index(idx)
if field.IsValid() {
return field.Interface(), truth(field), true
}

return nil, false, false
}

// The truth function will tell us if r is a truthy value or not. This is
// important for sections as they will render their content based on the output
// of this function.
Expand Down
Loading

0 comments on commit b6cec98

Please sign in to comment.