github.com/keysonZZZ/kmg@v0.0.0-20151121023212-05317bfd7d39/kmgView/kmgGoTpl/kmgGoTpl.go (about)

     1  package kmgGoTpl
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"github.com/bronze1man/kmg/kmgCache"
     7  	"github.com/bronze1man/kmg/kmgConfig"
     8  	"github.com/bronze1man/kmg/kmgErr"
     9  	"github.com/bronze1man/kmg/kmgFile"
    10  	"go/format"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  )
    15  
    16  type scope int
    17  
    18  const (
    19  	currentScopeStatement  scope = 1
    20  	currentScopeExpression scope = 2
    21  	currentScopeTpl        scope = 3
    22  )
    23  
    24  func BuildTplOneFile(in []byte, isHtml bool) (out []byte, err error) {
    25  	var transformer transformer
    26  	transformer.isHtml = isHtml
    27  	err = kmgErr.PanicToError(func() {
    28  		out = transformer.mustTransform(in)
    29  	})
    30  	if err != nil {
    31  		return nil, fmt.Errorf(":%d %s", transformer.lineNum, err)
    32  	}
    33  	return out, err
    34  }
    35  
    36  func MustBuildTplInDir(path string) {
    37  	pathList := kmgFile.MustGetAllFiles(path)
    38  	MustBuildTplInPathList(pathList)
    39  }
    40  
    41  func MustBuildTplInPathList(pathList []string) {
    42  	for _, val := range pathList {
    43  		ext := filepath.Ext(val)
    44  		if ext == ".gotpl" {
    45  			out, err := BuildTplOneFile(kmgFile.MustReadFile(val), false)
    46  			if err != nil {
    47  				panic(fmt.Sprintf("%s %s", val, err.Error()))
    48  			}
    49  			outFilePath := kmgFile.PathTrimExt(val) + ".go"
    50  			kmgFile.MustWriteFile(outFilePath, out)
    51  		} else if ext == ".gotplhtml" {
    52  			out, err := BuildTplOneFile(kmgFile.MustReadFile(val), true)
    53  			if err != nil {
    54  				panic(fmt.Sprintf("%s %s", val, err.Error()))
    55  			}
    56  			outFilePath := kmgFile.PathTrimExt(val) + ".go"
    57  			kmgFile.MustWriteFile(outFilePath, out)
    58  		}
    59  	}
    60  }
    61  
    62  // 此处路径表示 项目里面的一个路径
    63  func MustBuildTplInDirWithCache(root string) {
    64  	root = kmgConfig.DefaultEnv().PathInProject(root)
    65  	cachedFileList := cacheFileFilter(root)
    66  	kmgCache.MustMd5FileChangeCache("kmgGoTpl_"+root, cachedFileList, func() {
    67  		MustBuildTplInPathList(cachedFileList)
    68  	})
    69  }
    70  
    71  // 此处返回所有需要进行缓存的文件
    72  func cacheFileFilter(root string) []string {
    73  	output := make([]string, 0, 2048)
    74  	err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
    75  		if err != nil {
    76  			return err
    77  		}
    78  		if info.IsDir() {
    79  			return nil
    80  		}
    81  		ext := filepath.Ext(path)
    82  		if ext == ".gotpl" || ext == ".gotplhtml" {
    83  			output = append(output, path)
    84  			output = append(output, kmgFile.PathTrimExt(path)+".go")
    85  		}
    86  		return nil
    87  	})
    88  	if err != nil {
    89  		panic(err)
    90  	}
    91  	return output
    92  }
    93  
    94  type transformer struct {
    95  	isHtml bool
    96  
    97  	in           []byte
    98  	pos          int
    99  	lineNum      int
   100  	currentScope scope
   101  	lastScopeBuf bytes.Buffer
   102  	outBuf       bytes.Buffer
   103  	bracesLevel  int //大括号的层数
   104  
   105  	//函数是否横跨?>分析
   106  	isLastFuncTokenWithoutMatchBraces bool
   107  	isFuncOpenInScope                 bool //在当前这个scope里面是否有一个函数在里面打开
   108  	hasFuncOpenBetweenScope           bool //是否在scope之间有一个函数正在打开?
   109  	lastFuncBraceLevel                int
   110  
   111  	hasBytesPackage  bool
   112  	hasKmgXssPackage bool
   113  
   114  	// xss 目前简单采用排除法解决这个问题,很少使用的不考虑,并且信任写模板的人. 不是script,不是urlv 就是kmgXss.H
   115  	isInScript        bool
   116  	isLastScriptToken bool
   117  	urlvStatus        urlvStatus
   118  }
   119  
   120  type urlvStatus int
   121  
   122  const (
   123  	urlvStatusNot      urlvStatus = 0
   124  	urlvStatusQuestion urlvStatus = 1
   125  	urlvStatusKey      urlvStatus = 2
   126  	urlvStatusEqual    urlvStatus = 3
   127  	urlvStatusValue    urlvStatus = 4
   128  	urlvStatusAndSign  urlvStatus = 5
   129  )
   130  
   131  func (t *transformer) mustTransform(in []byte) []byte {
   132  	t.in = in
   133  	t.currentScope = currentScopeTpl
   134  	t.lastFuncBraceLevel = -1
   135  	t.lineNum = 1
   136  	for t.pos = 0; t.pos < len(t.in); t.pos++ {
   137  		if t.isMatchString("<?=") {
   138  			if t.currentScope != currentScopeTpl {
   139  				panic("<? and ?> not match")
   140  			}
   141  			t.endTplScope()
   142  			t.currentScope = currentScopeExpression
   143  			t.pos += 2
   144  			continue
   145  		} else if t.isMatchString("<?") {
   146  			if t.currentScope != currentScopeTpl {
   147  				panic("<? and ?> not match")
   148  			}
   149  			t.endTplScope()
   150  			t.currentScope = currentScopeStatement
   151  			t.pos += 1
   152  			continue
   153  		} else if t.isMatchString("?>") {
   154  			if t.currentScope == currentScopeTpl {
   155  				panic("<? and ?> not match")
   156  			}
   157  			t.endNotTplScope()
   158  			t.hasBytesPackage = true
   159  			t.currentScope = currentScopeTpl
   160  			t.pos += 1
   161  			continue
   162  		}
   163  		if t.currentScope == currentScopeStatement {
   164  			if t.isMatchString("func ") {
   165  				if t.isLastFuncTokenWithoutMatchBraces {
   166  					panic("func and { not match")
   167  				}
   168  				t.isLastFuncTokenWithoutMatchBraces = true
   169  			} else if in[t.pos] == '{' {
   170  				t.bracesLevel += 1
   171  				if t.isLastFuncTokenWithoutMatchBraces {
   172  					//函数的打开符
   173  					t.isLastFuncTokenWithoutMatchBraces = false
   174  					t.lastFuncBraceLevel = t.bracesLevel
   175  					t.isFuncOpenInScope = true
   176  				}
   177  			} else if in[t.pos] == '}' {
   178  				t.bracesLevel -= 1
   179  				if t.bracesLevel < 0 {
   180  					panic("{ and } not match")
   181  				}
   182  				if t.lastFuncBraceLevel == t.bracesLevel+1 {
   183  					t.lastFuncBraceLevel = -1
   184  					t.isFuncOpenInScope = false
   185  					//函数的关闭符
   186  					if t.hasFuncOpenBetweenScope {
   187  						t.lastScopeBuf.WriteString("return _buf.String()\n")
   188  						t.hasFuncOpenBetweenScope = false
   189  					}
   190  				}
   191  			}
   192  		} else if t.currentScope == currentScopeTpl {
   193  			if t.isHtml {
   194  				// script
   195  				if t.isMatchString("<script") { //暂时只支持小写
   196  					if t.isLastScriptToken {
   197  						panic("<script and > not match")
   198  					}
   199  					t.isLastScriptToken = true
   200  				} else if t.isMatchString("</script>") {
   201  					t.isInScript = false
   202  				} else if in[t.pos] == '>' {
   203  					if t.isLastScriptToken {
   204  						t.isLastScriptToken = false
   205  						t.isInScript = true
   206  					}
   207  				}
   208  				// urlv
   209  				if in[t.pos] == '?' {
   210  					// 忽略开头的状态错误
   211  					t.urlvStatus = urlvStatusQuestion
   212  				} else if t.urlvStatus == urlvStatusQuestion { //此处恰好是对的,后面不会匹配到?
   213  					if isUrlvChar(in[t.pos]) {
   214  						t.urlvStatus = urlvStatusKey
   215  					} else {
   216  						t.urlvStatus = urlvStatusNot //状态计算错误,忽略本次匹配
   217  					}
   218  				} else if t.urlvStatus == urlvStatusKey {
   219  					if isUrlvChar(in[t.pos]) {
   220  						t.urlvStatus = urlvStatusKey
   221  					} else if in[t.pos] == '=' {
   222  						t.urlvStatus = urlvStatusEqual
   223  					} else {
   224  						t.urlvStatus = urlvStatusNot //状态计算错误,忽略本次匹配
   225  					}
   226  				} else if t.urlvStatus == urlvStatusEqual {
   227  					if isUrlvChar(in[t.pos]) {
   228  						t.urlvStatus = urlvStatusValue
   229  					} else {
   230  						t.urlvStatus = urlvStatusNot //状态计算错误,忽略本次匹配
   231  					}
   232  				} else if t.urlvStatus == urlvStatusValue {
   233  					if isUrlvChar(in[t.pos]) {
   234  						t.urlvStatus = urlvStatusValue
   235  					} else if in[t.pos] == '&' {
   236  						t.urlvStatus = urlvStatusAndSign
   237  					} else {
   238  						t.urlvStatus = urlvStatusNot //状态计算错误,忽略本次匹配
   239  					}
   240  				} else if t.urlvStatus == urlvStatusAndSign {
   241  					if isUrlvChar(in[t.pos]) {
   242  						t.urlvStatus = urlvStatusKey
   243  					} else {
   244  						t.urlvStatus = urlvStatusNot //状态计算错误,忽略本次匹配
   245  					}
   246  				}
   247  			}
   248  		}
   249  		if in[t.pos] == '\n' {
   250  			t.lineNum++
   251  		}
   252  		t.lastScopeBuf.WriteByte(t.in[t.pos])
   253  	}
   254  	if t.currentScope == currentScopeTpl {
   255  		s := strings.TrimSpace(t.lastScopeBuf.String())
   256  		if s != "" {
   257  			panic("find tpl data after <? } ?>")
   258  		}
   259  	}
   260  	output := t.outBuf.Bytes()
   261  	addPkgList := []string{}
   262  	if t.hasBytesPackage {
   263  		addPkgList = append(addPkgList, "bytes")
   264  	}
   265  	if t.hasKmgXssPackage {
   266  		addPkgList = append(addPkgList, "github.com/bronze1man/kmg/kmgXss")
   267  	}
   268  	output = addImport(output, addPkgList)
   269  	f, err := format.Source(output)
   270  	if err != nil {
   271  		return output
   272  	}
   273  	return f
   274  }
   275  
   276  func (t *transformer) endTplScope() {
   277  	s := t.lastScopeBuf.String()
   278  	if t.isHtml {
   279  		s = strings.Trim(s, "\n")
   280  	}
   281  	if len(s) > 0 {
   282  		t.outBuf.WriteString("_buf.WriteString(")
   283  		if !strings.Contains(s, "`") {
   284  			t.outBuf.WriteString("`")
   285  			t.outBuf.WriteString(s)
   286  			t.outBuf.WriteString("`")
   287  		} else {
   288  			t.outBuf.WriteString(fmt.Sprintf("%#v", t.lastScopeBuf.String()))
   289  		}
   290  		t.outBuf.WriteString(")\n")
   291  	}
   292  	t.lastScopeBuf.Reset()
   293  }
   294  
   295  func (t *transformer) endNotTplScope() {
   296  	if t.currentScope == currentScopeStatement {
   297  		t.outBuf.WriteString(strings.TrimSpace(t.lastScopeBuf.String()))
   298  		t.outBuf.WriteString("\n")
   299  		if t.isFuncOpenInScope {
   300  			t.isFuncOpenInScope = false
   301  			t.hasFuncOpenBetweenScope = true
   302  			t.outBuf.WriteString("var _buf bytes.Buffer\n")
   303  		}
   304  	} else if t.currentScope == currentScopeExpression {
   305  		t.outBuf.WriteString("_buf.WriteString(")
   306  		s := strings.TrimSpace(t.lastScopeBuf.String())
   307  		if t.isHtml {
   308  			// raw
   309  			if strings.HasPrefix(s, "raw(") && strings.HasSuffix(s, ")") {
   310  				t.outBuf.WriteString(s[4 : len(s)-1])
   311  			} else if t.urlvStatus == urlvStatusValue || t.urlvStatus == urlvStatusEqual {
   312  				t.hasKmgXssPackage = true
   313  				t.outBuf.WriteString("kmgXss.Urlv(")
   314  				t.outBuf.WriteString(s)
   315  				t.outBuf.WriteString(")")
   316  				t.urlvStatus = urlvStatusValue
   317  			} else if t.isInScript {
   318  				t.hasKmgXssPackage = true
   319  				t.outBuf.WriteString("kmgXss.Jsonv(")
   320  				t.outBuf.WriteString(s)
   321  				t.outBuf.WriteString(")")
   322  			} else {
   323  				t.hasKmgXssPackage = true
   324  				t.outBuf.WriteString("kmgXss.H(")
   325  				t.outBuf.WriteString(s)
   326  				t.outBuf.WriteString(")")
   327  			}
   328  		} else {
   329  			t.outBuf.WriteString(s)
   330  		}
   331  		t.outBuf.WriteString(")\n")
   332  	}
   333  	t.lastScopeBuf.Reset()
   334  }
   335  
   336  func (t *transformer) isMatchString(token string) bool {
   337  	return bytes.HasPrefix(t.in[t.pos:], []byte(token))
   338  }
   339  
   340  func isAlphanum(b byte) bool {
   341  	return (b >= '0' && b <= '9') || (b >= 'A' && b <= 'Z') || (b >= 'a' && b <= 'z')
   342  }
   343  
   344  func isUrlvChar(b byte) bool {
   345  	return (b >= '0' && b <= '9') || (b >= 'A' && b <= 'Z') || (b >= 'a' && b <= 'z') ||
   346  		b == '.' || b == '-' || b == '_' || b == '%'
   347  }