123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360 |
- package parser
- import (
- "regexp"
- "github.com/aymerick/raymond/ast"
- )
- // whitespaceVisitor walks through the AST to perform whitespace control
- //
- // The logic was shamelessly borrowed from:
- // https://github.com/wycats/handlebars.js/blob/master/lib/handlebars/compiler/whitespace-control.js
- type whitespaceVisitor struct {
- isRootSeen bool
- }
- var (
- rTrimLeft = regexp.MustCompile(`^[ \t]*\r?\n?`)
- rTrimLeftMultiple = regexp.MustCompile(`^\s+`)
- rTrimRight = regexp.MustCompile(`[ \t]+$`)
- rTrimRightMultiple = regexp.MustCompile(`\s+$`)
- rPrevWhitespace = regexp.MustCompile(`\r?\n\s*?$`)
- rPrevWhitespaceStart = regexp.MustCompile(`(^|\r?\n)\s*?$`)
- rNextWhitespace = regexp.MustCompile(`^\s*?\r?\n`)
- rNextWhitespaceEnd = regexp.MustCompile(`^\s*?(\r?\n|$)`)
- rPartialIndent = regexp.MustCompile(`([ \t]+$)`)
- )
- // newWhitespaceVisitor instanciates a new whitespaceVisitor
- func newWhitespaceVisitor() *whitespaceVisitor {
- return &whitespaceVisitor{}
- }
- // processWhitespaces performs whitespace control on given AST
- //
- // WARNING: It must be called only once on AST.
- func processWhitespaces(node ast.Node) {
- node.Accept(newWhitespaceVisitor())
- }
- func omitRightFirst(body []ast.Node, multiple bool) {
- omitRight(body, -1, multiple)
- }
- func omitRight(body []ast.Node, i int, multiple bool) {
- if i+1 >= len(body) {
- return
- }
- current := body[i+1]
- node, ok := current.(*ast.ContentStatement)
- if !ok {
- return
- }
- if !multiple && node.RightStripped {
- return
- }
- original := node.Value
- r := rTrimLeft
- if multiple {
- r = rTrimLeftMultiple
- }
- node.Value = r.ReplaceAllString(node.Value, "")
- node.RightStripped = (original != node.Value)
- }
- func omitLeftLast(body []ast.Node, multiple bool) {
- omitLeft(body, len(body), multiple)
- }
- func omitLeft(body []ast.Node, i int, multiple bool) bool {
- if i-1 < 0 {
- return false
- }
- current := body[i-1]
- node, ok := current.(*ast.ContentStatement)
- if !ok {
- return false
- }
- if !multiple && node.LeftStripped {
- return false
- }
- original := node.Value
- r := rTrimRight
- if multiple {
- r = rTrimRightMultiple
- }
- node.Value = r.ReplaceAllString(node.Value, "")
- node.LeftStripped = (original != node.Value)
- return node.LeftStripped
- }
- func isPrevWhitespace(body []ast.Node) bool {
- return isPrevWhitespaceProgram(body, len(body), false)
- }
- func isPrevWhitespaceProgram(body []ast.Node, i int, isRoot bool) bool {
- if i < 1 {
- return isRoot
- }
- prev := body[i-1]
- if node, ok := prev.(*ast.ContentStatement); ok {
- if (node.Value == "") && node.RightStripped {
- // already stripped, so it may be an empty string not catched by regexp
- return true
- }
- r := rPrevWhitespaceStart
- if (i > 1) || !isRoot {
- r = rPrevWhitespace
- }
- return r.MatchString(node.Value)
- }
- return false
- }
- func isNextWhitespace(body []ast.Node) bool {
- return isNextWhitespaceProgram(body, -1, false)
- }
- func isNextWhitespaceProgram(body []ast.Node, i int, isRoot bool) bool {
- if i+1 >= len(body) {
- return isRoot
- }
- next := body[i+1]
- if node, ok := next.(*ast.ContentStatement); ok {
- if (node.Value == "") && node.LeftStripped {
- // already stripped, so it may be an empty string not catched by regexp
- return true
- }
- r := rNextWhitespaceEnd
- if (i+2 > len(body)) || !isRoot {
- r = rNextWhitespace
- }
- return r.MatchString(node.Value)
- }
- return false
- }
- //
- // Visitor interface
- //
- func (v *whitespaceVisitor) VisitProgram(program *ast.Program) interface{} {
- isRoot := !v.isRootSeen
- v.isRootSeen = true
- body := program.Body
- for i, current := range body {
- strip, _ := current.Accept(v).(*ast.Strip)
- if strip == nil {
- continue
- }
- _isPrevWhitespace := isPrevWhitespaceProgram(body, i, isRoot)
- _isNextWhitespace := isNextWhitespaceProgram(body, i, isRoot)
- openStandalone := strip.OpenStandalone && _isPrevWhitespace
- closeStandalone := strip.CloseStandalone && _isNextWhitespace
- inlineStandalone := strip.InlineStandalone && _isPrevWhitespace && _isNextWhitespace
- if strip.Close {
- omitRight(body, i, true)
- }
- if strip.Open && (i > 0) {
- omitLeft(body, i, true)
- }
- if inlineStandalone {
- omitRight(body, i, false)
- if omitLeft(body, i, false) {
- // If we are on a standalone node, save the indent info for partials
- if partial, ok := current.(*ast.PartialStatement); ok {
- // Pull out the whitespace from the final line
- if i > 0 {
- if prevContent, ok := body[i-1].(*ast.ContentStatement); ok {
- partial.Indent = rPartialIndent.FindString(prevContent.Original)
- }
- }
- }
- }
- }
- if b, ok := current.(*ast.BlockStatement); ok {
- if openStandalone {
- prog := b.Program
- if prog == nil {
- prog = b.Inverse
- }
- omitRightFirst(prog.Body, false)
- // Strip out the previous content node if it's whitespace only
- omitLeft(body, i, false)
- }
- if closeStandalone {
- prog := b.Inverse
- if prog == nil {
- prog = b.Program
- }
- // Always strip the next node
- omitRight(body, i, false)
- omitLeftLast(prog.Body, false)
- }
- }
- }
- return nil
- }
- func (v *whitespaceVisitor) VisitBlock(block *ast.BlockStatement) interface{} {
- if block.Program != nil {
- block.Program.Accept(v)
- }
- if block.Inverse != nil {
- block.Inverse.Accept(v)
- }
- program := block.Program
- inverse := block.Inverse
- if program == nil {
- program = inverse
- inverse = nil
- }
- firstInverse := inverse
- lastInverse := inverse
- if (inverse != nil) && inverse.Chained {
- b, _ := inverse.Body[0].(*ast.BlockStatement)
- firstInverse = b.Program
- for lastInverse.Chained {
- b, _ := lastInverse.Body[len(lastInverse.Body)-1].(*ast.BlockStatement)
- lastInverse = b.Program
- }
- }
- closeProg := firstInverse
- if closeProg == nil {
- closeProg = program
- }
- strip := &ast.Strip{
- Open: (block.OpenStrip != nil) && block.OpenStrip.Open,
- Close: (block.CloseStrip != nil) && block.CloseStrip.Close,
- OpenStandalone: isNextWhitespace(program.Body),
- CloseStandalone: isPrevWhitespace(closeProg.Body),
- }
- if (block.OpenStrip != nil) && block.OpenStrip.Close {
- omitRightFirst(program.Body, true)
- }
- if inverse != nil {
- if block.InverseStrip != nil {
- inverseStrip := block.InverseStrip
- if inverseStrip.Open {
- omitLeftLast(program.Body, true)
- }
- if inverseStrip.Close {
- omitRightFirst(firstInverse.Body, true)
- }
- }
- if (block.CloseStrip != nil) && block.CloseStrip.Open {
- omitLeftLast(lastInverse.Body, true)
- }
- // Find standalone else statements
- if isPrevWhitespace(program.Body) && isNextWhitespace(firstInverse.Body) {
- omitLeftLast(program.Body, false)
- omitRightFirst(firstInverse.Body, false)
- }
- } else if (block.CloseStrip != nil) && block.CloseStrip.Open {
- omitLeftLast(program.Body, true)
- }
- return strip
- }
- func (v *whitespaceVisitor) VisitMustache(mustache *ast.MustacheStatement) interface{} {
- return mustache.Strip
- }
- func _inlineStandalone(strip *ast.Strip) interface{} {
- return &ast.Strip{
- Open: strip.Open,
- Close: strip.Close,
- InlineStandalone: true,
- }
- }
- func (v *whitespaceVisitor) VisitPartial(node *ast.PartialStatement) interface{} {
- strip := node.Strip
- if strip == nil {
- strip = &ast.Strip{}
- }
- return _inlineStandalone(strip)
- }
- func (v *whitespaceVisitor) VisitComment(node *ast.CommentStatement) interface{} {
- strip := node.Strip
- if strip == nil {
- strip = &ast.Strip{}
- }
- return _inlineStandalone(strip)
- }
- // NOOP
- func (v *whitespaceVisitor) VisitContent(node *ast.ContentStatement) interface{} { return nil }
- func (v *whitespaceVisitor) VisitExpression(node *ast.Expression) interface{} { return nil }
- func (v *whitespaceVisitor) VisitSubExpression(node *ast.SubExpression) interface{} { return nil }
- func (v *whitespaceVisitor) VisitPath(node *ast.PathExpression) interface{} { return nil }
- func (v *whitespaceVisitor) VisitString(node *ast.StringLiteral) interface{} { return nil }
- func (v *whitespaceVisitor) VisitBoolean(node *ast.BooleanLiteral) interface{} { return nil }
- func (v *whitespaceVisitor) VisitNumber(node *ast.NumberLiteral) interface{} { return nil }
- func (v *whitespaceVisitor) VisitHash(node *ast.Hash) interface{} { return nil }
- func (v *whitespaceVisitor) VisitHashPair(node *ast.HashPair) interface{} { return nil }
|