gowalker

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 9, 2022 License: MIT Imports: 8 Imported by: 0

README

GoWalker

Status: CircleCI

GoWalker is two things:

A simple path expression interpreter

A very simple data expression interpreter. Given a data structure like this:

{
  "name": "Joe",
  "age": 22,
  "friends": [
    {
      "name": "billy",
      "age": 27
    } ,
    {
      "name": "john",
      "age": 23
    }
  ],
  "items": [
    "keys",
    "wallet"
  ]
}

You can use the Walk function to easily navigate the data structure as in:

Walk("name",data,nil)               // returns `Joe`
Walk("items[1]",data,nil)           // returns `wallet`
Walk("friends[0].name",data,nil)    // returns `billy`
Walk("items",data,nil)              // returns ["keys","wallet"]

The library uses no code evaluations therefore it's super safe.

Expressions

Expressions are actually pretty easy. A few notes:

  • the path separator through maps is the . (dot). No square-bracket notation is supported or required
  • The index expression in arrays uses the square-bracket notation
  • the . (dot) alone in an expression refers to the whole scope
Functions

Expressions also support the use of functions. From the expression parser standpoint, assertions work as follows:

  • at any point of the expression you can invoke a function
  • the function call must be the last segment in an expression
  • function chaining is not supported yet
  • functions can be reflective and can only operate on the piece of data they've been called upon
  • functions can receive comma separated parameters. Quotation is not required as data typing will be handled by the function implementation
  • running a function without a preceding expression will make the function operate on the full scope

Examples:

foo.bar.size()

Will evaluate the size of bar.

foo.myString.split(|)

Will split myString using pipe as separator.

foo.myArray.collect(banana,mango)

Where myArray is an array of objects, it will collect all the fields named banana and mango.

Implementing functions

The engine comes with just a few of default functions for demonstration purposes, such as:

  • size(): returns the size of the object in scope
  • split(sep): splits the string in scope, using a separator
  • collect(...): given an array containing maps, it will return an array of maps in which the maps only show the provided keys

You can implement more by passing the functions parameter when invoking Walk. Example:

Assuming you have a data structure as follows:

{
  "items": [
    "foo",
    "bar"
  ]
}
functions := NewFunctions()
functions.Add("sayHello",func (scope any, params ...string) (any, error) {
	if len(params) < 1 {
		return nil,errors.New("not enough parameters")
    }
	if data,ok := scope.(string); ok {
        return "hello "data+" from "+params[0]	
    } else {
        return nil, errors.New("cannot run sayHello against a data type that is not string")
    }
})
//...
Walk("items[0].sayHello(Barney)",data,functions)

will return:

hello foo from Barney

A simple template engine

Powered by the same path expression interpreter, this tiny template engine allows you to substitute strings with data coming from a map. As in:

{
  "name": "${name}",
  "first_item": "${items[0]}",
  "all_items": ${items}
}

When a complex object is referenced in an expression, the rendering engine will automatically convert it to its JSON counterpart.

Just call:

data := map[string]any{"name": "pino", "items": []any{"keys", "wallet"}}
templ := `{
    "name": "${name}",
    "first_item": "${items[0]}",
    "all_items": ${items}
}`
res, _ := Render(templ, data, nil)

and you're set. You can, of course, pass a Functions instance as third parameter.

Sub-templates

Sometimes you need to split your templates into multiple files. There are typically two scenarios when this is recommended in GoWalker:

  • When you want to share a sub-template across multiple master templates
  • When you need to run a template against each item in an array

Here's an example of simple template splitting. It uses the render function against items

t1 := "this is a test ${items.render(t2)}"
t2 := "T2 ${.}"
templates := NewTemplates()
templates.Add("t2",t2)
res, _ := RenderAll(t1, templates, map[string]any{"items": []string{"foo", "bar"}}, NewFunctions())
// prints:
// `this is a test T2 ["foo","bar"]`
}
  • render(templateName): renders a sub-template against the variable it was run against

And here's an example where we iterate over an array. It uses the renderEach function against items:

t1 := "this is a test ${items.renderEach(t2,\\,)}"
t2 := "\nT2 ${.}"
templates := NewTemplates()
templates.Add("t2",t2)
res, _ := RenderAll(t1, templates, map[string]any{"items": []string{"foo", "bar"}}, NewFunctions())
// prints:
// this is a test
// T2 foo
// T2 bar
  • renderEach(templateName,sep?): renders a sub-template against each item of the array it was run against. Additionally, you can provide an optional separator string that will be printed between an iteration and the next

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Render

func Render(template string, data any, functions *Functions) (string, error)

Render renders a template, using the provided map as scope. Will return the rendered template or an error

func RenderAll

func RenderAll(template string, subTemplates SubTemplates, data map[string]any, functions *Functions) (string, error)

RenderAll will render the provided templates, making subTemplates available for complex rendering

func Walk

func Walk(expr string, data any, functions *Functions) (any, error)

Walk "walks" the provided data using the provided expression

Types

type Functions

type Functions struct {
	// contains filtered or unexported fields
}

Functions is a map of actual Golang functions the expression can call. When invoked, a function receives a variadic argument in which the first position is always the current selected element in the expression, while the following ones are provided as params. Functions will return a value and an error. mapOfFunctions is the actual map of key=function functionScope is an extra scope a function can access if part of Functions

func NewFunctions

func NewFunctions() *Functions

NewFunctions is the constructor of Functions and adds some very basic implementations

func (*Functions) Add

func (f *Functions) Add(key string, function func(data any, params ...string) (any, error)) *Functions

Add adds a function ot the Functions' data structure

type SubTemplates

type SubTemplates map[string]string

SubTemplates is a collection of templates

func NewSubTemplates

func NewSubTemplates() SubTemplates

NewSubTemplates is a constructor of SubTemplates

func (*SubTemplates) Add

func (s *SubTemplates) Add(name string, template string) SubTemplates

Add adds a template to the collection

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL