Handlebars for golang

Overview

raymond Build Status GoDoc

Handlebars for golang with the same features as handlebars.js 3.0.

The full API documentation is available here: http://godoc.org/github.com/aymerick/raymond.

Raymond Logo

Table of Contents

Quick Start

$ go get github.com/aymerick/raymond

The quick and dirty way of rendering a handlebars template:

package main

import (
    "fmt"

    "github.com/aymerick/raymond"
)

func main() {
    tpl := `<div class="entry">
  <h1>{{title}}</h1>
  <div class="body">
    {{body}}
  </div>
</div>
`

    ctx := map[string]string{
        "title": "My New Post",
        "body":  "This is my first post!",
    }

    result, err := raymond.Render(tpl, ctx)
    if err != nil {
        panic("Please report a bug :)")
    }

    fmt.Print(result)
}

Displays:

<div class="entry">
  <h1>My New Post</h1>
  <div class="body">
    This is my first post!
  </div>
</div>

Please note that the template will be parsed everytime you call Render() function. So you probably want to read the next section.

Correct Usage

To avoid parsing a template several times, use the Parse() and Exec() functions:

package main

import (
    "fmt"

    "github.com/aymerick/raymond"
)

func main() {
    source := `<div class="entry">
  <h1>{{title}}</h1>
  <div class="body">
    {{body}}
  </div>
</div>
`

    ctxList := []map[string]string{
        {
            "title": "My New Post",
            "body":  "This is my first post!",
        },
        {
            "title": "Here is another post",
            "body":  "This is my second post!",
        },
    }

    // parse template
    tpl, err := raymond.Parse(source)
    if err != nil {
        panic(err)
    }

    for _, ctx := range ctxList {
        // render template
        result, err := tpl.Exec(ctx)
        if err != nil {
            panic(err)
        }

        fmt.Print(result)
    }
}

Displays:

<div class="entry">
  <h1>My New Post</h1>
  <div class="body">
    This is my first post!
  </div>
</div>
<div class="entry">
  <h1>Here is another post</h1>
  <div class="body">
    This is my second post!
  </div>
</div>

You can use MustParse() and MustExec() functions if you don't want to deal with errors:

// parse template
tpl := raymond.MustParse(source)

// render template
result := tpl.MustExec(ctx)

Context

The rendering context can contain any type of values, including array, slice, map, struct and func.

When using structs, be warned that only exported fields are accessible. However you can access exported fields in template with their lowercase names. For example, both {{author.firstName}} and {{Author.FirstName}} references give the same result, as long as Author and FirstName are exported struct fields.

More, you can use the handlebars struct tag to specify a template variable name different from the struct field name.

package main

import (
  "fmt"

  "github.com/aymerick/raymond"
)

func main() {
    source := `<div class="post">
  <h1>By {{author.firstName}} {{author.lastName}}</h1>
  <div class="body">{{body}}</div>

  <h1>Comments</h1>

  {{#each comments}}
  <h2>By {{author.firstName}} {{author.lastName}}</h2>
  <div class="body">{{content}}</div>
  {{/each}}
</div>`

    type Person struct {
        FirstName string
        LastName  string
    }

    type Comment struct {
        Author Person
        Body   string `handlebars:"content"`
    }

    type Post struct {
        Author   Person
        Body     string
        Comments []Comment
    }

    ctx := Post{
        Person{"Jean", "Valjean"},
        "Life is difficult",
        []Comment{
            Comment{
                Person{"Marcel", "Beliveau"},
                "LOL!",
            },
        },
    }

    output := raymond.MustRender(source, ctx)

    fmt.Print(output)
}

Output:

<div class="post">
  <h1>By Jean Valjean</h1>
  <div class="body">Life is difficult</div>

  <h1>Comments</h1>

  <h2>By Marcel Beliveau</h2>
  <div class="body">LOL!</div>
</div>

HTML Escaping

By default, the result of a mustache expression is HTML escaped. Use the triple mustache {{{ to output unescaped values.

source := `<div class="entry">
  <h1>{{title}}</h1>
  <div class="body">
    {{{body}}}
  </div>
</div>
`

ctx := map[string]string{
    "title": "All about <p> Tags",
    "body":  "<p>This is a post about &lt;p&gt; tags</p>",
}

tpl := raymond.MustParse(source)
result := tpl.MustExec(ctx)

fmt.Print(result)

Output:

<div class="entry">
  <h1>All about &lt;p&gt; Tags</h1>
  <div class="body">
    <p>This is a post about &lt;p&gt; tags</p>
  </div>
</div>

When returning HTML from a helper, you should return a SafeString if you don't want it to be escaped by default. When using SafeString all unknown or unsafe data should be manually escaped with the Escape method.

raymond.RegisterHelper("link", func(url, text string) raymond.SafeString {
    return raymond.SafeString("<a href='" + raymond.Escape(url) + "'>" + raymond.Escape(text) + "</a>")
})

tpl := raymond.MustParse("{{link url text}}")

ctx := map[string]string{
    "url":  "http://www.aymerick.com/",
    "text": "This is a <em>cool</em> website",
}

result := tpl.MustExec(ctx)
fmt.Print(result)

Output:

<a href='http://www.aymerick.com/'>This is a &lt;em&gt;cool&lt;/em&gt; website</a>

Helpers

Helpers can be accessed from any context in a template. You can register a helper with the RegisterHelper function.

For example:

<div class="post">
  <h1>By {{fullName author}}</h1>
  <div class="body">{{body}}</div>

  <h1>Comments</h1>

  {{#each comments}}
  <h2>By {{fullName author}}</h2>
  <div class="body">{{body}}</div>
  {{/each}}
</div>

With this context and helper:

ctx := map[string]interface{}{
    "author": map[string]string{"firstName": "Jean", "lastName": "Valjean"},
    "body":   "Life is difficult",
    "comments": []map[string]interface{}{{
        "author": map[string]string{"firstName": "Marcel", "lastName": "Beliveau"},
        "body":   "LOL!",
    }},
}

raymond.RegisterHelper("fullName", func(person map[string]string) string {
    return person["firstName"] + " " + person["lastName"]
})

Outputs:

<div class="post">
  <h1>By Jean Valjean</h1>
  <div class="body">Life is difficult</div>

  <h1>Comments</h1>

  <h2>By Marcel Beliveau</h2>
  <div class="body">LOL!</div>
</div>

Helper arguments can be any type.

The following example uses structs instead of maps and produces the same output as the previous one:

<div class="post">
  <h1>By {{fullName author}}</h1>
  <div class="body">{{body}}</div>

  <h1>Comments</h1>

  {{#each comments}}
  <h2>By {{fullName author}}</h2>
  <div class="body">{{body}}</div>
  {{/each}}
</div>

With this context and helper:

type Post struct {
    Author   Person
    Body     string
    Comments []Comment
}

type Person struct {
    FirstName string
    LastName  string
}

type Comment struct {
    Author Person
    Body   string
}

ctx := Post{
    Person{"Jean", "Valjean"},
    "Life is difficult",
    []Comment{
        Comment{
            Person{"Marcel", "Beliveau"},
            "LOL!",
        },
    },
}

raymond.RegisterHelper("fullName", func(person Person) string {
    return person.FirstName + " " + person.LastName
})

You can unregister global helpers with RemoveHelper and RemoveAllHelpers functions:

raymond.RemoveHelper("fullname")
raymond.RemoveAllHelpers()

Template Helpers

You can register a helper on a specific template, and in that case that helper will be available to that template only:

tpl := raymond.MustParse("User: {{fullName user.firstName user.lastName}}")

tpl.RegisterHelper("fullName", func(firstName, lastName string) string {
  return firstName + " " + lastName
})

Built-In Helpers

Those built-in helpers are available to all templates.

The if block helper

You can use the if helper to conditionally render a block. If its argument returns false, nil, 0, "", an empty array, an empty slice or an empty map, then raymond will not render the block.

<div class="entry">
  {{#if author}}
    <h1>{{firstName}} {{lastName}}</h1>
  {{/if}}
</div>

When using a block expression, you can specify a template section to run if the expression returns a falsy value. That section, marked by {{else}} is called an "else section".

<div class="entry">
  {{#if author}}
    <h1>{{firstName}} {{lastName}}</h1>
  {{else}}
    <h1>Unknown Author</h1>
  {{/if}}
</div>

You can chain several blocks. For example that template:

{{#if isActive}}
  <img src="star.gif" alt="Active">
{{else if isInactive}}
  <img src="cry.gif" alt="Inactive">
{{else}}
  <img src="wat.gif" alt="Unknown">
{{/if}}

With that context:

ctx := map[string]interface{}{
    "isActive":   false,
    "isInactive": false,
}

Outputs:

 <img src="wat.gif" alt="Unknown">

The unless block helper

You can use the unless helper as the inverse of the if helper. Its block will be rendered if the expression returns a falsy value.

<div class="entry">
  {{#unless license}}
  <h3 class="warning">WARNING: This entry does not have a license!</h3>
  {{/unless}}
</div>

The each block helper

You can iterate over an array, a slice, a map or a struct instance using this built-in each helper. Inside the block, you can use this to reference the element being iterated over.

For example:

<ul class="people">
  {{#each people}}
    <li>{{this}}</li>
  {{/each}}
</ul>

With this context:

map[string]interface{}{
    "people": []string{
        "Marcel", "Jean-Claude", "Yvette",
    },
}

Outputs:

<ul class="people">
  <li>Marcel</li>
  <li>Jean-Claude</li>
  <li>Yvette</li>
</ul>

You can optionally provide an {{else}} section which will display only when the passed argument is an empty array, an empty slice or an empty map (a struct instance is never considered empty).

{{#each paragraphs}}
  <p>{{this}}</p>
{{else}}
  <p class="empty">No content</p>
{{/each}}

When looping through items in each, you can optionally reference the current loop index via {{@index}}.

{{#each array}}
  {{@index}}: {{this}}
{{/each}}

Additionally for map and struct instance iteration, {{@key}} references the current map key or struct field name:

{{#each map}}
  {{@key}}: {{this}}
{{/each}}

The first and last steps of iteration are noted via the @first and @last variables.

The with block helper

You can shift the context for a section of a template by using the built-in with block helper.

<div class="entry">
  <h1>{{title}}</h1>

  {{#with author}}
  <h2>By {{firstName}} {{lastName}}</h2>
  {{/with}}
</div>

With this context:

map[string]interface{}{
    "title": "My first post!",
    "author": map[string]string{
        "firstName": "Jean",
        "lastName":  "Valjean",
    },
}

Outputs:

<div class="entry">
  <h1>My first post!</h1>

  <h2>By Jean Valjean</h2>
</div>

You can optionally provide an {{else}} section which will display only when the passed argument is falsy.

{{#with author}}
  <p>{{name}}</p>
{{else}}
  <p class="empty">No content</p>
{{/with}}

The lookup helper

The lookup helper allows for dynamic parameter resolution using handlebars variables.

{{#each bar}}
  {{lookup ../foo @index}}
{{/each}}

The log helper

The log helper allows for logging while rendering a template.

{{log "Look at me!"}}

Note that the handlebars.js @level variable is not supported.

The equal helper

The equal helper renders a block if the string version of both arguments are equals.

For example that template:

{{#equal foo "bar"}}foo is bar{{/equal}}
{{#equal foo baz}}foo is the same as baz{{/equal}}
{{#equal nb 0}}nothing{{/equal}}
{{#equal nb 1}}there is one{{/equal}}
{{#equal nb "1"}}everything is stringified before comparison{{/equal}}

With that context:

ctx := map[string]interface{}{
    "foo": "bar",
    "baz": "bar",
    "nb":  1,
}

Outputs:

foo is bar
foo is the same as baz

there is one
everything is stringified before comparison

Block Helpers

Block helpers make it possible to define custom iterators and other functionality that can invoke the passed block with a new context.

Block Evaluation

As an example, let's define a block helper that adds some markup to the wrapped text.

<div class="entry">
  <h1>{{title}}</h1>
  <div class="body">
    {{#bold}}{{body}}{{/bold}}
  </div>
</div>

The bold helper will add markup to make its text bold.

raymond.RegisterHelper("bold", func(options *raymond.Options) raymond.SafeString {
    return raymond.SafeString(`<div class="mybold">` + options.Fn() + "</div>")
})

A helper evaluates the block content with current context by calling options.Fn().

If you want to evaluate the block with another context, then use options.FnWith(ctx), like this french version of built-in with block helper:

raymond.RegisterHelper("avec", func(context interface{}, options *raymond.Options) string {
    return options.FnWith(context)
})

With that template:

{{#avec obj.text}}{{this}}{{/avec}}

Conditional

Let's write a french version of if block helper:

source := `{{#si yep}}YEP !{{/si}}`

ctx := map[string]interface{}{"yep": true}

raymond.RegisterHelper("si", func(conditional bool, options *raymond.Options) string {
    if conditional {
        return options.Fn()
    }
    return ""
})

Note that as the first parameter of the helper is typed as bool an automatic conversion is made if corresponding context value is not a boolean. So this helper works with that context too:

ctx := map[string]interface{}{"yep": "message"}

Here, "message" is converted to true because it is an non-empty string. See IsTrue() function for more informations on boolean conversion.

Else Block Evaluation

We can enhance the si block helper to evaluate the else block by calling options.Inverse() if conditional is false:

source := `{{#si yep}}YEP !{{else}}NOP !{{/si}}`

ctx := map[string]interface{}{"yep": false}

raymond.RegisterHelper("si", func(conditional bool, options *raymond.Options) string {
    if conditional {
        return options.Fn()
    }
    return options.Inverse()
})

Outputs:

NOP !

Block Parameters

It's possible to receive named parameters from supporting helpers.

{{#each users as |user userId|}}
  Id: {{userId}} Name: {{user.name}}
{{/each}}

In this particular example, user will have the same value as the current context and userId will have the index/key value for the iteration.

This allows for nested helpers to avoid name conflicts.

For example:

{{#each users as |user userId|}}
  {{#each user.books as |book bookId|}}
    User: {{userId}} Book: {{bookId}}
  {{/each}}
{{/each}}

With this context:

ctx := map[string]interface{}{
    "users": map[string]interface{}{
        "marcel": map[string]interface{}{
            "books": map[string]interface{}{
                "book1": "My first book",
                "book2": "My second book",
            },
        },
        "didier": map[string]interface{}{
            "books": map[string]interface{}{
                "bookA": "Good book",
                "bookB": "Bad book",
            },
        },
    },
}

Outputs:

  User: marcel Book: book1
  User: marcel Book: book2
  User: didier Book: bookA
  User: didier Book: bookB

As you can see, the second block parameter is the map key. When using structs, it is the struct field name.

When using arrays and slices, the second parameter is element index:

ctx := map[string]interface{}{
    "users": []map[string]interface{}{
        {
            "id": "marcel",
            "books": []map[string]interface{}{
                {"id": "book1", "title": "My first book"},
                {"id": "book2", "title": "My second book"},
            },
        },
        {
            "id": "didier",
            "books": []map[string]interface{}{
                {"id": "bookA", "title": "Good book"},
                {"id": "bookB", "title": "Bad book"},
            },
        },
    },
}

Outputs:

    User: 0 Book: 0
    User: 0 Book: 1
    User: 1 Book: 0
    User: 1 Book: 1

Helper Parameters

When calling a helper in a template, raymond expects the same number of arguments as the number of helper function parameters.

So this template:

{{add a}}

With this helper:

raymond.RegisterHelper("add", func(val1, val2 int) string {
    return strconv.Itoa(val1 + val2)
})

Will simply panics, because we call the helper with one argument whereas it expects two.

Automatic conversion

Let's create a concat helper that expects two strings and concat them:

source := `{{concat a b}}`

ctx := map[string]interface{}{
    "a": "Jean",
    "b": "Valjean",
}

raymond.RegisterHelper("concat", func(val1, val2 string) string {
    return val1 + " " + val2
})

Everything goes well, two strings are passed as arguments to the helper that outputs:

Jean VALJEAN

But what happens if there is another type than string in the context ? For example:

ctx := map[string]interface{}{
    "a": 10,
    "b": "Valjean",
}

Actually, raymond perfoms automatic string conversion. So because the first parameter of the helper is typed as string, the first argument will be converted from the 10 integer to "10", and the helper outputs:

10 VALJEAN

Note that this kind of automatic conversion is done with bool type too, thanks to the IsTrue() function.

Options Argument

If a helper needs the Options argument, just add it at the end of helper parameters:

raymond.RegisterHelper("add", func(val1, val2 int, options *raymond.Options) string {
    return strconv.Itoa(val1 + val2) + " " + options.ValueStr("bananas")
})

Thanks to the options argument, helpers have access to the current evaluation context, to the Hash arguments, and they can manipulate the private data variables.

The Options argument is even necessary for Block Helpers to evaluate block and "else block".

Context Values

Helpers fetch current context values with options.Value() and options.ValuesStr().

Value() returns an interface{} and lets the helper do the type assertions whereas ValueStr() automatically converts the value to a string.

For example:

source := `{{concat a b}}`

ctx := map[string]interface{}{
    "a":      "Marcel",
    "b":      "Beliveau",
    "suffix": "FOREVER !",
}

raymond.RegisterHelper("concat", func(val1, val2 string, options *raymond.Options) string {
    return val1 + " " + val2 + " " + options.ValueStr("suffix")
})

Outputs:

Marcel Beliveau FOREVER !

Helpers can get the entire current context with options.Ctx() that returns an interface{}.

Helper Hash Arguments

Helpers access hash arguments with options.HashProp() and options.HashStr().

HashProp() returns an interface{} and lets the helper do the type assertions whereas HashStr() automatically converts the value to a string.

For example:

source := `{{concat suffix first=a second=b}}`

ctx := map[string]interface{}{
    "a":      "Marcel",
    "b":      "Beliveau",
    "suffix": "FOREVER !",
}

raymond.RegisterHelper("concat", func(suffix string, options *raymond.Options) string {
    return options.HashStr("first") + " " + options.HashStr("second") + " " + suffix
})

Outputs:

Marcel Beliveau FOREVER !

Helpers can get the full hash with options.Hash() that returns a map[string]interface{}.

Private Data

Helpers access private data variables with options.Data() and options.DataStr().

Data() returns an interface{} and lets the helper do the type assertions whereas DataStr() automatically converts the value to a string.

Helpers can get the entire current data frame with options.DataFrame() that returns a *DataFrame.

For helpers that need to inject their own private data frame, use options.NewDataFrame() to create the frame and options.FnData() to evaluate the block with that frame.

For example:

source := `{{#voodoo kind=a}}Voodoo is {{@magix}}{{/voodoo}}`

ctx := map[string]interface{}{
    "a": "awesome",
}

raymond.RegisterHelper("voodoo", func(options *raymond.Options) string {
    // create data frame with @magix data
    frame := options.NewDataFrame()
    frame.Set("magix", options.HashProp("kind"))

    // evaluates block with new data frame
    return options.FnData(frame)
})

Helpers that need to evaluate the block with a private data frame and a new context can call options.FnCtxData().

Utilites

In addition to Escape(), raymond provides utility functions that can be usefull for helpers.

Str()

Str() converts its parameter to a string.

Booleans:

raymond.Str(3) + " foos and " + raymond.Str(-1.25) + " bars"
// Outputs: "3 foos and -1.25 bars"

Numbers:

"everything is " + raymond.Str(true) + " and nothing is " + raymond.Str(false)
// Outputs: "everything is true and nothing is false"

Maps:

raymond.Str(map[string]string{"foo": "bar"})
// Outputs: "map[foo:bar]"

Arrays and Slices:

raymond.Str([]interface{}{true, 10, "foo", 5, "bar"})
// Outputs: "true10foo5bar"

IsTrue()

IsTrue() returns the truthy version of its parameter.

It returns false when parameter is either:

  • an empty array
  • an empty slice
  • an empty map
  • ""
  • nil
  • 0
  • false

For all others values, IsTrue() returns true.

Context Functions

In addition to helpers, lambdas found in context are evaluated.

For example, that template and context:

source := "I {{feeling}} you"

ctx := map[string]interface{}{
    "feeling": func() string {
        rand.Seed(time.Now().UTC().UnixNano())

        feelings := []string{"hate", "love"}
        return feelings[rand.Intn(len(feelings))]
    },
}

Randomly renders I hate you or I love you.

Those context functions behave like helper functions: they can be called with parameters and they can have an Options argument.

Partials

Template Partials

You can register template partials before execution:

tpl := raymond.MustParse("{{> foo}} baz")
tpl.RegisterPartial("foo", "<span>bar</span>")

result := tpl.MustExec(nil)
fmt.Print(result)

Output:

<span>bar</span> baz

You can register several partials at once:

tpl := raymond.MustParse("{{> foo}} and {{> baz}}")
tpl.RegisterPartials(map[string]string{
    "foo": "<span>bar</span>",
    "baz": "<span>bat</span>",
})

result := tpl.MustExec(nil)
fmt.Print(result)

Output:

<span>bar</span> and <span>bat</span>

Global Partials

You can registers global partials that will be accessible by all templates:

raymond.RegisterPartial("foo", "<span>bar</span>")

tpl := raymond.MustParse("{{> foo}} baz")
result := tpl.MustExec(nil)
fmt.Print(result)

Or:

raymond.RegisterPartials(map[string]string{
    "foo": "<span>bar</span>",
    "baz": "<span>bat</span>",
})

tpl := raymond.MustParse("{{> foo}} and {{> baz}}")
result := tpl.MustExec(nil)
fmt.Print(result)

Dynamic Partials

It's possible to dynamically select the partial to be executed by using sub expression syntax.

For example, that template randomly evaluates the foo or baz partial:

tpl := raymond.MustParse("{{> (whichPartial) }}")
tpl.RegisterPartials(map[string]string{
    "foo": "<span>bar</span>",
    "baz": "<span>bat</span>",
})

ctx := map[string]interface{}{
    "whichPartial": func() string {
        rand.Seed(time.Now().UTC().UnixNano())

        names := []string{"foo", "baz"}
        return names[rand.Intn(len(names))]
    },
}

result := tpl.MustExec(ctx)
fmt.Print(result)

Partial Contexts

It's possible to execute partials on a custom context by passing in the context to the partial call.

For example:

tpl := raymond.MustParse("User: {{> userDetails user }}")
tpl.RegisterPartial("userDetails", "{{firstname}} {{lastname}}")

ctx := map[string]interface{}{
    "user": map[string]string{
        "firstname": "Jean",
        "lastname":  "Valjean",
    },
}

result := tpl.MustExec(ctx)
fmt.Print(result)

Displays:

User: Jean Valjean

Partial Parameters

Custom data can be passed to partials through hash parameters.

For example:

tpl := raymond.MustParse("{{> myPartial name=hero }}")
tpl.RegisterPartial("myPartial", "My hero is {{name}}")

ctx := map[string]interface{}{
    "hero": "Goldorak",
}

result := tpl.MustExec(ctx)
fmt.Print(result)

Displays:

My hero is Goldorak

Utility Functions

You can use following utility fuctions to parse and register partials from files:

  • ParseFile() - reads a file and return parsed template
  • Template.RegisterPartialFile() - reads a file and registers its content as a partial with given name
  • Template.RegisterPartialFiles() - reads several files and registers them as partials, the filename base is used as the partial name

Mustache

Handlebars is a superset of mustache but it differs on those points:

  • Alternative delimiters are not supported
  • There is no recursive lookup

Limitations

These handlebars options are currently NOT implemented:

  • compat - enables recursive field lookup
  • knownHelpers - list of helpers that are known to exist (truthy) at template execution time
  • knownHelpersOnly - allows further optimizations based on the known helpers list
  • trackIds - include the id names used to resolve parameters for helpers
  • noEscape - disables HTML escaping globally
  • strict - templates will throw rather than silently ignore missing fields
  • assumeObjects - removes object existence checks when traversing paths
  • preventIndent - disables the auto-indententation of nested partials
  • stringParams - resolves a parameter to it's name if the value isn't present in the context stack

These handlebars features are currently NOT implemented:

  • raw block content is not passed as a parameter to helper
  • blockHelperMissing - helper called when a helper can not be directly resolved
  • helperMissing - helper called when a potential helper expression was not found
  • @contextPath - value set in trackIds mode that records the lookup path for the current context
  • @level - log level

Handlebars Lexer

You should not use the lexer directly, but for your information here is an example:

package main

import (
    "fmt"

    "github.com/aymerick/raymond/lexer"
)

func main() {
    source := "You know {{nothing}} John Snow"

    output := ""

    lex := lexer.Scan(source)
    for {
        // consume next token
        token := lex.NextToken()

        output += fmt.Sprintf(" %s", token)

        // stops when all tokens have been consumed, or on error
        if token.Kind == lexer.TokenEOF || token.Kind == lexer.TokenError {
            break
        }
    }

    fmt.Print(output)
}

Outputs:

Content{"You know "} Open{"{{"} ID{"nothing"} Close{"}}"} Content{" John Snow"} EOF

Handlebars Parser

You should not use the parser directly, but for your information here is an example:

package main

import (
    "fmt"

    "github.com/aymerick/raymond/ast"
    "github.com/aymerick/raymond/parser"
)

fu  nc main() {
    source := "You know {{nothing}} John Snow"

    // parse template
    program, err := parser.Parse(source)
    if err != nil {
        panic(err)
    }

    // print AST
    output := ast.Print(program)

    fmt.Print(output)
}

Outputs:

CONTENT[ 'You know ' ]
{{ PATH:nothing [] }}
CONTENT[ ' John Snow' ]

Test

First, fetch mustache tests:

$ git submodule update --init

To run all tests:

$ go test ./...

To filter tests:

$ go test -run="Partials"

To run all test and all benchmarks:

$ go test -bench . ./...

To test with race detection:

$ go test -race ./...

References

Others Implementations

Issues
  •  Do not transferred the context in RegisterHelper in function rendering

    Do not transferred the context in RegisterHelper in function rendering

    Hello, I need to recursively load templates, but the context is not rendered

    package main
    
    import (
        "fmt"
    
        "github.com/aymerick/raymond"
    )
    
    type Author struct {
        FirstName string `json:"firstName"`
        LastName  string `json:"lastName"`
    }
    
    func main() {
        raymond.RegisterHelper("template", func(name string, options *raymond.Options) raymond.SafeString {
            context := options.Ctx()
            // Load template from file
            template := name + " - {{ firstName }} {{ lastName }}"
    
            result, _ := raymond.Render(template, context)
            return raymond.SafeString(result)
        })
    
        template := `By {{ template "namefile" }}`
        context := Author{"Alan", "Johnson"}
    
        result, _ := raymond.Render(template, context)
    
        fmt.Println(result)
    }
    

    Expected:

    By namefile - Alan Johnson

    but got:

    By namefile -

    Same thing on JS https://jsfiddle.net/Ghost_Russia/dv8tjekp/

    opened by ghostiam 3
  • Adds struct tag template variable support

    Adds struct tag template variable support

    First off, really great job with this project. Our team has been using it for a couple months and it's been flawless.

    We have some shared templates that have such characters as dashes and dots in the middle of the template variable names. For example:

    foo.bar-baz
    foo.[bar.baz]
    

    This is along the same lines of segment-literal notation that is denoted under expressions. To support this behavior, this work adds the ability to specify a template variable name defined in a struct tag. Here's how a struct would look:

    type Data struct {
      Name string `handlebars:"something-other-than-name"`
    }
    

    The corresponding template would look like:

    <p>{{something-other-than-name}}</p>
    

    This also extends to using a different name for the template variable, similar to JSON and various database implementations, if warranted.

    A few things to note:

    • It is currently using a struct tag called handlebars, but I'd be fine going with something more specific (i.e. raymond.name)
    • The current implementation will default to the field name in the struct. If the name of the field doesn't match in the template, this acts as a fallback. I would be fine with switching the order of this logic.

    Let me know if you have any other suggestions or questions regarding this work.

    opened by geoffberger 2
  • raymond isn't thread safe

    raymond isn't thread safe

    If you run the tests with the -race flag you'll find that raymond does not deal with it very well:

    $ go test -race ./...
    

    Produces the following output

    ==================
    WARNING: DATA RACE
    Write by goroutine 19:
      github.com/aymerick/raymond/lexer.(*Lexer).next()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/lexer/lexer.go:160 +0x14f
      github.com/aymerick/raymond/lexer.lexContent()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/lexer/lexer.go:315 +0x160
      github.com/aymerick/raymond/lexer.(*Lexer).run()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/lexer/lexer.go:147 +0xa1
    
    Previous read by goroutine 18:
      github.com/aymerick/raymond/parser.(*parser).parseProgram()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/parser/parser.go:107 +0x5e
      github.com/aymerick/raymond/parser.Parse()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/parser/parser.go:54 +0xa4
      github.com/aymerick/raymond.(*Template).parse()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/template.go:70 +0x86
      github.com/aymerick/raymond.Parse()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/template.go:37 +0x1a9
      github.com/aymerick/raymond.Render()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/raymond.go:9 +0x60
      github.com/aymerick/raymond.TestHelperCtx()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/helper_test.go:189 +0x13b
      testing.tRunner()
          /usr/local/go/src/testing/testing.go:473 +0xdc
    
    Goroutine 19 (running) created at:
      github.com/aymerick/raymond/lexer.scanWithName()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/lexer/lexer.go:103 +0x166
      github.com/aymerick/raymond/lexer.Scan()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/lexer/lexer.go:89 +0x44
      github.com/aymerick/raymond/parser.new()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/parser/parser.go:42 +0x3c
      github.com/aymerick/raymond/parser.Parse()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/parser/parser.go:51 +0x91
      github.com/aymerick/raymond.(*Template).parse()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/template.go:70 +0x86
      github.com/aymerick/raymond.Parse()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/template.go:37 +0x1a9
      github.com/aymerick/raymond.Render()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/raymond.go:9 +0x60
      github.com/aymerick/raymond.TestHelperCtx()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/helper_test.go:189 +0x13b
      testing.tRunner()
          /usr/local/go/src/testing/testing.go:473 +0xdc
    
    Goroutine 18 (running) created at:
      testing.RunTests()
          /usr/local/go/src/testing/testing.go:582 +0xae2
      testing.(*M).Run()
          /usr/local/go/src/testing/testing.go:515 +0x11d
      main.main()
          github.com/aymerick/raymond/_test/_testmain.go:132 +0x210
    ==================
    ==================
    WARNING: DATA RACE
    Write by goroutine 20:
      github.com/aymerick/raymond/lexer.(*Lexer).next()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/lexer/lexer.go:160 +0x14f
      github.com/aymerick/raymond/lexer.lexContent()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/lexer/lexer.go:315 +0x160
      github.com/aymerick/raymond/lexer.(*Lexer).run()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/lexer/lexer.go:147 +0xa1
    
    Previous read by goroutine 18:
      github.com/aymerick/raymond/parser.(*parser).parseProgram()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/parser/parser.go:107 +0x5e
      github.com/aymerick/raymond/parser.Parse()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/parser/parser.go:54 +0xa4
      github.com/aymerick/raymond.(*Template).parse()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/template.go:70 +0x86
      github.com/aymerick/raymond.Parse()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/template.go:37 +0x1a9
      github.com/aymerick/raymond.Render()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/raymond.go:9 +0x60
      github.com/aymerick/raymond.TestHelperCtx.func1()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/helper_test.go:181 +0xca
      runtime.call64()
          /usr/local/go/src/runtime/asm_amd64.s:473 +0x3d
      reflect.Value.Call()
          /usr/local/go/src/reflect/value.go:303 +0xcd
      github.com/aymerick/raymond.(*evalVisitor).callFunc()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/eval.go:621 +0xd6b
      github.com/aymerick/raymond.(*evalVisitor).callHelper()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/eval.go:628 +0xbb
      github.com/aymerick/raymond.(*evalVisitor).VisitExpression()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/eval.go:895 +0xa81
      github.com/aymerick/raymond/ast.(*Expression).Accept()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/ast/node.go:400 +0x4a
      github.com/aymerick/raymond.(*evalVisitor).VisitMustache()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/eval.go:777 +0x139
      github.com/aymerick/raymond/ast.(*MustacheStatement).Accept()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/ast/node.go:221 +0x4a
      github.com/aymerick/raymond.(*evalVisitor).VisitProgram()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/eval.go:762 +0x206
      github.com/aymerick/raymond/ast.(*Program).Accept()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/ast/node.go:181 +0x4a
      github.com/aymerick/raymond.(*Template).ExecWith()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/template.go:220 +0x176
      github.com/aymerick/raymond.(*Template).Exec()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/template.go:194 +0x77
      github.com/aymerick/raymond.Render()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/raymond.go:15 +0xc8
      github.com/aymerick/raymond.TestHelperCtx()
          /Users/markbates/Dropbox/development/gocode/src/github.com/aymerick/raymond/helper_test.go:189 +0x13b
      testing.tRunner()
          /usr/local/go/src/testing/testing.go:473 +0xdc
    
    ...
    
    Goroutine 8 (running) created at:
      testing.RunTests()
          /usr/local/go/src/testing/testing.go:582 +0xae2
      testing.(*M).Run()
          /usr/local/go/src/testing/testing.go:515 +0x11d
      main.main()
          github.com/aymerick/raymond/parser/_test/_testmain.go:56 +0x210
    ==================
    PASS
    Found 50 data race(s)
    FAIL    github.com/aymerick/raymond/parser  1.086s
    
    opened by markbates 2
  • RegisterHelper and RegisterHelpers do not panic

    RegisterHelper and RegisterHelpers do not panic

    Hi,

    We were having problems in our test code when we were registering a helper more than once. It feels more idiomatic to return an error than panic.

    Thanks

    opened by Lexcenture 1
  • Added methods to remove global partials

    Added methods to remove global partials

    First off, thank you very much for this great library!

    I needed the ability to remove registered partials, since I'm live-reloading them once they are edited on disk. This is needed for productive template development IMO.

    opened by kabukky 1
  • added the ability to remove a registered helper

    added the ability to remove a registered helper

    Because an error is raised if a person tries to register a helper with the same name, it means there is no way to redefined a helper. This PR adds the ability to remove an already registered helper, clearing the way for someone to register a new helper with the same name.

    opened by markbates 1
  • Block helpers as HTML attributes

    Block helpers as HTML attributes

    I’ve run into a bug where the parser throws an error rendering any other helper than {{value}} as HTML attributes:

    Parse error on line 7:\nExpecting Close, got: 'Equals{\"=\"}'
    

    Minimal Example

    <h1 {{add 1 2}}>
    

    Actual use case

    <option value="test" {{equals value "test"}}selected{{/equals}}>Test</option>
    
    template.RegisterHelper("equals", func(variable string, comparator string, options *raymond.Options) raymond.SafeString {
        if variable == comparator {
            return raymond.SafeString(options.Fn())
        } else {
            return raymond.SafeString("")
        }
    })
    

    Using helpers for rendering HTML attributes seems to be supported by handlebars, e.g. http://stackoverflow.com/questions/9917705/setting-the-selected-value-of-a-select-element-in-handlebars.

    opened by maxhoffmann 1
  • Go get problems with submodule mustache

    Go get problems with submodule mustache

    Hello @aymerick , I recently add built'n support for your template engine into Iris and I have some reports from its users, take a look here.

    This is the error from go get (as they said):

    # cd /Users/ehlertm/wt/go/src/github.com/aymerick/raymond; git submodule update --init --recursive
    Cloning into '/Users/ehlertm/wt/go/src/github.com/aymerick/raymond/mustache'...
    fatal: unable to connect to github.com:
    github.com[0: 192.30.253.113]: errno=Operation timed out
    
    fatal: clone of 'git://github.com/mustache/spec.git' into submodule path '/Users/ehlertm/wt/go/src/github.com/aymerick/raymond/mustache' failed
    package github.com/aymerick/raymond: exit status 128
    
    

    I personally, installed this today and I hadn't this issue, but I think it will be better to remove the mustache submodule from this repository, you don't need it as submodule ( I think)

    opened by ghost 1
  • PIP-1385: Add pluralize helper

    PIP-1385: Add pluralize helper

    Purpose

    This adds a pluralize helper. The pluralize helper is a short hand way of determining which value to use based on whether the value is greater than one or equal to or less than one. This could be used instead of an ifGt else block.

    Example

    {{pluralize error_count "errors" "error"}}
    

    https://mailgun.atlassian.net/browse/PIP-1385

    opened by Takumi2008 0
  • fix imports and setup go mod

    fix imports and setup go mod

    Purpose

    Fixing imports to point to the forked repo and configure go mod. This forked library is needed to add additional handlebar features for mailgun/temple as the original repo is no longer being maintained.

    https://mailgun.atlassian.net/browse/PIP-1310

    Reference: https://github.com/aymerick/raymond/issues/40

    opened by Takumi2008 0
  • UnregisterHelper

    UnregisterHelper

    We will be using this library in production, but don't want our end users to have the ability to print to our logs. This adds the UnregisterHelper function so we can unregister the "log" helper.

    opened by jimmyjames85 0
  • ReplacePartial

    ReplacePartial

    I need to replace a partial before Exec (usecase: in dev, want to reload a partial when I edit it)

    raw := `{{>partial}}`
    tpl, _ := raymond.Parse(raw)
    
    tpl.RegisterPartial("partial", "Bloubi")
    tpl.MustExec(tpl) // "Bloubi"
    
    tpl.ReplacePartial("partial", "Boulga")
    tpl.MustExec(tpl) // "Boulga"
    

    This is the code for this :)

    opened by dav-m85 0
  • question about interpolating inside of braces

    question about interpolating inside of braces

    how might i render a line like this?

    /resource/{{{Resource}}ID}

    "Resource" is the variable name so pretend its value is "pet". i'm trying to have it render to:

    /resource/{petID}

    However, it panics with: Expecting CloseUnescaped, got: 'Close{"}}"}'

    opened by phamdt 0
  • with helper not working when `this` refers to a struct

    with helper not working when `this` refers to a struct

    html:

    {{#with (lookup mymap 'key')}}
    <p>0: {{this}}</p>
    <p>1: {{ID}}</p>
    <p>2: {{this.ID}}</p>
    {{/with}}
    

    context:

    type MyStruct struct {
    	ID string `json:"id"`
    }
    ctx := map[string]interface{}{
    	"mymap": map[string]MyStruct{
    		"key": MyStruct{
    			ID: "42",
    		},
    	},
    }
    

    expected result:

    <p>0: {42}</p>
    <p>1: 42</p>
    <p>2: 42</p>
    

    actual result:

    <p>0: {42}</p>
    <p>1: </p>
    <p>2: </p>
    
    opened by nektro 0
  • Nil reference in `if` statement panics

    Nil reference in `if` statement panics

    Outside of an if statement raymond will silently ignore invalid references within a template. Within the condition of an if, raymond will panic on an invalid reference:

    Example:

    package main
    
    import (
    	"fmt"
    
    	"github.com/aymerick/raymond"
    )
    
    func main() {
    	tpl := `{{#if wat[0].fnord}}wat{{/if}}`
    
    	ctx := map[string]string{}
    
    	result, err := raymond.Render(tpl, ctx)
    	if err != nil {
    		panic("Please report a bug :)")
    	}
    
    	fmt.Print(result)
    }
    

    Results in:

    panic: runtime error: invalid memory address or nil pointer dereference [recovered]                                                    
            panic: runtime error: invalid memory address or nil pointer dereference                                                        
    [signal SIGSEGV: segmentation violation code=0x1 addr=0x20 pc=0x1103ed6]                                                               
                                                                                                                                           
    goroutine 1 [running]:                                                                                                                 
    github.com/aymerick/raymond.errRecover(0xc00009fee0)                                                                                   
            /Users/swood/work/src/github.com/aymerick/raymond/template.go:232 +0xf5                                                        
    panic(0x112e360, 0x1258f40)                                                                                                            
            /usr/local/Cellar/go/1.13.4/libexec/src/runtime/panic.go:679 +0x1b2                           
    github.com/aymerick/raymond.(*Options).HashProp(...)                                                                                   
            /Users/swood/work/src/github.com/aymerick/raymond/helper.go:145                                                                
    github.com/aymerick/raymond.(*Options).isIncludableZero(...)                                                                           
            /Users/swood/work/src/github.com/aymerick/raymond/helper.go:281                                                                
    github.com/aymerick/raymond.ifHelper(0x0, 0x0, 0x0, 0x0, 0x0)                                                                          
            /Users/swood/work/src/github.com/aymerick/raymond/helper.go:298 +0x26                                                          
    reflect.Value.call(0x112a860, 0x115d508, 0x13, 0x11549b2, 0x4, 0xc0000672f0, 0x2, 0x2, 0xc00009f970, 0xc00009f970, ...)
            /usr/local/Cellar/go/1.13.4/libexec/src/reflect/value.go:460 +0x5f6                              
    reflect.Value.Call(0x112a860, 0x115d508, 0x13, 0xc0000672f0, 0x2, 0x2, 0x3, 0x1159af0, 0x203000)
            /usr/local/Cellar/go/1.13.4/libexec/src/reflect/value.go:321 +0xb4                                                             
    github.com/aymerick/raymond.(*evalVisitor).callFunc(0xc0000c6000, 0x1159aea, 0x2, 0x112a860, 0x115d508, 0x13, 0xc0000672c0, 0x115d508, 0x13, 0x0)
            /Users/swood/work/src/github.com/aymerick/raymond/eval.go:642 +0x46e
    github.com/aymerick/raymond.(*evalVisitor).callHelper(0xc0000c6000, 0x1159aea, 0x2, 0x112a860, 0x115d508, 0x13, 0xc00008eb90, 0x1, 0x0) 
            /Users/swood/work/src/github.com/aymerick/raymond/eval.go:649 +0x8c                                                            
    github.com/aymerick/raymond.(*evalVisitor).VisitExpression(0xc0000c6000, 0xc00008eb90, 0x0, 0x0)                                       
            /Users/swood/work/src/github.com/aymerick/raymond/eval.go:916 +0x32d                                                           
    github.com/aymerick/raymond/ast.(*Expression).Accept(...)                                                                              
            /Users/swood/work/src/github.com/aymerick/raymond/ast/node.go:400                                                              
    github.com/aymerick/raymond.(*evalVisitor).VisitBlock(0xc0000c6000, 0xc00008eb40, 0xc00008a2b5, 0x1260f20)
            /Users/swood/work/src/github.com/aymerick/raymond/eval.go:822 +0xb0                                                            
    github.com/aymerick/raymond/ast.(*BlockStatement).Accept(0xc00008eb40, 0x1182480, 0xc0000c6000, 0x1148dc0, 0xc00009fdc8)
            /Users/swood/work/src/github.com/aymerick/raymond/ast/node.go:259 +0x3b                                                        
    github.com/aymerick/raymond.(*evalVisitor).VisitProgram(0xc0000c6000, 0xc000086f00, 0xc000066f90, 0x0)
            /Users/swood/work/src/github.com/aymerick/raymond/eval.go:783 +0x176                                                           
    github.com/aymerick/raymond/ast.(*Program).Accept(...)                                                                                 
            /Users/swood/work/src/github.com/aymerick/raymond/ast/node.go:181                                                              
    github.com/aymerick/raymond.(*Template).ExecWith(0xc000088500, 0x112b8e0, 0xc000066f90, 0x0, 0x0, 0x0, 0x0, 0x0)                       
            /Users/swood/work/src/github.com/aymerick/raymond/template.go:220 +0xf3                                                        
    github.com/aymerick/raymond.(*Template).Exec(...)                                                                                      
            /Users/swood/work/src/github.com/aymerick/raymond/template.go:194
    github.com/aymerick/raymond.Render(0x1159ae7, 0x1e, 0x112b8e0, 0xc000066f90, 0xc000062058, 0x0, 0x0, 0x0)                              
            /Users/swood/work/src/github.com/aymerick/raymond/raymond.go:15 +0x78                                                          
    main.main()                                                                                                                            
            /tmp/raymondpanic/main.go:14 +0x54                                               
    
    opened by sam-at-luther 0
Releases(v2.0.2)
  • v2.0.2(Mar 22, 2018)

  • v2.0.1(Jun 1, 2016)

  • v2.0.0(May 1, 2016)

    Changelog

    • [BUGFIX] Fixes passing of context in helper options #2 - Thanks @GhostRussia
    • [BREAKING] Renames and unexports constants:
      • handlebars.DUMP_TPL
      • lexer.ESCAPED_ESCAPED_OPEN_MUSTACHE
      • lexer.ESCAPED_OPEN_MUSTACHE
      • lexer.OPEN_MUSTACHE
      • lexer.CLOSE_MUSTACHE
      • lexer.CLOSE_STRIP_MUSTACHE
      • lexer.CLOSE_UNESCAPED_STRIP_MUSTACHE
      • lexer.DUMP_TOKEN_POS
      • lexer.DUMP_ALL_TOKENS_VAL
    Source code(tar.gz)
    Source code(zip)
  • v1.1.0(Jun 15, 2015)

    Changelog

    • Adds ParseFile() function.
    • Adds RegisterPartialFile(), RegisterPartialFiles() and Clone() methods on Template.
    • Helpers can now be struct methods.
    • Permits templates references with lowercase versions of struct fields.
    • Ensures safe concurrent access to helpers and partials.
    Source code(tar.gz)
    Source code(zip)
  • v1.0.0(Jun 9, 2015)

    Changelog

    • This is the first release. Raymond supports almost all handlebars features. See https://github.com/aymerick/raymond#limitations for a list of differences with the javascript implementation.
    Source code(tar.gz)
    Source code(zip)
Owner
Aymerick
Senior Architect @fairjungle
Aymerick
[Crawler/Scraper for Golang]🕷A lightweight distributed friendly Golang crawler framework.一个轻量的分布式友好的 Golang 爬虫框架。

Goribot 一个分布式友好的轻量的 Golang 爬虫框架。 完整文档 | Document !! Warning !! Goribot 已经被迁移到 Gospider|github.com/zhshch2002/gospider。修复了一些调度问题并分离了网络请求部分到另一个仓库。此仓库会继续

null 207 Jun 15, 2022
golang 在线预览word,excel,pdf,MarkDown(Online Preview Word,Excel,PPT,PDF,Image by Golang)

Go View File 在线体验地址 http://39.97.98.75:8082/view/upload (不会经常更新,保留最基本的预览功能。服务器配置较低,如果出现链接超时请等待几秒刷新重试,或者换Chrome) 目前已经完成 docker部署 (不用为运行环境烦恼) Wor

CZC 59 Jun 14, 2022
bluemonday: a fast golang HTML sanitizer (inspired by the OWASP Java HTML Sanitizer) to scrub user generated content of XSS

bluemonday bluemonday is a HTML sanitizer implemented in Go. It is fast and highly configurable. bluemonday takes untrusted user generated content as

Microcosm 2.3k Jun 24, 2022
Elegant Scraper and Crawler Framework for Golang

Colly Lightning Fast and Elegant Scraping Framework for Gophers Colly provides a clean interface to write any kind of crawler/scraper/spider. With Col

Colly 16.9k Jun 30, 2022
A golang package to work with Decentralized Identifiers (DIDs)

did did is a Go package that provides tools to work with Decentralized Identifiers (DIDs). Install go get github.com/ockam-network/did Example packag

Ockam 60 May 29, 2022
wcwidth for golang

go-runewidth Provides functions to get fixed width of the character or string. Usage runewidth.StringWidth("つのだ☆HIRO") == 12 Author Yasuhiro Matsumoto

mattn 460 Jun 26, 2022
Parses the Graphviz DOT language in golang

Parses the Graphviz DOT language and creates an interface, in golang, with which to easily create new and manipulate existing graphs which can be writ

Walter Schulze 484 Jun 25, 2022
Go (Golang) GNU gettext utilities package

Gotext GNU gettext utilities for Go. Features Implements GNU gettext support in native Go. Complete support for PO files including: Support for multil

Leonel Quinteros 342 Jun 29, 2022
htmlquery is golang XPath package for HTML query.

htmlquery Overview htmlquery is an XPath query package for HTML, lets you extract data or evaluate from HTML documents by an XPath expression. htmlque

null 491 Jun 24, 2022
omniparser: a native Golang ETL streaming parser and transform library for CSV, JSON, XML, EDI, text, etc.

omniparser Omniparser is a native Golang ETL parser that ingests input data of various formats (CSV, txt, fixed length/width, XML, EDI/X12/EDIFACT, JS

JF Technology 467 Jul 3, 2022
Pagser is a simple, extensible, configurable parse and deserialize html page to struct based on goquery and struct tags for golang crawler

Pagser Pagser inspired by page parser。 Pagser is a simple, extensible, configurable parse and deserialize html page to struct based on goquery and str

foolin 62 Jun 13, 2022
iTunes and RSS 2.0 Podcast Generator in Golang

podcast Package podcast generates a fully compliant iTunes and RSS 2.0 podcast feed for GoLang using a simple API. Full documentation with detailed ex

Eric Duncan 109 May 31, 2022
TOML parser for Golang with reflection.

THIS PROJECT IS UNMAINTAINED The last commit to this repo before writing this message occurred over two years ago. While it was never my intention to

Andrew Gallant 3.9k Jun 26, 2022
Your CSV pocket-knife (golang)

csvutil - Your CSV pocket-knife (golang) #WARNING I would advise against using this package. It was a language learning exercise from a time before "e

Bryan Matsuo 45 Feb 7, 2022
Golang (Go) bindings for GNU's gettext (http://www.gnu.org/software/gettext/)

gosexy/gettext Go bindings for GNU gettext, an internationalization and localization library for writing multilingual systems. Requeriments GNU gettex

Go toolbelt 61 Jun 6, 2022
agrep-like fuzzy matching, but made faster using Golang and precomputation.

goagrep There are situations where you want to take the user's input and match a primary key in a database. But, immediately a problem is introduced:

Zack 41 Nov 30, 2021
Ngram index for golang

go-ngram N-gram index for Go. Key features Unicode support. Append only. Data can't be deleted from index. GC friendly (all strings are pooled and com

Eugene Lazin 102 May 21, 2022
String-matching in Golang using the Knuth–Morris–Pratt algorithm (KMP)

gokmp String-matching in Golang using the Knuth–Morris–Pratt algorithm (KMP). Disclaimer This library was written as part of my Master's Thesis and sh

Patrick-Ranjit D. Madsen 38 Dec 16, 2021
Golang HTML to plaintext conversion library

html2text Converts HTML into text of the markdown-flavored variety Introduction Ensure your emails are readable by all! Turns HTML into raw text, usef

J. Elliot Taylor 408 Jun 18, 2022