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 }