123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743 |
- // Copyright 2009 The Go Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- // Code to parse a template.
- package template
- import (
- "fmt"
- "io"
- "io/ioutil"
- "reflect"
- "strconv"
- "strings"
- "unicode"
- "unicode/utf8"
- )
- // Errors returned during parsing and execution. Users may extract the information and reformat
- // if they desire.
- type Error struct {
- Line int
- Msg string
- }
- func (e *Error) Error() string { return fmt.Sprintf("line %d: %s", e.Line, e.Msg) }
- // checkError is a deferred function to turn a panic with type *Error into a plain error return.
- // Other panics are unexpected and so are re-enabled.
- func checkError(error *error) {
- if v := recover(); v != nil {
- if e, ok := v.(*Error); ok {
- *error = e
- } else {
- // runtime errors should crash
- panic(v)
- }
- }
- }
- // Most of the literals are aces.
- var lbrace = []byte{'{'}
- var rbrace = []byte{'}'}
- var space = []byte{' '}
- var tab = []byte{'\t'}
- // The various types of "tokens", which are plain text or (usually) brace-delimited descriptors
- const (
- tokAlternates = iota
- tokComment
- tokEnd
- tokLiteral
- tokOr
- tokRepeated
- tokSection
- tokText
- tokVariable
- )
- // FormatterMap is the type describing the mapping from formatter
- // names to the functions that implement them.
- type FormatterMap map[string]func(io.Writer, string, ...interface{})
- // Built-in formatters.
- var builtins = FormatterMap{
- "html": HTMLFormatter,
- "str": StringFormatter,
- "": StringFormatter,
- }
- // The parsed state of a template is a vector of xxxElement structs.
- // Sections have line numbers so errors can be reported better during execution.
- // Plain text.
- type textElement struct {
- text []byte
- }
- // A literal such as .meta-left or .meta-right
- type literalElement struct {
- text []byte
- }
- // A variable invocation to be evaluated
- type variableElement struct {
- linenum int
- args []interface{} // The fields and literals in the invocation.
- fmts []string // Names of formatters to apply. len(fmts) > 0
- }
- // A variableElement arg to be evaluated as a field name
- type fieldName string
- // A .section block, possibly with a .or
- type sectionElement struct {
- linenum int // of .section itself
- field string // cursor field for this block
- start int // first element
- or int // first element of .or block
- end int // one beyond last element
- }
- // A .repeated block, possibly with a .or and a .alternates
- type repeatedElement struct {
- sectionElement // It has the same structure...
- altstart int // ... except for alternates
- altend int
- }
- // Template is the type that represents a template definition.
- // It is unchanged after parsing.
- type Template struct {
- fmap FormatterMap // formatters for variables
- // Used during parsing:
- ldelim, rdelim []byte // delimiters; default {}
- buf []byte // input text to process
- p int // position in buf
- linenum int // position in input
- // Parsed results:
- elems []interface{}
- }
- // New creates a new template with the specified formatter map (which
- // may be nil) to define auxiliary functions for formatting variables.
- func New(fmap FormatterMap) *Template {
- t := new(Template)
- t.fmap = fmap
- t.ldelim = lbrace
- t.rdelim = rbrace
- t.elems = make([]interface{}, 0, 16)
- return t
- }
- // Report error and stop executing. The line number must be provided explicitly.
- func (t *Template) execError(st *state, line int, err string, args ...interface{}) {
- panic(&Error{line, fmt.Sprintf(err, args...)})
- }
- // Report error, panic to terminate parsing.
- // The line number comes from the template state.
- func (t *Template) parseError(err string, args ...interface{}) {
- panic(&Error{t.linenum, fmt.Sprintf(err, args...)})
- }
- // Is this an exported - upper case - name?
- func isExported(name string) bool {
- r, _ := utf8.DecodeRuneInString(name)
- return unicode.IsUpper(r)
- }
- // -- Lexical analysis
- // Is c a space character?
- func isSpace(c uint8) bool { return c == ' ' || c == '\t' || c == '\r' || c == '\n' }
- // Safely, does s[n:n+len(t)] == t?
- func equal(s []byte, n int, t []byte) bool {
- b := s[n:]
- if len(t) > len(b) { // not enough space left for a match.
- return false
- }
- for i, c := range t {
- if c != b[i] {
- return false
- }
- }
- return true
- }
- // isQuote returns true if c is a string- or character-delimiting quote character.
- func isQuote(c byte) bool {
- return c == '"' || c == '`' || c == '\''
- }
- // endQuote returns the end quote index for the quoted string that
- // starts at n, or -1 if no matching end quote is found before the end
- // of the line.
- func endQuote(s []byte, n int) int {
- quote := s[n]
- for n++; n < len(s); n++ {
- switch s[n] {
- case '\\':
- if quote == '"' || quote == '\'' {
- n++
- }
- case '\n':
- return -1
- case quote:
- return n
- }
- }
- return -1
- }
- // nextItem returns the next item from the input buffer. If the returned
- // item is empty, we are at EOF. The item will be either a
- // delimited string or a non-empty string between delimited
- // strings. Tokens stop at (but include, if plain text) a newline.
- // Action tokens on a line by themselves drop any space on
- // either side, up to and including the newline.
- func (t *Template) nextItem() []byte {
- startOfLine := t.p == 0 || t.buf[t.p-1] == '\n'
- start := t.p
- var i int
- newline := func() {
- t.linenum++
- i++
- }
- // Leading space up to but not including newline
- for i = start; i < len(t.buf); i++ {
- if t.buf[i] == '\n' || !isSpace(t.buf[i]) {
- break
- }
- }
- leadingSpace := i > start
- // What's left is nothing, newline, delimited string, or plain text
- switch {
- case i == len(t.buf):
- // EOF; nothing to do
- case t.buf[i] == '\n':
- newline()
- case equal(t.buf, i, t.ldelim):
- left := i // Start of left delimiter.
- right := -1 // Will be (immediately after) right delimiter.
- haveText := false // Delimiters contain text.
- i += len(t.ldelim)
- // Find the end of the action.
- for ; i < len(t.buf); i++ {
- if t.buf[i] == '\n' {
- break
- }
- if isQuote(t.buf[i]) {
- i = endQuote(t.buf, i)
- if i == -1 {
- t.parseError("unmatched quote")
- return nil
- }
- continue
- }
- if equal(t.buf, i, t.rdelim) {
- i += len(t.rdelim)
- right = i
- break
- }
- haveText = true
- }
- if right < 0 {
- t.parseError("unmatched opening delimiter")
- return nil
- }
- // Is this a special action (starts with '.' or '#') and the only thing on the line?
- if startOfLine && haveText {
- firstChar := t.buf[left+len(t.ldelim)]
- if firstChar == '.' || firstChar == '#' {
- // It's special and the first thing on the line. Is it the last?
- for j := right; j < len(t.buf) && isSpace(t.buf[j]); j++ {
- if t.buf[j] == '\n' {
- // Yes it is. Drop the surrounding space and return the {.foo}
- t.linenum++
- t.p = j + 1
- return t.buf[left:right]
- }
- }
- }
- }
- // No it's not. If there's leading space, return that.
- if leadingSpace {
- // not trimming space: return leading space if there is some.
- t.p = left
- return t.buf[start:left]
- }
- // Return the word, leave the trailing space.
- start = left
- break
- default:
- for ; i < len(t.buf); i++ {
- if t.buf[i] == '\n' {
- newline()
- break
- }
- if equal(t.buf, i, t.ldelim) {
- break
- }
- }
- }
- item := t.buf[start:i]
- t.p = i
- return item
- }
- // Turn a byte array into a space-split array of strings,
- // taking into account quoted strings.
- func words(buf []byte) []string {
- s := make([]string, 0, 5)
- for i := 0; i < len(buf); {
- // One word per loop
- for i < len(buf) && isSpace(buf[i]) {
- i++
- }
- if i == len(buf) {
- break
- }
- // Got a word
- start := i
- if isQuote(buf[i]) {
- i = endQuote(buf, i)
- if i < 0 {
- i = len(buf)
- } else {
- i++
- }
- }
- // Even with quotes, break on space only. This handles input
- // such as {""|} and catches quoting mistakes.
- for i < len(buf) && !isSpace(buf[i]) {
- i++
- }
- s = append(s, string(buf[start:i]))
- }
- return s
- }
- // Analyze an item and return its token type and, if it's an action item, an array of
- // its constituent words.
- func (t *Template) analyze(item []byte) (tok int, w []string) {
- // item is known to be non-empty
- if !equal(item, 0, t.ldelim) { // doesn't start with left delimiter
- tok = tokText
- return
- }
- if !equal(item, len(item)-len(t.rdelim), t.rdelim) { // doesn't end with right delimiter
- t.parseError("internal error: unmatched opening delimiter") // lexing should prevent this
- return
- }
- if len(item) <= len(t.ldelim)+len(t.rdelim) { // no contents
- t.parseError("empty directive")
- return
- }
- // Comment
- if item[len(t.ldelim)] == '#' {
- tok = tokComment
- return
- }
- // Split into words
- w = words(item[len(t.ldelim) : len(item)-len(t.rdelim)]) // drop final delimiter
- if len(w) == 0 {
- t.parseError("empty directive")
- return
- }
- first := w[0]
- if first[0] != '.' {
- tok = tokVariable
- return
- }
- if len(first) > 1 && first[1] >= '0' && first[1] <= '9' {
- // Must be a float.
- tok = tokVariable
- return
- }
- switch first {
- case ".meta-left", ".meta-right", ".space", ".tab":
- tok = tokLiteral
- return
- case ".or":
- tok = tokOr
- return
- case ".end":
- tok = tokEnd
- return
- case ".section":
- if len(w) != 2 {
- t.parseError("incorrect fields for .section: %s", item)
- return
- }
- tok = tokSection
- return
- case ".repeated":
- if len(w) != 3 || w[1] != "section" {
- t.parseError("incorrect fields for .repeated: %s", item)
- return
- }
- tok = tokRepeated
- return
- case ".alternates":
- if len(w) != 2 || w[1] != "with" {
- t.parseError("incorrect fields for .alternates: %s", item)
- return
- }
- tok = tokAlternates
- return
- }
- t.parseError("bad directive: %s", item)
- return
- }
- // formatter returns the Formatter with the given name in the Template, or nil if none exists.
- func (t *Template) formatter(name string) func(io.Writer, string, ...interface{}) {
- if t.fmap != nil {
- if fn := t.fmap[name]; fn != nil {
- return fn
- }
- }
- return builtins[name]
- }
- // -- Parsing
- // newVariable allocates a new variable-evaluation element.
- func (t *Template) newVariable(words []string) *variableElement {
- formatters := extractFormatters(words)
- args := make([]interface{}, len(words))
- // Build argument list, processing any literals
- for i, word := range words {
- var lerr error
- switch word[0] {
- case '"', '`', '\'':
- v, err := strconv.Unquote(word)
- if err == nil && word[0] == '\'' {
- args[i], _ = utf8.DecodeRuneInString(v)
- } else {
- args[i], lerr = v, err
- }
- case '.', '+', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
- v, err := strconv.ParseInt(word, 0, 64)
- if err == nil {
- args[i] = v
- } else {
- v, err := strconv.ParseFloat(word, 64)
- args[i], lerr = v, err
- }
- default:
- args[i] = fieldName(word)
- }
- if lerr != nil {
- t.parseError("invalid literal: %q: %s", word, lerr)
- }
- }
- // We could remember the function address here and avoid the lookup later,
- // but it's more dynamic to let the user change the map contents underfoot.
- // We do require the name to be present, though.
- // Is it in user-supplied map?
- for _, f := range formatters {
- if t.formatter(f) == nil {
- t.parseError("unknown formatter: %q", f)
- }
- }
- return &variableElement{t.linenum, args, formatters}
- }
- // extractFormatters extracts a list of formatters from words.
- // After the final space-separated argument in a variable, formatters may be
- // specified separated by pipe symbols. For example: {a b c|d|e}
- // The words parameter still has the formatters joined by '|' in the last word.
- // extractFormatters splits formatters, replaces the last word with the content
- // found before the first '|' within it, and returns the formatters obtained.
- // If no formatters are found in words, the default formatter is returned.
- func extractFormatters(words []string) (formatters []string) {
- // "" is the default formatter.
- formatters = []string{""}
- if len(words) == 0 {
- return
- }
- var bar int
- lastWord := words[len(words)-1]
- if isQuote(lastWord[0]) {
- end := endQuote([]byte(lastWord), 0)
- if end < 0 || end+1 == len(lastWord) || lastWord[end+1] != '|' {
- return
- }
- bar = end + 1
- } else {
- bar = strings.IndexRune(lastWord, '|')
- if bar < 0 {
- return
- }
- }
- words[len(words)-1] = lastWord[0:bar]
- formatters = strings.Split(lastWord[bar+1:], "|")
- return
- }
- // Grab the next item. If it's simple, just append it to the template.
- // Otherwise return its details.
- func (t *Template) parseSimple(item []byte) (done bool, tok int, w []string) {
- tok, w = t.analyze(item)
- done = true // assume for simplicity
- switch tok {
- case tokComment:
- return
- case tokText:
- t.elems = append(t.elems, &textElement{item})
- return
- case tokLiteral:
- switch w[0] {
- case ".meta-left":
- t.elems = append(t.elems, &literalElement{t.ldelim})
- case ".meta-right":
- t.elems = append(t.elems, &literalElement{t.rdelim})
- case ".space":
- t.elems = append(t.elems, &literalElement{space})
- case ".tab":
- t.elems = append(t.elems, &literalElement{tab})
- default:
- t.parseError("internal error: unknown literal: %s", w[0])
- }
- return
- case tokVariable:
- t.elems = append(t.elems, t.newVariable(w))
- return
- }
- return false, tok, w
- }
- // parseRepeated and parseSection are mutually recursive
- func (t *Template) parseRepeated(words []string) *repeatedElement {
- r := new(repeatedElement)
- t.elems = append(t.elems, r)
- r.linenum = t.linenum
- r.field = words[2]
- // Scan section, collecting true and false (.or) blocks.
- r.start = len(t.elems)
- r.or = -1
- r.altstart = -1
- r.altend = -1
- Loop:
- for {
- item := t.nextItem()
- if len(item) == 0 {
- t.parseError("missing .end for .repeated section")
- break
- }
- done, tok, w := t.parseSimple(item)
- if done {
- continue
- }
- switch tok {
- case tokEnd:
- break Loop
- case tokOr:
- if r.or >= 0 {
- t.parseError("extra .or in .repeated section")
- break Loop
- }
- r.altend = len(t.elems)
- r.or = len(t.elems)
- case tokSection:
- t.parseSection(w)
- case tokRepeated:
- t.parseRepeated(w)
- case tokAlternates:
- if r.altstart >= 0 {
- t.parseError("extra .alternates in .repeated section")
- break Loop
- }
- if r.or >= 0 {
- t.parseError(".alternates inside .or block in .repeated section")
- break Loop
- }
- r.altstart = len(t.elems)
- default:
- t.parseError("internal error: unknown repeated section item: %s", item)
- break Loop
- }
- }
- if r.altend < 0 {
- r.altend = len(t.elems)
- }
- r.end = len(t.elems)
- return r
- }
- func (t *Template) parseSection(words []string) *sectionElement {
- s := new(sectionElement)
- t.elems = append(t.elems, s)
- s.linenum = t.linenum
- s.field = words[1]
- // Scan section, collecting true and false (.or) blocks.
- s.start = len(t.elems)
- s.or = -1
- Loop:
- for {
- item := t.nextItem()
- if len(item) == 0 {
- t.parseError("missing .end for .section")
- break
- }
- done, tok, w := t.parseSimple(item)
- if done {
- continue
- }
- switch tok {
- case tokEnd:
- break Loop
- case tokOr:
- if s.or >= 0 {
- t.parseError("extra .or in .section")
- break Loop
- }
- s.or = len(t.elems)
- case tokSection:
- t.parseSection(w)
- case tokRepeated:
- t.parseRepeated(w)
- case tokAlternates:
- t.parseError(".alternates not in .repeated")
- default:
- t.parseError("internal error: unknown section item: %s", item)
- }
- }
- s.end = len(t.elems)
- return s
- }
- func (t *Template) parse() {
- for {
- item := t.nextItem()
- if len(item) == 0 {
- break
- }
- done, tok, w := t.parseSimple(item)
- if done {
- continue
- }
- switch tok {
- case tokOr, tokEnd, tokAlternates:
- t.parseError("unexpected %s", w[0])
- case tokSection:
- t.parseSection(w)
- case tokRepeated:
- t.parseRepeated(w)
- default:
- t.parseError("internal error: bad directive in parse: %s", item)
- }
- }
- }
- // -- Execution
- // -- Public interface
- // Parse initializes a Template by parsing its definition. The string
- // s contains the template text. If any errors occur, Parse returns
- // the error.
- func (t *Template) Parse(s string) (err error) {
- if t.elems == nil {
- return &Error{1, "template not allocated with New"}
- }
- if !validDelim(t.ldelim) || !validDelim(t.rdelim) {
- return &Error{1, fmt.Sprintf("bad delimiter strings %q %q", t.ldelim, t.rdelim)}
- }
- defer checkError(&err)
- t.buf = []byte(s)
- t.p = 0
- t.linenum = 1
- t.parse()
- return nil
- }
- // ParseFile is like Parse but reads the template definition from the
- // named file.
- func (t *Template) ParseFile(filename string) (err error) {
- b, err := ioutil.ReadFile(filename)
- if err != nil {
- return err
- }
- return t.Parse(string(b))
- }
- // Execute applies a parsed template to the specified data object,
- // generating output to wr.
- func (t *Template) Execute(wr io.Writer, data interface{}) (err error) {
- // Extract the driver data.
- val := reflect.ValueOf(data)
- defer checkError(&err)
- t.p = 0
- t.execute(0, len(t.elems), &state{parent: nil, data: val, wr: wr})
- return nil
- }
- // SetDelims sets the left and right delimiters for operations in the
- // template. They are validated during parsing. They could be
- // validated here but it's better to keep the routine simple. The
- // delimiters are very rarely invalid and Parse has the necessary
- // error-handling interface already.
- func (t *Template) SetDelims(left, right string) {
- t.ldelim = []byte(left)
- t.rdelim = []byte(right)
- }
- // Parse creates a Template with default parameters (such as {} for
- // metacharacters). The string s contains the template text while
- // the formatter map fmap, which may be nil, defines auxiliary functions
- // for formatting variables. The template is returned. If any errors
- // occur, err will be non-nil.
- func Parse(s string, fmap FormatterMap) (t *Template, err error) {
- t = New(fmap)
- err = t.Parse(s)
- if err != nil {
- t = nil
- }
- return
- }
- // ParseFile is a wrapper function that creates a Template with default
- // parameters (such as {} for metacharacters). The filename identifies
- // a file containing the template text, while the formatter map fmap, which
- // may be nil, defines auxiliary functions for formatting variables.
- // The template is returned. If any errors occur, err will be non-nil.
- func ParseFile(filename string, fmap FormatterMap) (t *Template, err error) {
- b, err := ioutil.ReadFile(filename)
- if err != nil {
- return nil, err
- }
- return Parse(string(b), fmap)
- }
- // MustParse is like Parse but panics if the template cannot be parsed.
- func MustParse(s string, fmap FormatterMap) *Template {
- t, err := Parse(s, fmap)
- if err != nil {
- panic("template.MustParse error: " + err.Error())
- }
- return t
- }
- // MustParseFile is like ParseFile but panics if the file cannot be read
- // or the template cannot be parsed.
- func MustParseFile(filename string, fmap FormatterMap) *Template {
- b, err := ioutil.ReadFile(filename)
- if err != nil {
- panic("template.MustParseFile error: " + err.Error())
- }
- return MustParse(string(b), fmap)
- }
|