miru

package
v0.1.9 Latest Latest
Warning

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

Go to latest
Published: Jan 9, 2021 License: MIT Imports: 11 Imported by: 0

README

Miru

Miru takes the standard go template parser implementation adding more features with a different end result where templates are parsed into pure go based packages which can be rendered faster and more efficiently. This allows us bundle this templates directly into binaries among other benefits.

Why Miru

Miru was created to allow an optimized generated form of any html template which would allow users build web application using templates whilst still benefiting from a componentized view for their application.

We hope to use the benefit of a simplified html template with the speed that brings in regards setup, use and time compared to the time investment required by new web frameworks and libraries.

var dir = Miru.NewVDir("./testdata")
var indexFile, err = dir.GetFile("index.html")
if err != nil {
	log.Fatal("Failed: ", err)
}

var parsedData, parseErr = indexFile.Parse(Miru.DefaultOption, nil)
if parseErr != nil {
	log.Fatal("Failed: ", err)
}

// Format takes the name of the package to be used for generated file.
var parsedString, parsedStrErr = parsedData.Format("example")
if parsedStrErr != nil {
	log.Fatal("Failed: ", err)
}

// parsedString contains generated go content

Miru Template Language

Whilst Miru utilizes the native Go template package text/template, alot of changes were done to improve support for native html handling and other features added. Hence providing a clear and detailed view of these changes and how they are expected to work is required.

The described sections below are the new support capabilities which the user should be aware of as they use Miru.

Template Gotchas
  1. Miru provides limited internal functions even compared to go/template packages, hence only basic types are supported in the default functions that come with Miru templates. Developers can include custom helpers through Package Directives to include packages customly provided by the developer and used as functions in the templates.

  2. Miru generates a go file out of a template hence it's preset and defined and not customizable how that output looks like.

Package Directives

Miru adds the ability to include a external package into the template to provide package level variables, constants and functions. These allows a broader level of support and customization to developers.

{{ import plackers | github.com/influx6/groundlayer/pkg/plackers }}

Above is a package directive with declared alias plackers pointed at package path github.com/influx6/groundlayer/pkg/plackers.

Data and Types Directives

Miru adds the ability to define structs and types within the templates itself to allow a form of typesafety in the final generated code. This becomes important due to the naturally need to define template entry expectations.

Model Directive

Model directives is used to define a struct type within the templates and there can be many defined across files and in templates with the only constraint is to have unique names.

{{model Address | Zip:String, Street: string, Number:Int }}
{{model Product | Isin:String, Price: Float, Address:Address }}
ModelType Directive

ModelType directives allows defining a typed definition which is exactly as it sounds as in go. A custom type aliased to another type be it basic or defined type is used as the base.

In go code:

type Name string

is exactly the following in Miru:

{{modeltype productmapping |  string }}

Miru provides the Any key to represent the interface{} base type.

{{modelType ProductLog | Any }}

For List and Map types, there is specific rules unlike in go code, where we use (KeyType, ValueType) as the blocks to define the Key type and Value type.

{{modelType ProductMapping |  Map(string, Product) }}
{{modelType ListProductMapping |   List(ProductMapping) }}
Method Directives

Miru adds ability to define methods which specific purpose is to encapsulate specific UI views as methods which can be re-used across different parts of the template. They are similar to the define, but typed argument.

{{method renderSubProducts | product:Product, subProducts:ProductMapping }}
	<div class="product">
	{{if len subProducts}}
		<div class="item">
		<span class="price">No sub products available</span>
		</div>
	{{else}}
		<div class="item">
		<span class="price">Categories</span>
		</div>
	{{end}}
	</div>
{{endMethod}}
Component Directive

Component directive provides a clear means of defining possible components which are argument to the root template.

In essence, you tell the compiler to create a grouping which allows injecting a peji which will be mounted as desirable as part of the layout. This provides the benefit of clarity in how a layout expectations on its parts.

{{Komponent UserComponent }}
{{Komponent profileComponent }}

You can then mount these components within your template using the mount keyword with provided context:

{{ mount UserComponent . }}

The mount keyword cannot be used like other identifiers or functions, it's a special purpose identifier which does not work with pipes or any other composition directive. It exists to instruct generated code to expect said component and mount it with provided argument using the current scope (the current parent node) as root for the component.

Dom Identifiers

DOM identifiers exists to allow you to query for the name of the parent, root or previous dom parent from current, due to some need to pass this off to a function or to a custom function which appends these to it's parent.

Hence, there exists 3 identifiers to be used: dom, parentdom and rootdom.

Noop directive

The noop function call in Miru exists due to the fact that all function calls, fields, text, variables are automatically attached to the current node which is the parent of others (where the final is the root node if no html exists). This occurs because Miru is a compiled expression of a output to be rendered, hence unless it's a variable declaration, or assignment then it must lead to some result to be displayed.

Due to this, the noop function name exists which will indicate to the parser to not wrap the result in a helpers.AttachToNode function, as we may wish to have a function call or calls that have no output result.

Bad form:
{{ print .TotalLives | noop | printMore }}
Correct form:
{{ print .TotalLives | printMore | noop }}
Basic Template Elements

As already provided by the text/template support for texts tokens, assignment operations, variables, functions, range statements, if statements, and pipes are maintained.

Text blocks
{{ $name := "alex" }}

I was born on the day of {{ $name }}, but these needs to be counted as {{ $name | count }} to be certain of the name length.

Pipe blocks
{{  .Field3 | print }}
MultiPipe Block

*A special thing to note is when having multiple pipe blocks that each pipe block should be wrapped in ( and ), which if absent may not be properly handled in special cases.

Bad form:
{{ $x := "wonder" }}
{{ $x = (print .TotalLives "alex" $x | print) }}
Correct form:
{{ $x := "wonder" }}
{{ $x = (print .TotalLives "alex" $x) | print }}
Correct form:
{{ $x := "wonder" }}
{{ $x = ((print .TotalLives "alex" $x) | print) }}
Variable blocks
{{printf .Field1.Field2.Field3}}
{{(printf .Field1.Field3.Field6).Value}}
{{$x := (printf .Field1.Field2.Field3 .Field1.Field2).Value}}
{{$y := (printf $x.Field1.Field2.Field3).Value}}
{{$ui = $y }}
If blocks
Hello, {{if .Name}} {{.Name}} {{else}} there {{end}}!
{{if and .User .User.Admin}}
	You are an admin user!
{{else if or .User .User.Admin }}
	Access denied!
{{end}}
Range blocks
{{range .Items}}
	Hello, {{.Name}} 
{{else}}
	No items available
{{end}}
{{range $x, $y := .Items}}
	Hello, {{$y.Name}}  from {{ $x }} queue.
{{else}}
	No items available
{{end}}
With blocks
{{with contains .Items }}
	{{printf "%q" . | rewrap }}
{{else}}
	{{printf "%d %d %d" 11 11 11}}
{{end}}
{{with .Field | contains "boring" | contains "water" }}
	{{printf "%q" . | printf "%s"}}
{{else}}
	{{printf "%d %d %d" 11 11 11}}
{{end}}
Defined templates
This is rendered text

{{range .Items}}
    <div class="item">{{.}}</div>
{{end}}

{{template "footer.tmper" .}}

We added the ability to reference specific define blocks all defined in a single file from the template directive.

  • File1.html
{{define "footer" }}
	This is rendered text
	{{range .Items}}
		<div class="item">{{.}}</div>
	{{end}}
{{end}}

{{define "fender" }}
	This is rendered text
	{{range .Items}}
		<div class="item">{{.}}</div>
	{{end}}
{{end}}
  • File 2
{{template "footer.tmper" .}}
{{template "footer.tmper" .}}

Where each template specifically targets the specified file.

With the addition of Data and Types Directives, define blocks can also be typed:

{{modelType ItemList | List(string) }}
{{model Product | Items: ItemList }}

This is rendered text
Html tags

Html tags are now supported directly within the text parsing code, hence allowing you to write html text like any other text.

{{range $x, $y := .Items}}
	<div class="item">
		<h3 class="name item-{{$x}}">{{.Name}}</h3>
		<span class="price">{{$y.Price}}</span>
	</div>
{{else}}
	No data to count
{{end}}
Theme Attributes

The current templates had supports for theme attributes in Tailwind style. It allows specifying such values if so desired which will be applied and generated accordingly to the element.

<p
    class="bob"
    theme=[
        color-red-50
        sm-padding-10
        max-w
        max-h
        {{if and .User .User.Admin}}
            bob
        {{else}}
            bob-10
        {{end}}
    ]
    id=wan
>It was you</p>

One thing to notice with list attributes for html is a limitation in the parser which requires that space be only used to separate values and any value which is not meant to be separated should generally only have space after it's definition, so:

Bad form:
<p
    class="bob"
    theme=[
        color-red-50
        sm-padding-10
        xs:(bg-color-500, fg-color-400) // this is wrong
    ]
    id=wan
    >It was you</p>
Good form:
<p
    class="bob"
    theme=[
        color-red-50
        sm-padding-10
        xs:(bg-color-500,fg-color-400) // this is correct, no space in-between
    ]
    id=wan
>It was you</p>

Documentation

Index

Constants

View Source
const (
	DefaultLeftDelimiter  = "{{"
	DefaultRightDelimiter = "}}"
)

Variables

View Source
var (
	DefaultOption = Options{
		LeftDelimiter:  DefaultLeftDelimiter,
		RightDelimiter: DefaultRightDelimiter,
		Packages:       nil,
	}
)

Functions

This section is empty.

Types

type CommandPipe

type CommandPipe struct {
	RootCommander *CommandPipe
	Root          *parse.CommandNode
	FuncName      *parse.IdentifierNode
	MoreFields    []parse.Node
	Arguments     []parse.NodeTyper
	NoAttaching   bool
	// contains filtered or unexported fields
}

func (*CommandPipe) Parse

func (c *CommandPipe) Parse(t *TextWriter, tree *parse.Tree) error

func (*CommandPipe) ParseArgs

func (c *CommandPipe) ParseArgs(t *TextWriter, tree *parse.Tree) error

func (*CommandPipe) Prepare

func (c *CommandPipe) Prepare(set []*parse.CommandNode) error

func (*CommandPipe) PrepareCommand

func (c *CommandPipe) PrepareCommand(cmd *parse.CommandNode, others []*parse.CommandNode) error

func (*CommandPipe) Type

func (c *CommandPipe) Type() parse.NodeType

type File

type File interface {
	Refresh() error
	Read() (string, error)
	Parse(ops Options, ms map[string]string) (*ParsedData, error)
	ParseFor(ops Options, fm parse.FuncMaps, t Treeset) (*ParsedData, error)
	ParseBlock(blockName string, ops Options, ms map[string]string) (*ParsedData, error)
	ParseBlockFor(blockName string, ops Options, fm parse.FuncMaps, t Treeset) (*ParsedData, error)
}

File represents a actually file (be it memory or local) which contains contents which can be refreshed to get the latest content.

type Options

type Options struct {
	LeftDelimiter  string
	RightDelimiter string

	// Packages contain packages we wish added
	// into import path. It provides an alternative
	// to one declared directly in the template using
	// the {{import ... }} directive.
	//
	// The key is the alias and value the package path which
	// is to be imported.
	Packages map[string]string
	// contains filtered or unexported fields
}

Options contains config fields for the TextWriter.

type ParsedData

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

func New

func New(text string, functions []map[string]string, sf SourceFileSystem) (*ParsedData, error)

func Parse

func Parse(name string, text string, ops Options, funcNames []map[string]string, sf SourceFileSystem, tset Treeset) (*ParsedData, error)

Parse parses provided string into underline collector.

@param(require): name represent the special name to represent this parsed tree @param(required): text contains the content to be parsed @param(required): functions is the list of maps defining actions that should be considered functions @param(required): sf is the root file system to be used when retrieving template blocks for parsing @param(optional): tset is the previous Treeset from a previous run where this is considered a child of.

func ParseTree

func ParseTree(name string, text string, ops Options, fset map[string]string, sf SourceFileSystem, tset Treeset) (*ParsedData, error)

func ParseTreeWithFuncMaps

func ParseTreeWithFuncMaps(name string, fileName string, text string, ops Options, maps parse.FuncMaps, sf SourceFileSystem, set Treeset) (*ParsedData, error)

func (*ParsedData) Format

func (pd *ParsedData) Format(packageName string) (string, error)

type SourceFileSystem

type SourceFileSystem interface {
	GetFile(name string) (File, error)
}

SourceFileSystem embodies a provider which allows retrieval of specific sources based on giving unique name.

type TextWriter

type TextWriter struct {
	TreeSet Treeset

	KomponentBuilder *strings.Builder
	// contains filtered or unexported fields
}

TextWriter implements a template to go parser.

func (*TextWriter) AddFuncArg

func (t *TextWriter) AddFuncArg(varName string, varType string)

func (*TextWriter) AddFuncNamedReturn

func (t *TextWriter) AddFuncNamedReturn(varName string, varType string)

func (*TextWriter) AddToAttr added in v0.1.8

func (t *TextWriter) AddToAttr(varName string, text string)

func (*TextWriter) AddValueToHtmlListAttr

func (t *TextWriter) AddValueToHtmlListAttr(varName string, content string)

func (*TextWriter) AddValueToThemeListAttr added in v0.1.8

func (t *TextWriter) AddValueToThemeListAttr(varName string, content string)

func (*TextWriter) AppendNode

func (t *TextWriter) AppendNode(rootName string, varName string)

func (*TextWriter) AssignNodeTheme added in v0.1.8

func (t *TextWriter) AssignNodeTheme(rootName string, varName string)

func (*TextWriter) BooleanValue

func (t *TextWriter) BooleanValue(res bool)

func (*TextWriter) BooleanVar

func (t *TextWriter) BooleanVar(varName string, val bool)

func (*TextWriter) DotValue

func (t *TextWriter) DotValue()

func (*TextWriter) FuncArgEnd

func (t *TextWriter) FuncArgEnd()

func (*TextWriter) FuncArgStart

func (t *TextWriter) FuncArgStart(varName string, varType string)

func (*TextWriter) FuncNamedReturnEnd

func (t *TextWriter) FuncNamedReturnEnd(varName string, varType string)

func (*TextWriter) FuncNamedReturnStart

func (t *TextWriter) FuncNamedReturnStart(varName string, varType string)

func (*TextWriter) FuncReturns

func (t *TextWriter) FuncReturns(returnTypes ...string)

func (*TextWriter) Identifier

func (t *TextWriter) Identifier(id string)

func (*TextWriter) Indent

func (t *TextWriter) Indent()

func (*TextWriter) NewComment

func (t *TextWriter) NewComment(varName string, content string)

func (*TextWriter) NewDocument

func (t *TextWriter) NewDocument(varName string)

func (*TextWriter) NewElement

func (t *TextWriter) NewElement(varName string, tagName string, id string)

func (*TextWriter) NewFuncStart

func (t *TextWriter) NewFuncStart(funcName string)

func (*TextWriter) NewHtmlAttr

func (t *TextWriter) NewHtmlAttr(name string, content string, rootNode string)

func (*TextWriter) NewHtmlListAttr

func (t *TextWriter) NewHtmlListAttr(varName string, name string)

func (*TextWriter) NewHtmlTheme added in v0.1.8

func (t *TextWriter) NewHtmlTheme(varName string)

func (*TextWriter) NewLine

func (t *TextWriter) NewLine()

func (*TextWriter) NewNode

func (t *TextWriter) NewNode(varName string, nodeType int, tagName string, id string)

func (*TextWriter) NewText

func (t *TextWriter) NewText(varName string, text string)

func (*TextWriter) NewVar

func (t *TextWriter) NewVar(varName string, valName string)

func (*TextWriter) NewVarStart

func (t *TextWriter) NewVarStart(varName string)

func (*TextWriter) NilValue

func (t *TextWriter) NilValue()

func (*TextWriter) NumberValue

func (t *TextWriter) NumberValue(constant *parse.NumberNode) error

func (*TextWriter) ParseTree

func (t *TextWriter) ParseTree() error

ParseTree parses the tree with provided Treeset and parent.

func (*TextWriter) Reset

func (t *TextWriter) Reset()

func (*TextWriter) Space

func (t *TextWriter) Space()

func (*TextWriter) SpaceN

func (t *TextWriter) SpaceN(length int)

func (*TextWriter) StringValue

func (t *TextWriter) StringValue(res string)

func (*TextWriter) Var

func (t *TextWriter) Var()

func (*TextWriter) Write

func (t *TextWriter) Write(s string)

type Treeset

type Treeset map[string]*parse.Tree

type VDir

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

VDir implements SourceFileSystem provided a caching system for files and directories.

func NewVDir

func NewVDir(targetDir string) *VDir

NewVDir returns a new VDir for the giving target path

func (*VDir) GetFile

func (v *VDir) GetFile(subPath string) (File, error)

GetFile returns a file within the directory which provides a VFile.

func (*VDir) LoadDir

func (v *VDir) LoadDir() error

LoadDir loads a giving directory files as cache VFiles for quicker access.

type VFile

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

func NewVFile

func NewVFile(targetFile string) *VFile

NewVFile returns a new file.

func NewVFileWithFS

func NewVFileWithFS(targetFile string, fs SourceFileSystem) *VFile

NewVFileWithFS returns a new VFile with associated fs.

func (*VFile) Parse

func (vt *VFile) Parse(ops Options, fns map[string]string) (*ParsedData, error)

Parse parses the content of the File using the miru.TextWriter returning a strings.Builder containing the parsed content.

func (*VFile) ParseBlock

func (vt *VFile) ParseBlock(blockName string, ops Options, fns map[string]string) (*ParsedData, error)

ParseBlock parses the content of the File using the miru.TextWriter returning a strings.Builder containing the parsed content.

func (*VFile) ParseBlockFor

func (vt *VFile) ParseBlockFor(blockName string, ops Options, fns parse.FuncMaps, treeset Treeset) (*ParsedData, error)

ParseBlockFor parses the content of the File using the miru.TextWriter returning a strings.Builder containing the parsed content.

func (*VFile) ParseFor

func (vt *VFile) ParseFor(ops Options, fns parse.FuncMaps, treeset Treeset) (*ParsedData, error)

ParseFor parses the content of the File using the miru.TextWriter returning a strings.Builder containing the parsed content.

func (*VFile) Read

func (vt *VFile) Read() (string, error)

Read returns the string from it's cache (a strings.Builder) which uses unsafe to convert a byteslice into a string when calling strings.Builder.String, so ensure to not write to the returned string memory location as it will cause a memory corruption.

func (*VFile) Refresh

func (vt *VFile) Refresh() error

Refresh reloads the internal cache of the content of the file.

Directories

Path Synopsis
package escaper provides functions for escaping and unescaping HTML text.
package escaper provides functions for escaping and unescaping HTML text.
Package parse builds parse trees for templates as defined by text/template and html/template.
Package parse builds parse trees for templates as defined by text/template and html/template.

Jump to

Keyboard shortcuts

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