| // 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. |
| |
| // Template library. See http://code.google.com/p/json-template/wiki/Reference |
| // TODO: document this here as well. |
| package template |
| |
| import ( |
| "fmt"; |
| "io"; |
| "os"; |
| "reflect"; |
| "strings"; |
| "template"; |
| ) |
| |
| var ErrUnmatchedRDelim = os.NewError("unmatched closing delimiter") |
| var ErrUnmatchedLDelim = os.NewError("unmatched opening delimiter") |
| var ErrBadDirective = os.NewError("unrecognized directive name") |
| var ErrEmptyDirective = os.NewError("empty directive") |
| var ErrFields = os.NewError("incorrect fields for directive") |
| var ErrSyntax = os.NewError("directive out of place") |
| var ErrNoEnd = os.NewError("section does not have .end") |
| var ErrNoVar = os.NewError("variable name not in struct"); |
| var ErrBadType = os.NewError("unsupported type for variable"); |
| var ErrNotStruct = os.NewError("driver must be a struct") |
| var ErrNoFormatter = os.NewError("unknown formatter") |
| var ErrEmptyDelims = os.NewError("empty delimiter strings") |
| |
| // All the literals are aces. |
| var lbrace = []byte{ '{' } |
| var rbrace = []byte{ '}' } |
| var space = []byte{ ' ' } |
| |
| // The various types of "tokens", which are plain text or (usually) brace-delimited descriptors |
| const ( |
| Alternates = iota; |
| Comment; |
| End; |
| Literal; |
| Or; |
| Repeated; |
| Section; |
| Text; |
| Variable; |
| ) |
| |
| // FormatterMap is the type describing the mapping from formatter |
| // names to the functions that implement them. |
| type FormatterMap map[string] func(io.Write, interface{}, string) |
| |
| // Built-in formatters. |
| var builtins = FormatterMap { |
| "html" : HtmlFormatter, |
| "str" : StringFormatter, |
| "" : StringFormatter, |
| } |
| |
| // State for executing a Template |
| type state struct { |
| parent *state; // parent in hierarchy |
| errorchan chan *os.Error; // for erroring out |
| data reflect.Value; // the driver data for this section etc. |
| wr io.Write; // where to send output |
| } |
| |
| // Report error and stop generation. |
| func (st *state) error(err *os.Error, args ...) { |
| fmt.Fprintf(os.Stderr, "template: %v%s\n", err, fmt.Sprint(args)); |
| st.errorchan <- err; |
| sys.Goexit(); |
| } |
| |
| type Template struct { |
| fmap FormatterMap; // formatters for variables |
| ldelim, rdelim []byte; // delimiters; default {} |
| buf []byte; // input text to process |
| p int; // position in buf |
| linenum *int; // position in input |
| } |
| |
| // Initialize a top-level template in prepratation for parsing. |
| // The formatter map and delimiters are already set. |
| func (t *Template) init(buf []byte) *Template { |
| t.buf = buf; |
| t.p = 0; |
| t.linenum = new(int); |
| return t; |
| } |
| // Create a template deriving from its parent |
| func childTemplate(parent *Template, buf []byte) *Template { |
| t := new(Template); |
| t.ldelim = parent.ldelim; |
| t.rdelim = parent.rdelim; |
| t.buf = buf; |
| t.p = 0; |
| t.fmap = parent.fmap; |
| t.linenum = parent.linenum; |
| return t; |
| } |
| |
| func white(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:len(s)]; |
| 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 |
| } |
| |
| func (t *Template) execute(st *state) |
| func (t *Template) executeSection(w []string, st *state) |
| |
| // 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. Most tokens stop at (but include, if plain text) a newline. |
| // Action tokens on a line by themselves drop the white space on |
| // either side, up to and including the newline. |
| func (t *Template) nextItem(st *state) []byte { |
| sawLeft := false; // are we waiting for an opening delimiter? |
| special := false; // is this a {.foo} directive, which means trim white space? |
| // Delete surrounding white space if this {.foo} is the only thing on the line. |
| trim_white := t.p == 0 || t.buf[t.p-1] == '\n'; |
| only_white := true; // we have seen only white space so far |
| var i int; |
| start := t.p; |
| Loop: |
| for i = t.p; i < len(t.buf); i++ { |
| switch { |
| case t.buf[i] == '\n': |
| *t.linenum++; |
| i++; |
| break Loop; |
| case white(t.buf[i]): |
| // white space, do nothing |
| case !sawLeft && equal(t.buf, i, t.ldelim): // sawLeft checked because delims may be equal |
| // anything interesting already on the line? |
| if !only_white { |
| break Loop; |
| } |
| // is it a directive or comment? |
| j := i + len(t.ldelim); // position after delimiter |
| if j+1 < len(t.buf) && (t.buf[j] == '.' || t.buf[j] == '#') { |
| special = true; |
| if trim_white && only_white { |
| start = i; |
| } |
| } else if i > t.p { // have some text accumulated so stop before delimiter |
| break Loop; |
| } |
| sawLeft = true; |
| i = j - 1; |
| case equal(t.buf, i, t.rdelim): |
| if !sawLeft { |
| st.error(ErrUnmatchedRDelim) |
| } |
| sawLeft = false; |
| i += len(t.rdelim); |
| break Loop; |
| default: |
| only_white = false; |
| } |
| } |
| if sawLeft { |
| st.error(ErrUnmatchedLDelim) |
| } |
| item := t.buf[start:i]; |
| if special && trim_white { |
| // consume trailing white space |
| for ; i < len(t.buf) && white(t.buf[i]); i++ { |
| if t.buf[i] == '\n' { |
| i++; |
| break // stop after newline |
| } |
| } |
| } |
| t.p = i; |
| return item |
| } |
| |
| // Turn a byte array into a white-space-split array of strings. |
| func words(buf []byte) []string { |
| s := make([]string, 0, 5); |
| p := 0; // position in buf |
| // one word per loop |
| for i := 0; ; i++ { |
| // skip white space |
| for ; p < len(buf) && white(buf[p]); p++ { |
| } |
| // grab word |
| start := p; |
| for ; p < len(buf) && !white(buf[p]); p++ { |
| } |
| if start == p { // no text left |
| break |
| } |
| if i == cap(s) { |
| ns := make([]string, 2*cap(s)); |
| for j := range s { |
| ns[j] = s[j] |
| } |
| s = ns; |
| } |
| s = s[0:i+1]; |
| s[i] = string(buf[start:p]) |
| } |
| return s |
| } |
| |
| // Analyze an item and return its type and, if it's an action item, an array of |
| // its constituent words. |
| func (t *Template) analyze(item []byte, st *state) (tok int, w []string) { |
| // item is known to be non-empty |
| if !equal(item, 0, t.ldelim) { // doesn't start with left delimiter |
| tok = Text; |
| return |
| } |
| if !equal(item, len(item)-len(t.rdelim), t.rdelim) { // doesn't end with right delimiter |
| st.error(ErrUnmatchedLDelim) // should not happen anyway |
| } |
| if len(item) <= len(t.ldelim)+len(t.rdelim) { // no contents |
| st.error(ErrEmptyDirective) |
| } |
| // Comment |
| if item[len(t.ldelim)] == '#' { |
| tok = Comment; |
| return |
| } |
| // Split into words |
| w = words(item[len(t.ldelim): len(item)-len(t.rdelim)]); // drop final delimiter |
| if len(w) == 0 { |
| st.error(ErrBadDirective) |
| } |
| if len(w[0]) == 0 { |
| st.error(ErrEmptyDirective) |
| } |
| if len(w) == 1 && w[0][0] != '.' { |
| tok = Variable; |
| return; |
| } |
| switch w[0] { |
| case ".meta-left", ".meta-right", ".space": |
| tok = Literal; |
| return; |
| case ".or": |
| tok = Or; |
| return; |
| case ".end": |
| tok = End; |
| return; |
| case ".section": |
| if len(w) != 2 { |
| st.error(ErrFields, ": ", string(item)) |
| } |
| tok = Section; |
| return; |
| case ".repeated": |
| if len(w) != 3 || w[1] != "section" { |
| st.error(ErrFields, ": ", string(item)) |
| } |
| tok = Repeated; |
| return; |
| case ".alternates": |
| if len(w) != 2 || w[1] != "with" { |
| st.error(ErrFields, ": ", string(item)) |
| } |
| tok = Alternates; |
| return; |
| } |
| st.error(ErrBadDirective, ": ", string(item)); |
| return |
| } |
| |
| // If the data for this template is a struct, find the named variable. |
| // The special name "@" denotes the current data. |
| func (st *state) findVar(s string) reflect.Value { |
| if s == "@" { |
| return st.data |
| } |
| data := reflect.Indirect(st.data); |
| typ, ok := data.Type().(reflect.StructType); |
| if ok { |
| for i := 0; i < typ.Len(); i++ { |
| name, ftyp, tag, offset := typ.Field(i); |
| if name == s { |
| return data.(reflect.StructValue).Field(i) |
| } |
| } |
| } |
| return nil |
| } |
| |
| // Is there no data to look at? |
| func empty(v reflect.Value, indirect_ok bool) bool { |
| v = reflect.Indirect(v); |
| if v == nil { |
| return true |
| } |
| switch v.Type().Kind() { |
| case reflect.StringKind: |
| return v.(reflect.StringValue).Get() == ""; |
| case reflect.StructKind: |
| return false; |
| case reflect.ArrayKind: |
| return v.(reflect.ArrayValue).Len() == 0; |
| } |
| return true; |
| } |
| |
| // Execute a ".repeated" section |
| func (t *Template) executeRepeated(w []string, st *state) { |
| if w[1] != "section" { |
| st.error(ErrSyntax, `: .repeated must have "section"`) |
| } |
| |
| // Find driver array/struct for this section. It must be in the current struct. |
| field := st.findVar(w[2]); |
| if field == nil { |
| st.error(ErrNoVar, ": .repeated ", w[2], " in ", reflect.Indirect(st.data).Type()); |
| } |
| |
| // Must be an array/slice |
| if field != nil && field.Kind() != reflect.ArrayKind { |
| st.error(ErrBadType, " in .repeated: ", w[2], " ", field.Type().String()); |
| } |
| // Scan repeated section, remembering slice of text we must execute. |
| nesting := 0; |
| start := t.p; |
| end := t.p; |
| Loop: |
| for { |
| item := t.nextItem(st); |
| if len(item) == 0 { |
| st.error(ErrNoEnd) |
| } |
| tok, s := t.analyze(item, st); |
| switch tok { |
| case Comment: |
| continue; // just ignore it |
| case End: |
| if nesting == 0 { |
| break Loop |
| } |
| nesting--; |
| case Repeated, Section: |
| nesting++; |
| case Literal, Or, Text, Variable: |
| // just accumulate |
| default: |
| panic("unknown section item", string(item)); |
| } |
| end = t.p |
| } |
| if field != nil { |
| array := field.(reflect.ArrayValue); |
| for i := 0; i < array.Len(); i++ { |
| tmp := childTemplate(t, t.buf[start:end]); |
| tmp.execute(&state{st, st.errorchan, array.Elem(i), st.wr}); |
| } |
| } |
| } |
| |
| // Execute a ".section" |
| func (t *Template) executeSection(w []string, st *state) { |
| // Find driver data for this section. It must be in the current struct. |
| field := st.findVar(w[1]); |
| if field == nil { |
| st.error(ErrNoVar, ": .section ", w[1], " in ", reflect.Indirect(st.data).Type()); |
| } |
| // Scan section, remembering slice of text we must execute. |
| orFound := false; |
| nesting := 0; // How deeply are .section and .repeated nested? |
| start := t.p; |
| end := t.p; |
| accumulate := !empty(field, true); // Keep this section if there's data |
| Loop: |
| for { |
| item := t.nextItem(st); |
| if len(item) == 0 { |
| st.error(ErrNoEnd) |
| } |
| tok, s := t.analyze(item, st); |
| switch tok { |
| case Comment: |
| continue; // just ignore it |
| case End: |
| if nesting == 0 { |
| break Loop |
| } |
| nesting--; |
| case Or: |
| if nesting > 0 { // just accumulate |
| break |
| } |
| if orFound { |
| st.error(ErrSyntax, ": .or"); |
| } |
| orFound = true; |
| if !accumulate { |
| // No data; execute the .or instead |
| start = t.p; |
| end = t.p; |
| accumulate = true; |
| continue; |
| } else { |
| // Data present so disregard the .or section |
| accumulate = false |
| } |
| case Repeated, Section: |
| nesting++; |
| case Literal, Text, Variable: |
| // just accumulate |
| default: |
| panic("unknown section item", string(item)); |
| } |
| if accumulate { |
| end = t.p |
| } |
| } |
| tmp := childTemplate(t, t.buf[start:end]); |
| tmp.execute(&state{st, st.errorchan, field, st.wr}); |
| } |
| |
| // Look up a variable, up through the parent if necessary. |
| func (t *Template) varValue(name string, st *state) reflect.Value { |
| field := st.findVar(name); |
| if field == nil { |
| if st.parent == nil { |
| st.error(ErrNoVar, ": ", name) |
| } |
| return t.varValue(name, st.parent); |
| } |
| return field; |
| } |
| |
| // Evaluate a variable, looking up through the parent if necessary. |
| // If it has a formatter attached ({var|formatter}) run that too. |
| func (t *Template) writeVariable(st *state, name_formatter string) { |
| name := name_formatter; |
| formatter := ""; |
| bar := strings.Index(name_formatter, "|"); |
| if bar >= 0 { |
| name = name_formatter[0:bar]; |
| formatter = name_formatter[bar+1:len(name_formatter)]; |
| } |
| val := t.varValue(name, st).Interface(); |
| // is it in user-supplied map? |
| if t.fmap != nil { |
| if fn, ok := t.fmap[formatter]; ok { |
| fn(st.wr, val, formatter); |
| return; |
| } |
| } |
| // is it in builtin map? |
| if fn, ok := builtins[formatter]; ok { |
| fn(st.wr, val, formatter); |
| return; |
| } |
| st.error(ErrNoFormatter, ": ", formatter); |
| panic("notreached"); |
| } |
| |
| func (t *Template) execute(st *state) { |
| for { |
| item := t.nextItem(st); |
| if len(item) == 0 { |
| return |
| } |
| tok, w := t.analyze(item, st); |
| switch tok { |
| case Comment: |
| break; |
| case Text: |
| st.wr.Write(item); |
| case Literal: |
| switch w[0] { |
| case ".meta-left": |
| st.wr.Write(t.ldelim); |
| case ".meta-right": |
| st.wr.Write(t.rdelim); |
| case ".space": |
| st.wr.Write(space); |
| default: |
| panic("unknown literal: ", w[0]); |
| } |
| case Variable: |
| t.writeVariable(st, w[0]); |
| case Or, End, Alternates: |
| st.error(ErrSyntax, ": ", string(item)); |
| case Section: |
| t.executeSection(w, st); |
| case Repeated: |
| t.executeRepeated(w, st); |
| default: |
| panic("bad directive in execute:", string(item)); |
| } |
| } |
| } |
| |
| func (t *Template) doParse() { |
| // stub for now |
| } |
| |
| // Parse initializes a Template by parsing its definition. The string s contains |
| // the template text. If any errors occur, it returns the error and line number |
| // in the text of the erroneous construct. |
| func (t *Template) Parse(s string) (*os.Error, int) { |
| if len(t.ldelim) == 0 || len(t.rdelim) == 0 { |
| return ErrEmptyDelims, 0 |
| } |
| t.init(io.StringBytes(s)); |
| ch := make(chan *os.Error); |
| go func() { |
| t.doParse(); |
| ch <- nil; // clean return; |
| }(); |
| err := <-ch; |
| if err != nil { |
| return err, *t.linenum |
| } |
| return nil, 0 |
| } |
| |
| // Execute executes a parsed template on the specified data object, |
| // generating output to wr. |
| func (t *Template) Execute(data interface{}, wr io.Write) *os.Error { |
| // Extract the driver data. |
| val := reflect.NewValue(data); |
| ch := make(chan *os.Error); |
| go func() { |
| t.p = 0; |
| t.execute(&state{nil, ch, val, wr}); |
| ch <- nil; // clean return; |
| }(); |
| return <-ch; |
| } |
| |
| // New creates a new template with the specified formatter map (which |
| // may be nil) defining auxiliary functions for formatting variables. |
| func New(fmap FormatterMap) *Template { |
| t := new(Template); |
| t.fmap = fmap; |
| t.ldelim = lbrace; |
| t.rdelim = rbrace; |
| return t; |
| } |
| |
| // SetDelims sets the left and right delimiters for operations in the template. |
| func (t *Template) SetDelims(left, right string) { |
| t.ldelim = io.StringBytes(left); |
| t.rdelim = io.StringBytes(right); |
| } |
| |
| // Parse creates a Template with default parameters (such as {} for |
| // metacharacters). The string s contains the template text and the |
| // formatter map fmap (which may be nil) defines auxiliary functions |
| // for formatting variables. It returns the template, an error report |
| // (or nil), and the line number in the text of the erroneous construct. |
| func Parse(s string, fmap FormatterMap) (*Template, *os.Error, int) { |
| t := New(fmap); |
| err, line := t.Parse(s); |
| return t, err, line |
| } |