github.com/gofiber/pug@v1.0.1/jade_parse.go (about)

     1  package jade
     2  
     3  import (
     4  	"io/ioutil"
     5  	"log"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  )
    10  
    11  func (t *Tree) topParse() {
    12  	t.Root = t.newList(t.peek().pos)
    13  	var (
    14  		ext   bool
    15  		token = t.nextNonSpace()
    16  	)
    17  	if token.typ == itemExtends {
    18  		ext = true
    19  		t.Root.append(t.parseSubFile(token.val))
    20  		token = t.nextNonSpace()
    21  	}
    22  	for {
    23  		switch token.typ {
    24  		case itemInclude:
    25  			t.Root.append(t.parseInclude(token))
    26  		case itemBlock, itemBlockPrepend, itemBlockAppend:
    27  			if ext {
    28  				t.parseBlock(token)
    29  			} else {
    30  				t.Root.append(t.parseBlock(token))
    31  			}
    32  		case itemMixin:
    33  			t.mixin[token.val] = t.parseMixin(token)
    34  		case itemEOF:
    35  			return
    36  		case itemExtends:
    37  			t.errorf(`Declaration of template inheritance ("extends") should be the first thing in the file. There can only be one extends statement per file.`)
    38  		case itemError:
    39  			t.errorf("%s line: %d\n", token.val, token.line)
    40  		default:
    41  			if ext {
    42  				t.errorf(`Only import, named blocks and mixins can appear at the top level of an extending template`)
    43  			}
    44  			t.Root.append(t.hub(token))
    45  		}
    46  		token = t.nextNonSpace()
    47  	}
    48  }
    49  
    50  func (t *Tree) hub(token item) (n Node) {
    51  	for {
    52  		switch token.typ {
    53  		case itemDiv:
    54  			token.val = "div"
    55  			fallthrough
    56  		case itemTag, itemTagInline, itemTagVoid, itemTagVoidInline:
    57  			return t.parseTag(token)
    58  		case itemText, itemComment, itemHTMLTag:
    59  			return t.newText(token.pos, []byte(token.val), token.typ)
    60  		case itemCode, itemCodeBuffered, itemCodeUnescaped, itemMixinBlock:
    61  			return t.newCode(token.pos, token.val, token.typ)
    62  		case itemIf, itemUnless:
    63  			return t.parseIf(token)
    64  		case itemFor, itemEach, itemWhile:
    65  			return t.parseFor(token)
    66  		case itemCase:
    67  			return t.parseCase(token)
    68  		case itemBlock, itemBlockPrepend, itemBlockAppend:
    69  			return t.parseBlock(token)
    70  		case itemMixinCall:
    71  			return t.parseMixinUse(token)
    72  		case itemInclude:
    73  			return t.parseInclude(token)
    74  		case itemDoctype:
    75  			return t.newDoctype(token.pos, token.val)
    76  		case itemFilter:
    77  			return t.parseFilter(token)
    78  		case itemError:
    79  			t.errorf("Error lex: %s line: %d\n", token.val, token.line)
    80  		default:
    81  			t.errorf(`Error hub(): unexpected token  "%s"  type  "%s"`, token.val, token.typ)
    82  		}
    83  	}
    84  }
    85  
    86  func (t *Tree) parseFilter(tk item) Node {
    87  	var subf, args, text string
    88  Loop:
    89  	for {
    90  		switch token := t.nextNonSpace(); token.typ {
    91  		case itemFilterSubf:
    92  			subf = token.val
    93  		case itemFilterArgs:
    94  			args = strings.Trim(token.val, " \t\r\n")
    95  		case itemFilterText:
    96  			text = strings.Trim(token.val, " \t\r\n")
    97  		default:
    98  			break Loop
    99  		}
   100  	}
   101  	t.backup()
   102  	switch tk.val {
   103  	case "go":
   104  		filterGo(subf, args, text)
   105  	case "markdown", "markdown-it":
   106  		// TODO: filterMarkdown(subf, args, text)
   107  	}
   108  	return t.newList(tk.pos) // for return nothing
   109  }
   110  
   111  func filterGo(subf, args, text string) {
   112  	switch subf {
   113  	case "func":
   114  		Go.Name = ""
   115  		switch args {
   116  		case "name":
   117  			Go.Name = text
   118  		case "arg", "args":
   119  			if Go.Args != "" {
   120  				Go.Args += ", " + strings.Trim(text, "()")
   121  			} else {
   122  				Go.Args = strings.Trim(text, "()")
   123  			}
   124  		default:
   125  			fn := strings.Split(text, "(")
   126  			if len(fn) == 2 {
   127  				Go.Name = strings.Trim(fn[0], " \t\n)")
   128  				Go.Args = strings.Trim(fn[1], " \t\n)")
   129  			} else {
   130  				log.Fatal(":go:func filter error in " + text)
   131  			}
   132  		}
   133  	case "import":
   134  		Go.Import = text
   135  	}
   136  }
   137  
   138  func (t *Tree) parseTag(tk item) Node {
   139  	var (
   140  		deep = tk.depth
   141  		tag  = t.newTag(tk.pos, tk.val, tk.typ)
   142  	)
   143  Loop:
   144  	for {
   145  		switch token := t.nextNonSpace(); {
   146  		case token.depth > deep:
   147  			if tag.tagType == itemTagVoid || tag.tagType == itemTagVoidInline {
   148  				break Loop
   149  			}
   150  			tag.append(t.hub(token))
   151  		case token.depth == deep:
   152  			switch token.typ {
   153  			case itemClass:
   154  				tag.attr("class", `"`+token.val+`"`, false)
   155  			case itemID:
   156  				tag.attr("id", `"`+token.val+`"`, false)
   157  			case itemAttrStart:
   158  				t.parseAttributes(tag, `"`)
   159  			case itemTagEnd:
   160  				tag.tagType = itemTagVoid
   161  				return tag
   162  			default:
   163  				break Loop
   164  			}
   165  		default:
   166  			break Loop
   167  		}
   168  	}
   169  	t.backup()
   170  	return tag
   171  }
   172  
   173  type pAttr interface {
   174  	attr(string, string, bool)
   175  }
   176  
   177  func (t *Tree) parseAttributes(tag pAttr, qw string) {
   178  	var (
   179  		aname string
   180  		equal bool
   181  		unesc bool
   182  		stack = make([]string, 0, 4)
   183  	)
   184  	for {
   185  		switch token := t.next(); token.typ {
   186  		case itemAttrSpace:
   187  			// skip
   188  		case itemAttr:
   189  			switch {
   190  			case aname == "":
   191  				aname = token.val
   192  			case aname != "" && !equal:
   193  				tag.attr(aname, qw+aname+qw, unesc)
   194  				aname = token.val
   195  			case aname != "" && equal:
   196  				stack = append(stack, token.val)
   197  			}
   198  		case itemAttrEqual, itemAttrEqualUn:
   199  			if token.typ == itemAttrEqual {
   200  				unesc = false
   201  			} else {
   202  				unesc = true
   203  			}
   204  			equal = true
   205  			switch len_stack := len(stack); {
   206  			case len_stack == 0 && aname != "":
   207  				// skip
   208  			case len_stack > 1 && aname != "":
   209  				tag.attr(aname, strings.Join(stack[:len(stack)-1], " "), unesc)
   210  
   211  				aname = stack[len(stack)-1]
   212  				stack = stack[:0]
   213  			case len_stack == 1 && aname == "":
   214  				aname = stack[0]
   215  				stack = stack[:0]
   216  			default:
   217  				t.errorf("unexpected '='")
   218  			}
   219  		case itemAttrComma:
   220  			equal = false
   221  			switch len_stack := len(stack); {
   222  			case len_stack > 0 && aname != "":
   223  				tag.attr(aname, strings.Join(stack, " "), unesc)
   224  				aname = ""
   225  				stack = stack[:0]
   226  			case len_stack == 0 && aname != "":
   227  				tag.attr(aname, qw+aname+qw, unesc)
   228  				aname = ""
   229  			}
   230  		case itemAttrEnd:
   231  			switch len_stack := len(stack); {
   232  			case len_stack > 0 && aname != "":
   233  				tag.attr(aname, strings.Join(stack, " "), unesc)
   234  			case len_stack > 0 && aname == "":
   235  				for _, a := range stack {
   236  					tag.attr(a, a, unesc)
   237  				}
   238  			case len_stack == 0 && aname != "":
   239  				tag.attr(aname, qw+aname+qw, unesc)
   240  			}
   241  			return
   242  		default:
   243  			t.errorf("unexpected %s", token.val)
   244  		}
   245  	}
   246  }
   247  
   248  func (t *Tree) parseIf(tk item) Node {
   249  	var (
   250  		deep = tk.depth
   251  		cond = t.newCond(tk.pos, tk.val, tk.typ)
   252  	)
   253  Loop:
   254  	for {
   255  		switch token := t.nextNonSpace(); {
   256  		case token.depth > deep:
   257  			cond.append(t.hub(token))
   258  		case token.depth == deep:
   259  			switch token.typ {
   260  			case itemElse:
   261  				ni := t.peek()
   262  				if ni.typ == itemIf {
   263  					token = t.next()
   264  					cond.append(t.newCode(token.pos, token.val, itemElseIf))
   265  				} else {
   266  					cond.append(t.newCode(token.pos, token.val, token.typ))
   267  				}
   268  			default:
   269  				break Loop
   270  			}
   271  		default:
   272  			break Loop
   273  		}
   274  	}
   275  	t.backup()
   276  	return cond
   277  }
   278  
   279  func (t *Tree) parseFor(tk item) Node {
   280  	var (
   281  		deep = tk.depth
   282  		cond = t.newCond(tk.pos, tk.val, tk.typ)
   283  	)
   284  Loop:
   285  	for {
   286  		switch token := t.nextNonSpace(); {
   287  		case token.depth > deep:
   288  			cond.append(t.hub(token))
   289  		case token.depth == deep:
   290  			if token.typ == itemElse {
   291  				cond.condType = itemForIfNotContain
   292  				cond.append(t.newCode(token.pos, token.val, itemForElse))
   293  			} else {
   294  				break Loop
   295  			}
   296  		default:
   297  			break Loop
   298  		}
   299  	}
   300  	t.backup()
   301  	return cond
   302  }
   303  
   304  func (t *Tree) parseCase(tk item) Node {
   305  	var (
   306  		deep  = tk.depth
   307  		iCase = t.newCond(tk.pos, tk.val, tk.typ)
   308  	)
   309  	for {
   310  		if token := t.nextNonSpace(); token.depth > deep {
   311  			switch token.typ {
   312  			case itemCaseWhen, itemCaseDefault:
   313  				iCase.append(t.newCode(token.pos, token.val, token.typ))
   314  			default:
   315  				iCase.append(t.hub(token))
   316  			}
   317  		} else {
   318  			break
   319  		}
   320  	}
   321  	t.backup()
   322  	return iCase
   323  }
   324  
   325  func (t *Tree) parseMixin(tk item) *MixinNode {
   326  	var (
   327  		deep  = tk.depth
   328  		mixin = t.newMixin(tk.pos)
   329  	)
   330  Loop:
   331  	for {
   332  		switch token := t.nextNonSpace(); {
   333  		case token.depth > deep:
   334  			mixin.append(t.hub(token))
   335  		case token.depth == deep:
   336  			if token.typ == itemAttrStart {
   337  				t.parseAttributes(mixin, "")
   338  			} else {
   339  				break Loop
   340  			}
   341  		default:
   342  			break Loop
   343  		}
   344  	}
   345  	t.backup()
   346  	return mixin
   347  }
   348  
   349  func (t *Tree) parseMixinUse(tk item) Node {
   350  	tMix, ok := t.mixin[tk.val]
   351  	if !ok {
   352  		t.errorf(`Mixin "%s" must be declared before use.`, tk.val)
   353  	}
   354  	var (
   355  		deep  = tk.depth
   356  		mixin = tMix.CopyMixin()
   357  	)
   358  Loop:
   359  	for {
   360  		switch token := t.nextNonSpace(); {
   361  		case token.depth > deep:
   362  			mixin.appendToBlock(t.hub(token))
   363  		case token.depth == deep:
   364  			if token.typ == itemAttrStart {
   365  				t.parseAttributes(mixin, "")
   366  			} else {
   367  				break Loop
   368  			}
   369  		default:
   370  			break Loop
   371  		}
   372  	}
   373  	t.backup()
   374  
   375  	use := len(mixin.AttrName)
   376  	tpl := len(tMix.AttrName)
   377  	switch {
   378  	case use < tpl:
   379  		i := 0
   380  		diff := tpl - use
   381  		mixin.AttrCode = append(mixin.AttrCode, make([]string, diff)...) // Extend slice
   382  		for index := 0; index < diff; index++ {
   383  			i = tpl - index - 1
   384  			if tMix.AttrName[i] != tMix.AttrCode[i] {
   385  				mixin.AttrCode[i] = tMix.AttrCode[i]
   386  			} else {
   387  				mixin.AttrCode[i] = `""`
   388  			}
   389  		}
   390  		mixin.AttrName = tMix.AttrName
   391  	case use > tpl:
   392  		if tpl <= 0 {
   393  			break
   394  		}
   395  		if strings.HasPrefix(tMix.AttrName[tpl-1], "...") {
   396  			mixin.AttrRest = mixin.AttrCode[tpl-1:]
   397  		}
   398  		mixin.AttrCode = mixin.AttrCode[:tpl]
   399  		mixin.AttrName = tMix.AttrName
   400  	case use == tpl:
   401  		mixin.AttrName = tMix.AttrName
   402  	}
   403  	return mixin
   404  }
   405  
   406  func (t *Tree) parseBlock(tk item) *BlockNode {
   407  	block := t.newList(tk.pos)
   408  	for {
   409  		token := t.nextNonSpace()
   410  		if token.depth > tk.depth {
   411  			block.append(t.hub(token))
   412  		} else {
   413  			break
   414  		}
   415  	}
   416  	t.backup()
   417  	var suf string
   418  	switch tk.typ {
   419  	case itemBlockPrepend:
   420  		suf = "_prepend"
   421  	case itemBlockAppend:
   422  		suf = "_append"
   423  	}
   424  	t.block[tk.val+suf] = block
   425  	return t.newBlock(tk.pos, tk.val, tk.typ)
   426  }
   427  
   428  func (t *Tree) parseInclude(tk item) *ListNode {
   429  	switch ext := filepath.Ext(tk.val); ext {
   430  	case ".jade", ".pug", "":
   431  		return t.parseSubFile(tk.val)
   432  	case ".js", ".css", ".tpl", ".md":
   433  		ln := t.newList(tk.pos)
   434  		ln.append(t.newText(tk.pos, t.read(tk.val), itemText))
   435  		return ln
   436  	default:
   437  		t.errorf(`file extension is not supported`)
   438  		return nil
   439  	}
   440  }
   441  
   442  func (t *Tree) parseSubFile(path string) *ListNode {
   443  	// log.Println("subtemplate: " + path)
   444  	var incTree = New(path)
   445  	incTree.block = t.block
   446  	incTree.mixin = t.mixin
   447  	wd, _ := os.Getwd()
   448  
   449  	dir, file := filepath.Split(path)
   450  	if dir != "" && dir != "./" {
   451  		os.Chdir(dir)
   452  	}
   453  
   454  	_, err := incTree.Parse(t.read(file))
   455  	if err != nil {
   456  		d, _ := os.Getwd()
   457  		t.errorf(`in '%s' subtemplate '%s': parseSubFile() error: %s`, d, path, err)
   458  	}
   459  
   460  	os.Chdir(wd)
   461  	return incTree.Root
   462  }
   463  
   464  func (t *Tree) read(path string) []byte {
   465  	var (
   466  		bb  []byte
   467  		ext string
   468  		err error
   469  	)
   470  	switch ext = filepath.Ext(path); ext {
   471  	case ".jade", ".pug", ".js", ".css", ".tpl", ".md":
   472  		bb, err = ioutil.ReadFile(path)
   473  	case "":
   474  		if _, err = os.Stat(path + ".jade"); os.IsNotExist(err) {
   475  			if _, err = os.Stat(path + ".pug"); os.IsNotExist(err) {
   476  				wd, _ := os.Getwd()
   477  				t.errorf("in '%s' subtemplate '%s': file path error: '.jade' or '.pug' file required", wd, path)
   478  			} else {
   479  				ext = ".pug"
   480  			}
   481  		} else {
   482  			ext = ".jade"
   483  		}
   484  		bb, err = ioutil.ReadFile(path + ext)
   485  	default:
   486  		t.errorf(`file extension  %s  is not supported`, ext)
   487  	}
   488  	if err != nil {
   489  		wd, _ := os.Getwd()
   490  		t.errorf(`%s  work dir: %s `, err, wd)
   491  	}
   492  
   493  	return bb
   494  }