package jade import ( "log" "os" "path/filepath" "strings" ) func (t *Tree) topParse() { t.Root = t.newList(t.peek().pos) var ( ext bool token = t.nextNonSpace() ) if token.typ == itemExtends { ext = true t.Root.append(t.parseSubFile(token.val)) token = t.nextNonSpace() } for { switch token.typ { case itemInclude: t.Root.append(t.parseInclude(token)) case itemBlock, itemBlockPrepend, itemBlockAppend: if ext { t.parseBlock(token) } else { t.Root.append(t.parseBlock(token)) } case itemMixin: t.mixin[token.val] = t.parseMixin(token) case itemEOF: return case itemExtends: t.errorf(`Declaration of template inheritance ("extends") should be the first thing in the file. There can only be one extends statement per file.`) case itemError: t.errorf("%s line: %d\n", token.val, token.line) default: if ext { t.errorf(`Only import, named blocks and mixins can appear at the top level of an extending template`) } t.Root.append(t.hub(token)) } token = t.nextNonSpace() } } func (t *Tree) hub(token item) (n Node) { for { switch token.typ { case itemDiv: token.val = "div" fallthrough case itemTag, itemTagInline, itemTagVoid, itemTagVoidInline: return t.parseTag(token) case itemText, itemComment, itemHTMLTag: return t.newText(token.pos, []byte(token.val), token.typ) case itemCode, itemCodeBuffered, itemCodeUnescaped, itemMixinBlock: return t.newCode(token.pos, token.val, token.typ) case itemIf, itemUnless: return t.parseIf(token) case itemFor, itemEach, itemWhile: return t.parseFor(token) case itemCase: return t.parseCase(token) case itemBlock, itemBlockPrepend, itemBlockAppend: return t.parseBlock(token) case itemMixinCall: return t.parseMixinUse(token) case itemInclude: return t.parseInclude(token) case itemDoctype: return t.newDoctype(token.pos, token.val) case itemFilter: return t.parseFilter(token) case itemError: t.errorf("Error lex: %s line: %d\n", token.val, token.line) default: t.errorf(`Error hub(): unexpected token "%s" type "%s"`, token.val, token.typ) } } } func (t *Tree) parseFilter(tk item) Node { var subf, args, text string Loop: for { switch token := t.nextNonSpace(); token.typ { case itemFilterSubf: subf = token.val case itemFilterArgs: args = strings.Trim(token.val, " \t\r\n") case itemFilterText: text = strings.Trim(token.val, " \t\r\n") default: break Loop } } t.backup() switch tk.val { case "go": filterGo(subf, args, text) case "markdown", "markdown-it": // TODO: filterMarkdown(subf, args, text) } return t.newList(tk.pos) // for return nothing } func filterGo(subf, args, text string) { switch subf { case "func": Go.Name = "" switch args { case "name": Go.Name = text case "arg", "args": if Go.Args != "" { Go.Args += ", " + strings.Trim(text, "()") } else { Go.Args = strings.Trim(text, "()") } default: fn := strings.Split(text, "(") if len(fn) == 2 { Go.Name = strings.Trim(fn[0], " \t\n)") Go.Args = strings.Trim(fn[1], " \t\n)") } else { log.Fatal(":go:func filter error in " + text) } } case "import": Go.Import = text } } func (t *Tree) parseTag(tk item) Node { var ( deep = tk.depth tag = t.newTag(tk.pos, tk.val, tk.typ) ) Loop: for { switch token := t.nextNonSpace(); { case token.depth > deep: if tag.tagType == itemTagVoid || tag.tagType == itemTagVoidInline { break Loop } tag.append(t.hub(token)) case token.depth == deep: switch token.typ { case itemClass: tag.attr("class", `"`+token.val+`"`, false) case itemID: tag.attr("id", `"`+token.val+`"`, false) case itemAttrStart: t.parseAttributes(tag, `"`) case itemTagEnd: tag.tagType = itemTagVoid return tag default: break Loop } default: break Loop } } t.backup() return tag } type pAttr interface { attr(string, string, bool) } func (t *Tree) parseAttributes(tag pAttr, qw string) { var ( aname string equal bool unesc bool stack = make([]string, 0, 4) ) for { switch token := t.next(); token.typ { case itemAttrSpace: // skip case itemAttr: switch { case aname == "": aname = token.val case aname != "" && !equal: tag.attr(aname, qw+aname+qw, unesc) aname = token.val case aname != "" && equal: stack = append(stack, token.val) } case itemAttrEqual, itemAttrEqualUn: if token.typ == itemAttrEqual { unesc = false } else { unesc = true } equal = true switch len_stack := len(stack); { case len_stack == 0 && aname != "": // skip case len_stack > 1 && aname != "": tag.attr(aname, strings.Join(stack[:len(stack)-1], " "), unesc) aname = stack[len(stack)-1] stack = stack[:0] case len_stack == 1 && aname == "": aname = stack[0] stack = stack[:0] default: t.errorf("unexpected '='") } case itemAttrComma: equal = false switch len_stack := len(stack); { case len_stack > 0 && aname != "": tag.attr(aname, strings.Join(stack, " "), unesc) aname = "" stack = stack[:0] case len_stack == 0 && aname != "": tag.attr(aname, qw+aname+qw, unesc) aname = "" } case itemAttrEnd: switch len_stack := len(stack); { case len_stack > 0 && aname != "": tag.attr(aname, strings.Join(stack, " "), unesc) case len_stack > 0 && aname == "": for _, a := range stack { tag.attr(a, a, unesc) } case len_stack == 0 && aname != "": tag.attr(aname, qw+aname+qw, unesc) } return default: t.errorf("unexpected %s", token.val) } } } func (t *Tree) parseIf(tk item) Node { var ( deep = tk.depth cond = t.newCond(tk.pos, tk.val, tk.typ) ) Loop: for { switch token := t.nextNonSpace(); { case token.depth > deep: cond.append(t.hub(token)) case token.depth == deep: switch token.typ { case itemElse: ni := t.peek() if ni.typ == itemIf { token = t.next() cond.append(t.newCode(token.pos, token.val, itemElseIf)) } else { cond.append(t.newCode(token.pos, token.val, token.typ)) } default: break Loop } default: break Loop } } t.backup() return cond } func (t *Tree) parseFor(tk item) Node { var ( deep = tk.depth cond = t.newCond(tk.pos, tk.val, tk.typ) ) Loop: for { switch token := t.nextNonSpace(); { case token.depth > deep: cond.append(t.hub(token)) case token.depth == deep: if token.typ == itemElse { cond.condType = itemForIfNotContain cond.append(t.newCode(token.pos, token.val, itemForElse)) } else { break Loop } default: break Loop } } t.backup() return cond } func (t *Tree) parseCase(tk item) Node { var ( deep = tk.depth iCase = t.newCond(tk.pos, tk.val, tk.typ) ) for { if token := t.nextNonSpace(); token.depth > deep { switch token.typ { case itemCaseWhen, itemCaseDefault: iCase.append(t.newCode(token.pos, token.val, token.typ)) default: iCase.append(t.hub(token)) } } else { break } } t.backup() return iCase } func (t *Tree) parseMixin(tk item) *MixinNode { var ( deep = tk.depth mixin = t.newMixin(tk.pos) ) Loop: for { switch token := t.nextNonSpace(); { case token.depth > deep: mixin.append(t.hub(token)) case token.depth == deep: if token.typ == itemAttrStart { t.parseAttributes(mixin, "") } else { break Loop } default: break Loop } } t.backup() return mixin } func (t *Tree) parseMixinUse(tk item) Node { tMix, ok := t.mixin[tk.val] if !ok { t.errorf(`Mixin "%s" must be declared before use.`, tk.val) } var ( deep = tk.depth mixin = tMix.CopyMixin() ) Loop: for { switch token := t.nextNonSpace(); { case token.depth > deep: mixin.appendToBlock(t.hub(token)) case token.depth == deep: if token.typ == itemAttrStart { t.parseAttributes(mixin, "") } else { break Loop } default: break Loop } } t.backup() use := len(mixin.AttrName) tpl := len(tMix.AttrName) switch { case use < tpl: i := 0 diff := tpl - use mixin.AttrCode = append(mixin.AttrCode, make([]string, diff)...) // Extend slice for index := 0; index < diff; index++ { i = tpl - index - 1 if tMix.AttrName[i] != tMix.AttrCode[i] { mixin.AttrCode[i] = tMix.AttrCode[i] } else { mixin.AttrCode[i] = `""` } } mixin.AttrName = tMix.AttrName case use > tpl: if tpl <= 0 { break } if strings.HasPrefix(tMix.AttrName[tpl-1], "...") { mixin.AttrRest = mixin.AttrCode[tpl-1:] } mixin.AttrCode = mixin.AttrCode[:tpl] mixin.AttrName = tMix.AttrName case use == tpl: mixin.AttrName = tMix.AttrName } return mixin } func (t *Tree) parseBlock(tk item) *BlockNode { block := t.newList(tk.pos) for { token := t.nextNonSpace() if token.depth > tk.depth { block.append(t.hub(token)) } else { break } } t.backup() var suf string switch tk.typ { case itemBlockPrepend: suf = "_prepend" case itemBlockAppend: suf = "_append" } t.block[tk.val+suf] = block return t.newBlock(tk.pos, tk.val, tk.typ) } func (t *Tree) parseInclude(tk item) *ListNode { switch ext := filepath.Ext(tk.val); ext { case ".jade", ".pug", "": return t.parseSubFile(tk.val) case ".js", ".css", ".tpl", ".md": ln := t.newList(tk.pos) ln.append(t.newText(tk.pos, t.read(tk.val), itemText)) return ln default: t.errorf(`file extension is not supported`) return nil } } func (t *Tree) parseSubFile(path string) *ListNode { // log.Println("subtemplate: " + path) currentTmplDir, _ := filepath.Split(t.Name) var incTree = New(currentTmplDir + path) incTree.block = t.block incTree.mixin = t.mixin incTree.ReadFunc = t.ReadFunc _, err := incTree.Parse(t.read(path)) if err != nil { d, _ := os.Getwd() t.errorf(`in '%s' subtemplate '%s': parseSubFile() error: %s`, d, path, err) } return incTree.Root } var extensions = [...]string{".jade", ".pug", ".js", ".css", ".tpl", ".md"} func (t *Tree) read(path string) []byte { currentTmplDir, _ := filepath.Split(t.Name) path = currentTmplDir + path var ( bb []byte err error ) ext := filepath.Ext(path) for _, s := range extensions { if s == ext { bb, err = t.ReadFunc(path) break } if bb, err = t.ReadFunc(path + s); err == nil { break } } if err != nil { wd, _ := os.Getwd() t.errorf(`%s work dir: %s `, err, wd) } return bb }