mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2026-05-18 01:06:33 +03:00
Compare commits
3 Commits
hide-remot
...
make/use-g
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d6340fe83 | ||
|
|
1334eee433 | ||
|
|
2d67e14d59 |
11
Makefile
11
Makefile
@@ -528,8 +528,6 @@ vet:
|
||||
|
||||
check-all: fmt vet golangci-lint govulncheck
|
||||
|
||||
clean-checkers: remove-golangci-lint remove-govulncheck
|
||||
|
||||
test:
|
||||
GOEXPERIMENT=synctest go test ./lib/... ./app/...
|
||||
|
||||
@@ -574,12 +572,11 @@ app-local-goos-goarch:
|
||||
app-local-windows-goarch:
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=$(GOARCH) go build $(RACE) -ldflags "$(GO_BUILDINFO)" -o bin/$(APP_NAME)-windows-$(GOARCH)$(RACE).exe $(PKG_PREFIX)/app/$(APP_NAME)
|
||||
|
||||
quicktemplate-gen: install-qtc
|
||||
qtc
|
||||
|
||||
install-qtc:
|
||||
which qtc || go install github.com/valyala/quicktemplate/qtc@latest
|
||||
quicktemplate-gen:
|
||||
go tool qtc
|
||||
|
||||
golangci-lint:
|
||||
GOEXPERIMENT=synctest go tool golangci-lint run
|
||||
|
||||
golangci-lint: install-golangci-lint
|
||||
GOEXPERIMENT=synctest golangci-lint run
|
||||
|
||||
4
go.mod
4
go.mod
@@ -135,7 +135,9 @@ require (
|
||||
github.com/prometheus/sigv4 v0.2.0 // indirect
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/stretchr/testify v1.10.0 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
||||
@@ -180,3 +182,5 @@ require (
|
||||
k8s.io/utils v0.0.0-20250502105355-0f33e8f1c979 // indirect
|
||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||
)
|
||||
|
||||
tool github.com/valyala/quicktemplate/qtc
|
||||
|
||||
8
go.sum
8
go.sum
@@ -332,14 +332,14 @@ github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.32 h1:4+LP7qmsLSGbmc66m1s5dKRMBwztRppfxFKlYqYte/c=
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.32/go.mod h1:kzh+BSAvpoyHHdHBCDhmSWtBc1NbLMZ2lWHqnBoxFks=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
|
||||
205
vendor/github.com/valyala/quicktemplate/parser/functype.go
generated
vendored
Normal file
205
vendor/github.com/valyala/quicktemplate/parser/functype.go
generated
vendored
Normal file
@@ -0,0 +1,205 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
goparser "go/parser"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type funcType struct {
|
||||
name string
|
||||
defPrefix string
|
||||
callPrefix string
|
||||
argNames string
|
||||
args string
|
||||
}
|
||||
|
||||
func parseFuncDef(b []byte) (*funcType, error) {
|
||||
defStr := string(b)
|
||||
|
||||
// extract func name
|
||||
n := strings.Index(defStr, "(")
|
||||
if n < 0 {
|
||||
return nil, fmt.Errorf("cannot find '(' in function definition")
|
||||
}
|
||||
name := defStr[:n]
|
||||
defStr = defStr[n+1:]
|
||||
defPrefix := ""
|
||||
callPrefix := ""
|
||||
if len(name) == 0 {
|
||||
// Either empty func name or valid method definition. Let's check.
|
||||
|
||||
// parse method receiver
|
||||
n = strings.Index(defStr, ")")
|
||||
if n < 0 {
|
||||
return nil, fmt.Errorf("cannot find ')' in func")
|
||||
}
|
||||
recvStr := defStr[:n]
|
||||
defStr = defStr[n+1:]
|
||||
exprStr := fmt.Sprintf("func (%s)", recvStr)
|
||||
expr, err := goparser.ParseExpr(exprStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid method definition: %s", err)
|
||||
}
|
||||
ft := expr.(*ast.FuncType)
|
||||
if len(ft.Params.List) != 1 || len(ft.Params.List[0].Names) != 1 {
|
||||
// method receiver must contain only one param
|
||||
return nil, fmt.Errorf("missing func or method name")
|
||||
}
|
||||
recvName := ft.Params.List[0].Names[0].Name
|
||||
defPrefix = fmt.Sprintf("(%s) ", recvStr)
|
||||
callPrefix = recvName + "."
|
||||
|
||||
// extract method name
|
||||
n = strings.Index(defStr, "(")
|
||||
if n < 0 {
|
||||
return nil, fmt.Errorf("missing func name")
|
||||
}
|
||||
name = string(stripLeadingSpace([]byte(defStr[:n])))
|
||||
if len(name) == 0 {
|
||||
return nil, fmt.Errorf("missing method name")
|
||||
}
|
||||
defStr = defStr[n+1:]
|
||||
}
|
||||
|
||||
// validate and collect func args
|
||||
if len(defStr) == 0 || defStr[len(defStr)-1] != ')' {
|
||||
return nil, fmt.Errorf("missing ')' at the end of func")
|
||||
}
|
||||
args := defStr[:len(defStr)-1]
|
||||
exprStr := fmt.Sprintf("func (%s)", args)
|
||||
expr, err := goparser.ParseExpr(exprStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid func args: %s", err)
|
||||
}
|
||||
ft := expr.(*ast.FuncType)
|
||||
if ft.Results != nil {
|
||||
return nil, fmt.Errorf("func mustn't return any results")
|
||||
}
|
||||
|
||||
// extract arg names
|
||||
var tmp []string
|
||||
for _, f := range ft.Params.List {
|
||||
if len(f.Names) == 0 {
|
||||
return nil, fmt.Errorf("func cannot contain untyped arguments")
|
||||
}
|
||||
for _, n := range f.Names {
|
||||
if n == nil {
|
||||
return nil, fmt.Errorf("func cannot contain untyped arguments")
|
||||
}
|
||||
if _, isVariadic := f.Type.(*ast.Ellipsis); isVariadic {
|
||||
tmp = append(tmp, n.Name+"...")
|
||||
} else {
|
||||
tmp = append(tmp, n.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
argNames := strings.Join(tmp, ", ")
|
||||
|
||||
if len(args) > 0 {
|
||||
args = ", " + args
|
||||
}
|
||||
if len(argNames) > 0 {
|
||||
argNames = ", " + argNames
|
||||
}
|
||||
return &funcType{
|
||||
name: name,
|
||||
defPrefix: defPrefix,
|
||||
callPrefix: callPrefix,
|
||||
argNames: argNames,
|
||||
args: args,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseFuncCall(b []byte) (*funcType, error) {
|
||||
exprStr := string(b)
|
||||
expr, err := goparser.ParseExpr(exprStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ce, ok := expr.(*ast.CallExpr)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing function call")
|
||||
}
|
||||
callPrefix, name, err := getCallName(ce)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
argNames := exprStr[ce.Lparen : ce.Rparen-1]
|
||||
|
||||
if len(argNames) > 0 {
|
||||
argNames = ", " + argNames
|
||||
}
|
||||
return &funcType{
|
||||
name: name,
|
||||
callPrefix: callPrefix,
|
||||
argNames: argNames,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f *funcType) DefStream(dst string) string {
|
||||
return fmt.Sprintf("%s%s%s(%s *qt%s.Writer%s)", f.defPrefix, f.prefixStream(), f.name, dst, mangleSuffix, f.args)
|
||||
}
|
||||
|
||||
func (f *funcType) CallStream(dst string) string {
|
||||
return fmt.Sprintf("%s%s%s(%s%s)", f.callPrefix, f.prefixStream(), f.name, dst, f.argNames)
|
||||
}
|
||||
|
||||
func (f *funcType) DefWrite(dst string) string {
|
||||
return fmt.Sprintf("%s%s%s(%s qtio%s.Writer%s)", f.defPrefix, f.prefixWrite(), f.name, dst, mangleSuffix, f.args)
|
||||
}
|
||||
|
||||
func (f *funcType) CallWrite(dst string) string {
|
||||
return fmt.Sprintf("%s%s%s(%s%s)", f.callPrefix, f.prefixWrite(), f.name, dst, f.argNames)
|
||||
}
|
||||
|
||||
func (f *funcType) DefString() string {
|
||||
args := f.args
|
||||
if len(args) > 0 {
|
||||
// skip the first ', '
|
||||
args = args[2:]
|
||||
}
|
||||
return fmt.Sprintf("%s%s(%s) string", f.defPrefix, f.name, args)
|
||||
}
|
||||
|
||||
func (f *funcType) prefixWrite() string {
|
||||
s := "write"
|
||||
if isUpper(f.name[0]) {
|
||||
s = "Write"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (f *funcType) prefixStream() string {
|
||||
s := "stream"
|
||||
if isUpper(f.name[0]) {
|
||||
s = "Stream"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func getCallName(ce *ast.CallExpr) (string, string, error) {
|
||||
callPrefix := ""
|
||||
name := ""
|
||||
expr := ce.Fun
|
||||
for {
|
||||
switch x := expr.(type) {
|
||||
case *ast.Ident:
|
||||
if len(callPrefix) == 0 && len(name) == 0 {
|
||||
return "", x.Name, nil
|
||||
}
|
||||
callPrefix = x.Name + "." + callPrefix
|
||||
return callPrefix, name, nil
|
||||
case *ast.SelectorExpr:
|
||||
if len(name) == 0 {
|
||||
name = x.Sel.Name
|
||||
} else {
|
||||
callPrefix = x.Sel.Name + "." + callPrefix
|
||||
}
|
||||
expr = x.X
|
||||
default:
|
||||
return "", "", fmt.Errorf("unexpected function name")
|
||||
}
|
||||
}
|
||||
}
|
||||
921
vendor/github.com/valyala/quicktemplate/parser/parser.go
generated
vendored
Normal file
921
vendor/github.com/valyala/quicktemplate/parser/parser.go
generated
vendored
Normal file
@@ -0,0 +1,921 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
goparser "go/parser"
|
||||
gotoken "go/token"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type parser struct {
|
||||
s *scanner
|
||||
w io.Writer
|
||||
packageName string
|
||||
skipLineComments bool
|
||||
prefix string
|
||||
forDepth int
|
||||
switchDepth int
|
||||
skipOutputDepth int
|
||||
|
||||
importsUseEmitted bool
|
||||
packageNameEmitted bool
|
||||
}
|
||||
|
||||
// Parse parses the contents of the supplied reader, writing generated code to
|
||||
// the supplied writer. Uses filename as the source file for line comments, and
|
||||
// pkg as the Go package name.
|
||||
func Parse(w io.Writer, r io.Reader, filename, pkg string) error {
|
||||
return parse(w, r, filename, pkg, false)
|
||||
}
|
||||
|
||||
// ParseNoLineComments is the same as Parse, but does not write line comments.
|
||||
func ParseNoLineComments(w io.Writer, r io.Reader, filename, pkg string) error {
|
||||
return parse(w, r, filename, pkg, true)
|
||||
}
|
||||
|
||||
func parse(w io.Writer, r io.Reader, filename, pkg string, skipLineComments bool) error {
|
||||
p := &parser{
|
||||
s: newScanner(r, filename),
|
||||
w: w,
|
||||
packageName: pkg,
|
||||
skipLineComments: skipLineComments,
|
||||
}
|
||||
return p.parseTemplate()
|
||||
}
|
||||
|
||||
func (p *parser) parseTemplate() error {
|
||||
s := p.s
|
||||
fmt.Fprintf(p.w, `// Code generated by qtc from %q. DO NOT EDIT.
|
||||
// See https://github.com/valyala/quicktemplate for details.
|
||||
|
||||
`,
|
||||
filepath.Base(s.filePath))
|
||||
for s.Next() {
|
||||
t := s.Token()
|
||||
switch t.ID {
|
||||
case text:
|
||||
p.emitComment(t.Value)
|
||||
case tagName:
|
||||
switch string(t.Value) {
|
||||
case "package":
|
||||
if p.packageNameEmitted {
|
||||
return fmt.Errorf("package name must be at the top of the template. Found at %s", s.Context())
|
||||
}
|
||||
if err := p.parsePackageName(); err != nil {
|
||||
return err
|
||||
}
|
||||
case "import":
|
||||
p.emitPackageName()
|
||||
if p.importsUseEmitted {
|
||||
return fmt.Errorf("imports must be at the top of the template. Found at %s", s.Context())
|
||||
}
|
||||
if err := p.parseImport(); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
p.emitPackageName()
|
||||
p.emitImportsUse()
|
||||
switch string(t.Value) {
|
||||
case "interface", "iface":
|
||||
if err := p.parseInterface(); err != nil {
|
||||
return err
|
||||
}
|
||||
case "code":
|
||||
if err := p.parseTemplateCode(); err != nil {
|
||||
return err
|
||||
}
|
||||
case "func":
|
||||
if err := p.parseFunc(); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unexpected tag found outside func: %q at %s", t.Value, s.Context())
|
||||
}
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unexpected token found %s outside func at %s", t, s.Context())
|
||||
}
|
||||
}
|
||||
p.emitImportsUse()
|
||||
if err := s.LastError(); err != nil {
|
||||
return fmt.Errorf("cannot parse template: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *parser) emitPackageName() {
|
||||
if !p.packageNameEmitted {
|
||||
p.Printf("package %s\n", p.packageName)
|
||||
p.packageNameEmitted = true
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) emitComment(comment []byte) {
|
||||
isFirstNonemptyLine := false
|
||||
for len(comment) > 0 {
|
||||
n := bytes.IndexByte(comment, '\n')
|
||||
if n < 0 {
|
||||
n = len(comment)
|
||||
}
|
||||
line := stripTrailingSpace(comment[:n])
|
||||
if bytes.HasPrefix(line, []byte("//")) {
|
||||
line = line[2:]
|
||||
if len(line) > 0 && isSpace(line[0]) {
|
||||
line = line[1:]
|
||||
}
|
||||
}
|
||||
if len(line) == 0 {
|
||||
if isFirstNonemptyLine {
|
||||
fmt.Fprintf(p.w, "//\n")
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintf(p.w, "// %s\n", line)
|
||||
isFirstNonemptyLine = true
|
||||
}
|
||||
|
||||
if n < len(comment) {
|
||||
comment = comment[n+1:]
|
||||
} else {
|
||||
comment = comment[n:]
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(p.w, "\n")
|
||||
}
|
||||
|
||||
func (p *parser) emitImportsUse() {
|
||||
if p.importsUseEmitted {
|
||||
return
|
||||
}
|
||||
p.Printf(`import (
|
||||
qtio%s "io"
|
||||
|
||||
qt%s "github.com/valyala/quicktemplate"
|
||||
)
|
||||
`, mangleSuffix, mangleSuffix)
|
||||
p.Printf(`var (
|
||||
_ = qtio%s.Copy
|
||||
_ = qt%s.AcquireByteBuffer
|
||||
)
|
||||
`, mangleSuffix, mangleSuffix)
|
||||
p.importsUseEmitted = true
|
||||
}
|
||||
|
||||
func (p *parser) parseFunc() error {
|
||||
s := p.s
|
||||
t, err := expectTagContents(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
funcStr := "func " + string(t.Value)
|
||||
f, err := parseFuncDef(t.Value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in %q at %s: %s", funcStr, s.Context(), err)
|
||||
}
|
||||
p.emitFuncStart(f)
|
||||
for s.Next() {
|
||||
t := s.Token()
|
||||
switch t.ID {
|
||||
case text:
|
||||
p.emitText(t.Value)
|
||||
case tagName:
|
||||
ok, err := p.tryParseCommonTags(t.Value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in %q: %s", funcStr, err)
|
||||
}
|
||||
if ok {
|
||||
continue
|
||||
}
|
||||
switch string(t.Value) {
|
||||
case "endfunc":
|
||||
if err = skipTagContents(s); err != nil {
|
||||
return err
|
||||
}
|
||||
p.emitFuncEnd(f)
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("unexpected tag found in %q: %q at %s", funcStr, t.Value, s.Context())
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unexpected token found when parsing %q: %s at %s", funcStr, t, s.Context())
|
||||
}
|
||||
}
|
||||
if err := s.LastError(); err != nil {
|
||||
return fmt.Errorf("cannot parse %q: %s", funcStr, err)
|
||||
}
|
||||
return fmt.Errorf("cannot find endfunc tag for %q at %s", funcStr, s.Context())
|
||||
}
|
||||
|
||||
func (p *parser) parseFor() error {
|
||||
s := p.s
|
||||
t, err := expectTagContents(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
forStr := "for " + string(t.Value)
|
||||
if err = validateForStmt(t.Value); err != nil {
|
||||
return fmt.Errorf("invalid statement %q at %s: %s", forStr, s.Context(), err)
|
||||
}
|
||||
p.Printf("for %s {", t.Value)
|
||||
p.prefix += "\t"
|
||||
p.forDepth++
|
||||
for s.Next() {
|
||||
t := s.Token()
|
||||
switch t.ID {
|
||||
case text:
|
||||
p.emitText(t.Value)
|
||||
case tagName:
|
||||
ok, err := p.tryParseCommonTags(t.Value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in %q: %s", forStr, err)
|
||||
}
|
||||
if ok {
|
||||
continue
|
||||
}
|
||||
switch string(t.Value) {
|
||||
case "endfor":
|
||||
if err = skipTagContents(s); err != nil {
|
||||
return err
|
||||
}
|
||||
p.forDepth--
|
||||
p.prefix = p.prefix[1:]
|
||||
p.Printf("}")
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("unexpected tag found in %q: %q at %s", forStr, t.Value, s.Context())
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unexpected token found when parsing %q: %s at %s", forStr, t, s.Context())
|
||||
}
|
||||
}
|
||||
if err := s.LastError(); err != nil {
|
||||
return fmt.Errorf("cannot parse %q: %s", forStr, err)
|
||||
}
|
||||
return fmt.Errorf("cannot find endfor tag for %q at %s", forStr, s.Context())
|
||||
}
|
||||
|
||||
func (p *parser) parseDefault() error {
|
||||
s := p.s
|
||||
if err := skipTagContents(s); err != nil {
|
||||
return err
|
||||
}
|
||||
stmtStr := "default"
|
||||
p.Printf("default:")
|
||||
p.prefix += "\t"
|
||||
for s.Next() {
|
||||
t := s.Token()
|
||||
switch t.ID {
|
||||
case text:
|
||||
p.emitText(t.Value)
|
||||
case tagName:
|
||||
ok, err := p.tryParseCommonTags(t.Value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in %q: %s", stmtStr, err)
|
||||
}
|
||||
if !ok {
|
||||
s.Rewind()
|
||||
p.prefix = p.prefix[1:]
|
||||
return nil
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unexpected token found when parsing %q: %s at %s", stmtStr, t, s.Context())
|
||||
}
|
||||
}
|
||||
if err := s.LastError(); err != nil {
|
||||
return fmt.Errorf("cannot parse %q: %s", stmtStr, err)
|
||||
}
|
||||
return fmt.Errorf("cannot find end of %q at %s", stmtStr, s.Context())
|
||||
}
|
||||
|
||||
func (p *parser) parseCase(switchValue string) error {
|
||||
s := p.s
|
||||
t, err := expectTagContents(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
caseStr := "case " + string(t.Value)
|
||||
if err = validateCaseStmt(switchValue, t.Value); err != nil {
|
||||
return fmt.Errorf("invalid statement %q at %s: %s", caseStr, s.Context(), err)
|
||||
}
|
||||
p.Printf("case %s:", t.Value)
|
||||
p.prefix += "\t"
|
||||
for s.Next() {
|
||||
t := s.Token()
|
||||
switch t.ID {
|
||||
case text:
|
||||
p.emitText(t.Value)
|
||||
case tagName:
|
||||
ok, err := p.tryParseCommonTags(t.Value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in %q: %s", caseStr, err)
|
||||
}
|
||||
if !ok {
|
||||
s.Rewind()
|
||||
p.prefix = p.prefix[1:]
|
||||
return nil
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unexpected token found when parsing %q: %s at %s", caseStr, t, s.Context())
|
||||
}
|
||||
}
|
||||
if err := s.LastError(); err != nil {
|
||||
return fmt.Errorf("cannot parse %q: %s", caseStr, err)
|
||||
}
|
||||
return fmt.Errorf("cannot find end of %q at %s", caseStr, s.Context())
|
||||
}
|
||||
|
||||
func (p *parser) parseCat() error {
|
||||
s := p.s
|
||||
t, err := expectTagContents(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filename, err := strconv.Unquote(string(t.Value))
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid cat value %q at %s: %s", t.Value, s.Context(), err)
|
||||
}
|
||||
|
||||
data, err := readFile(s.filePath, filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot cat file %q at %s: %s", filename, s.Context(), err)
|
||||
}
|
||||
p.emitText(data)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *parser) parseSwitch() error {
|
||||
s := p.s
|
||||
t, err := expectTagContents(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switchStr := "switch " + string(t.Value)
|
||||
if err = validateSwitchStmt(t.Value); err != nil {
|
||||
return fmt.Errorf("invalid statement %q at %s: %s", switchStr, s.Context(), err)
|
||||
}
|
||||
p.Printf("switch %s {", t.Value)
|
||||
switchValue := string(t.Value)
|
||||
caseNum := 0
|
||||
defaultFound := false
|
||||
p.switchDepth++
|
||||
for s.Next() {
|
||||
t := s.Token()
|
||||
switch t.ID {
|
||||
case text:
|
||||
if caseNum == 0 {
|
||||
comment := stripLeadingSpace(t.Value)
|
||||
if len(comment) > 0 {
|
||||
p.emitComment(comment)
|
||||
}
|
||||
} else {
|
||||
p.emitText(t.Value)
|
||||
}
|
||||
case tagName:
|
||||
switch string(t.Value) {
|
||||
case "endswitch":
|
||||
if caseNum == 0 {
|
||||
return fmt.Errorf("empty statement %q found at %s", switchStr, s.Context())
|
||||
}
|
||||
if err = skipTagContents(s); err != nil {
|
||||
return err
|
||||
}
|
||||
p.switchDepth--
|
||||
p.Printf("}")
|
||||
return nil
|
||||
case "case":
|
||||
caseNum++
|
||||
if err = p.parseCase(switchValue); err != nil {
|
||||
return err
|
||||
}
|
||||
case "default":
|
||||
if defaultFound {
|
||||
return fmt.Errorf("duplicate default tag found in %q at %s", switchStr, s.Context())
|
||||
}
|
||||
defaultFound = true
|
||||
caseNum++
|
||||
if err = p.parseDefault(); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unexpected tag found in %q: %q at %s", switchStr, t.Value, s.Context())
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unexpected token found when parsing %q: %s at %s", switchStr, t, s.Context())
|
||||
}
|
||||
}
|
||||
if err := s.LastError(); err != nil {
|
||||
return fmt.Errorf("cannot parse %q: %s", switchStr, err)
|
||||
}
|
||||
return fmt.Errorf("cannot find endswitch tag for %q at %s", switchStr, s.Context())
|
||||
}
|
||||
|
||||
func (p *parser) parseIf() error {
|
||||
s := p.s
|
||||
t, err := expectTagContents(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(t.Value) == 0 {
|
||||
return fmt.Errorf("empty if condition at %s", s.Context())
|
||||
}
|
||||
ifStr := "if " + string(t.Value)
|
||||
if err = validateIfStmt(t.Value); err != nil {
|
||||
return fmt.Errorf("invalid statement %q at %s: %s", ifStr, s.Context(), err)
|
||||
}
|
||||
p.Printf("if %s {", t.Value)
|
||||
p.prefix += "\t"
|
||||
elseUsed := false
|
||||
for s.Next() {
|
||||
t := s.Token()
|
||||
switch t.ID {
|
||||
case text:
|
||||
p.emitText(t.Value)
|
||||
case tagName:
|
||||
ok, err := p.tryParseCommonTags(t.Value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in %q: %s", ifStr, err)
|
||||
}
|
||||
if ok {
|
||||
continue
|
||||
}
|
||||
switch string(t.Value) {
|
||||
case "endif":
|
||||
if err = skipTagContents(s); err != nil {
|
||||
return err
|
||||
}
|
||||
p.prefix = p.prefix[1:]
|
||||
p.Printf("}")
|
||||
return nil
|
||||
case "else":
|
||||
if elseUsed {
|
||||
return fmt.Errorf("duplicate else branch found for %q at %s", ifStr, s.Context())
|
||||
}
|
||||
if err = skipTagContents(s); err != nil {
|
||||
return err
|
||||
}
|
||||
p.prefix = p.prefix[1:]
|
||||
p.Printf("} else {")
|
||||
p.prefix += "\t"
|
||||
elseUsed = true
|
||||
case "elseif":
|
||||
if elseUsed {
|
||||
return fmt.Errorf("unexpected elseif branch found after else branch for %q at %s",
|
||||
ifStr, s.Context())
|
||||
}
|
||||
t, err = expectTagContents(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.prefix = p.prefix[1:]
|
||||
p.Printf("} else if %s {", t.Value)
|
||||
p.prefix += "\t"
|
||||
default:
|
||||
return fmt.Errorf("unexpected tag found in %q: %q at %s", ifStr, t.Value, s.Context())
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unexpected token found when parsing %q: %s at %s", ifStr, t, s.Context())
|
||||
}
|
||||
}
|
||||
if err := s.LastError(); err != nil {
|
||||
return fmt.Errorf("cannot parse %q: %s", ifStr, err)
|
||||
}
|
||||
return fmt.Errorf("cannot find endif tag for %q at %s", ifStr, s.Context())
|
||||
}
|
||||
|
||||
func (p *parser) tryParseCommonTags(tagBytes []byte) (bool, error) {
|
||||
tagNameStr, prec := splitTagNamePrec(string(tagBytes))
|
||||
switch tagNameStr {
|
||||
case "s", "v", "d", "dl", "dul", "f", "q", "z", "j", "u",
|
||||
"s=", "v=", "d=", "dl=", "dul=", "f=", "q=", "z=", "j=", "u=",
|
||||
"sz", "qz", "jz", "uz",
|
||||
"sz=", "qz=", "jz=", "uz=":
|
||||
if err := p.parseOutputTag(tagNameStr, prec); err != nil {
|
||||
return false, err
|
||||
}
|
||||
case "=", "=h", "=u", "=uh", "=q", "=qh", "=j", "=jh":
|
||||
if err := p.parseOutputFunc(tagNameStr); err != nil {
|
||||
return false, err
|
||||
}
|
||||
case "return":
|
||||
if err := p.skipAfterTag(tagNameStr); err != nil {
|
||||
return false, err
|
||||
}
|
||||
case "break":
|
||||
if p.forDepth <= 0 && p.switchDepth <= 0 {
|
||||
return false, fmt.Errorf("found break tag outside for loop and switch block")
|
||||
}
|
||||
if err := p.skipAfterTag(tagNameStr); err != nil {
|
||||
return false, err
|
||||
}
|
||||
case "continue":
|
||||
if p.forDepth <= 0 {
|
||||
return false, fmt.Errorf("found continue tag outside for loop")
|
||||
}
|
||||
if err := p.skipAfterTag(tagNameStr); err != nil {
|
||||
return false, err
|
||||
}
|
||||
case "code":
|
||||
if err := p.parseFuncCode(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
case "for":
|
||||
if err := p.parseFor(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
case "if":
|
||||
if err := p.parseIf(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
case "switch":
|
||||
if err := p.parseSwitch(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
case "cat":
|
||||
if err := p.parseCat(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
default:
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func splitTagNamePrec(tagName string) (string, int) {
|
||||
parts := strings.Split(tagName, ".")
|
||||
if len(parts) == 2 && parts[0] == "f" {
|
||||
p := parts[1]
|
||||
if strings.HasSuffix(p, "=") {
|
||||
p = p[:len(p)-1]
|
||||
}
|
||||
if len(p) == 0 {
|
||||
return "f", 0
|
||||
}
|
||||
prec, err := strconv.Atoi(p)
|
||||
if err == nil && prec >= 0 {
|
||||
return "f", prec
|
||||
}
|
||||
}
|
||||
return tagName, -1
|
||||
}
|
||||
|
||||
func (p *parser) skipAfterTag(tagStr string) error {
|
||||
s := p.s
|
||||
if err := skipTagContents(s); err != nil {
|
||||
return err
|
||||
}
|
||||
p.Printf("%s", tagStr)
|
||||
p.skipOutputDepth++
|
||||
defer func() {
|
||||
p.skipOutputDepth--
|
||||
}()
|
||||
for s.Next() {
|
||||
t := s.Token()
|
||||
switch t.ID {
|
||||
case text:
|
||||
// skip text
|
||||
case tagName:
|
||||
ok, err := p.tryParseCommonTags(t.Value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error when parsing contents after %q: %s", tagStr, err)
|
||||
}
|
||||
if ok {
|
||||
continue
|
||||
}
|
||||
switch string(t.Value) {
|
||||
case "endfunc", "endfor", "endif", "else", "elseif", "case", "default", "endswitch":
|
||||
s.Rewind()
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("unexpected tag found after %q: %q at %s", tagStr, t.Value, s.Context())
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unexpected token found when parsing contents after %q: %s at %s", tagStr, t, s.Context())
|
||||
}
|
||||
}
|
||||
if err := s.LastError(); err != nil {
|
||||
return fmt.Errorf("cannot parse contents after %q: %s", tagStr, err)
|
||||
}
|
||||
return fmt.Errorf("cannot find closing tag after %q at %s", tagStr, s.Context())
|
||||
}
|
||||
|
||||
func (p *parser) parseInterface() error {
|
||||
s := p.s
|
||||
t, err := expectTagContents(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n := bytes.IndexByte(t.Value, '{')
|
||||
if n < 0 {
|
||||
return fmt.Errorf("missing '{' in interface at %s", s.Context())
|
||||
}
|
||||
ifname := string(stripTrailingSpace(t.Value[:n]))
|
||||
if len(ifname) == 0 {
|
||||
return fmt.Errorf("missing interface name at %s", s.Context())
|
||||
}
|
||||
p.Printf("type %s interface {", ifname)
|
||||
p.prefix = "\t"
|
||||
|
||||
tail := t.Value[n:]
|
||||
exprStr := fmt.Sprintf("interface %s", tail)
|
||||
expr, err := goparser.ParseExpr(exprStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error when parsing interface at %s: %s", s.Context(), err)
|
||||
}
|
||||
it, ok := expr.(*ast.InterfaceType)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected interface type at %s: %T", s.Context(), expr)
|
||||
}
|
||||
methods := it.Methods.List
|
||||
if len(methods) == 0 {
|
||||
return fmt.Errorf("interface must contain at least one method at %s", s.Context())
|
||||
}
|
||||
|
||||
for _, m := range it.Methods.List {
|
||||
methodStr := exprStr[m.Pos()-1 : m.End()-1]
|
||||
f, err := parseFuncDef([]byte(methodStr))
|
||||
if err != nil {
|
||||
return fmt.Errorf("when when parsing %q at %s: %s", methodStr, s.Context(), err)
|
||||
}
|
||||
p.Printf("%s string", methodStr)
|
||||
p.Printf("%s", f.DefStream("qw"+mangleSuffix))
|
||||
p.Printf("%s", f.DefWrite("qq"+mangleSuffix))
|
||||
}
|
||||
p.prefix = ""
|
||||
p.Printf("}")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *parser) parsePackageName() error {
|
||||
t, err := expectTagContents(p.s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(t.Value) == 0 {
|
||||
return fmt.Errorf("empty package name found at %s", p.s.Context())
|
||||
}
|
||||
if err = validatePackageName(t.Value); err != nil {
|
||||
return fmt.Errorf("invalid package name found at %s: %s", p.s.Context(), err)
|
||||
}
|
||||
p.packageName = string(t.Value)
|
||||
p.emitPackageName()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *parser) parseImport() error {
|
||||
t, err := expectTagContents(p.s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(t.Value) == 0 {
|
||||
return fmt.Errorf("empty import found at %s", p.s.Context())
|
||||
}
|
||||
if err = validateImport(t.Value); err != nil {
|
||||
return fmt.Errorf("invalid import found at %s: %s", p.s.Context(), err)
|
||||
}
|
||||
p.Printf("import %s\n", t.Value)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *parser) parseTemplateCode() error {
|
||||
t, err := expectTagContents(p.s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = validateTemplateCode(t.Value); err != nil {
|
||||
return fmt.Errorf("invalid code at %s: %s", p.s.Context(), err)
|
||||
}
|
||||
p.Printf("%s\n", t.Value)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *parser) parseFuncCode() error {
|
||||
t, err := expectTagContents(p.s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = validateFuncCode(t.Value); err != nil {
|
||||
return fmt.Errorf("invalid code at %s: %s", p.s.Context(), err)
|
||||
}
|
||||
p.Printf("%s\n", t.Value)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *parser) parseOutputTag(tagNameStr string, prec int) error {
|
||||
s := p.s
|
||||
t, err := expectTagContents(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = validateOutputTagValue(t.Value); err != nil {
|
||||
return fmt.Errorf("invalid output tag value at %s: %s", s.Context(), err)
|
||||
}
|
||||
filter := "N"
|
||||
switch tagNameStr {
|
||||
case "s", "v", "q", "z", "j", "sz", "qz", "jz":
|
||||
filter = "E"
|
||||
}
|
||||
if strings.HasSuffix(tagNameStr, "=") {
|
||||
tagNameStr = tagNameStr[:len(tagNameStr)-1]
|
||||
}
|
||||
if tagNameStr == "f" && prec >= 0 {
|
||||
p.Printf("qw%s.N().FPrec(%s, %d)", mangleSuffix, t.Value, prec)
|
||||
} else {
|
||||
tagNameStr = strings.ToUpper(tagNameStr)
|
||||
p.Printf("qw%s.%s().%s(%s)", mangleSuffix, filter, tagNameStr, t.Value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *parser) parseOutputFunc(tagNameStr string) error {
|
||||
s := p.s
|
||||
t, err := expectTagContents(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f, err := parseFuncCall(t.Value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error at %s: %s", s.Context(), err)
|
||||
}
|
||||
filter := "N"
|
||||
tagNameStr = tagNameStr[1:]
|
||||
if strings.HasSuffix(tagNameStr, "h") {
|
||||
tagNameStr = tagNameStr[:len(tagNameStr)-1]
|
||||
switch tagNameStr {
|
||||
case "", "q", "j":
|
||||
filter = "E"
|
||||
}
|
||||
}
|
||||
|
||||
if len(tagNameStr) > 0 || filter == "E" {
|
||||
tagNameStr = strings.ToUpper(tagNameStr)
|
||||
p.Printf("{")
|
||||
p.Printf("qb%s := qt%s.AcquireByteBuffer()", mangleSuffix, mangleSuffix)
|
||||
p.Printf("%s", f.CallWrite("qb"+mangleSuffix))
|
||||
p.Printf("qw%s.%s().%sZ(qb%s.B)", mangleSuffix, filter, tagNameStr, mangleSuffix)
|
||||
p.Printf("qt%s.ReleaseByteBuffer(qb%s)", mangleSuffix, mangleSuffix)
|
||||
p.Printf("}")
|
||||
} else {
|
||||
p.Printf("%s", f.CallStream("qw"+mangleSuffix))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *parser) emitText(text []byte) {
|
||||
for len(text) > 0 {
|
||||
n := bytes.IndexByte(text, '`')
|
||||
if n < 0 {
|
||||
p.Printf("qw%s.N().S(`%s`)", mangleSuffix, text)
|
||||
return
|
||||
}
|
||||
p.Printf("qw%s.N().S(`%s`)", mangleSuffix, text[:n])
|
||||
p.Printf("qw%s.N().S(\"`\")", mangleSuffix)
|
||||
text = text[n+1:]
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) emitFuncStart(f *funcType) {
|
||||
p.Printf("func %s {", f.DefStream("qw"+mangleSuffix))
|
||||
p.prefix = "\t"
|
||||
}
|
||||
|
||||
func (p *parser) emitFuncEnd(f *funcType) {
|
||||
p.prefix = ""
|
||||
p.Printf("}\n")
|
||||
|
||||
p.Printf("func %s {", f.DefWrite("qq"+mangleSuffix))
|
||||
p.prefix = "\t"
|
||||
p.Printf("qw%s := qt%s.AcquireWriter(qq%s)", mangleSuffix, mangleSuffix, mangleSuffix)
|
||||
p.Printf("%s", f.CallStream("qw"+mangleSuffix))
|
||||
p.Printf("qt%s.ReleaseWriter(qw%s)", mangleSuffix, mangleSuffix)
|
||||
p.prefix = ""
|
||||
p.Printf("}\n")
|
||||
|
||||
p.Printf("func %s {", f.DefString())
|
||||
p.prefix = "\t"
|
||||
p.Printf("qb%s := qt%s.AcquireByteBuffer()", mangleSuffix, mangleSuffix)
|
||||
p.Printf("%s", f.CallWrite("qb"+mangleSuffix))
|
||||
p.Printf("qs%s := string(qb%s.B)", mangleSuffix, mangleSuffix)
|
||||
p.Printf("qt%s.ReleaseByteBuffer(qb%s)", mangleSuffix, mangleSuffix)
|
||||
p.Printf("return qs%s", mangleSuffix)
|
||||
p.prefix = ""
|
||||
p.Printf("}\n")
|
||||
}
|
||||
|
||||
func (p *parser) Printf(format string, args ...interface{}) {
|
||||
if p.skipOutputDepth > 0 {
|
||||
return
|
||||
}
|
||||
w := p.w
|
||||
if !p.skipLineComments {
|
||||
// line comments are required to start at the beginning of the line
|
||||
p.s.WriteLineComment(w)
|
||||
}
|
||||
fmt.Fprintf(w, "%s", p.prefix)
|
||||
fmt.Fprintf(w, format, args...)
|
||||
fmt.Fprintf(w, "\n")
|
||||
}
|
||||
|
||||
func skipTagContents(s *scanner) error {
|
||||
tagName := string(s.Token().Value)
|
||||
t, err := expectTagContents(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(t.Value) > 0 {
|
||||
return fmt.Errorf("unexpected extra value after %s: %q at %s", tagName, t.Value, s.Context())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func expectTagContents(s *scanner) (*token, error) {
|
||||
return expectToken(s, tagContents)
|
||||
}
|
||||
|
||||
func expectToken(s *scanner, id int) (*token, error) {
|
||||
if !s.Next() {
|
||||
return nil, fmt.Errorf("cannot find token %s: %v", tokenIDToStr(id), s.LastError())
|
||||
}
|
||||
t := s.Token()
|
||||
if t.ID != id {
|
||||
return nil, fmt.Errorf("unexpected token found %s. Expecting %s at %s", t, tokenIDToStr(id), s.Context())
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func validateOutputTagValue(stmt []byte) error {
|
||||
exprStr := string(stmt)
|
||||
_, err := goparser.ParseExpr(exprStr)
|
||||
return err
|
||||
}
|
||||
|
||||
func validateForStmt(stmt []byte) error {
|
||||
exprStr := fmt.Sprintf("func () { for %s {} }", stmt)
|
||||
_, err := goparser.ParseExpr(exprStr)
|
||||
return err
|
||||
}
|
||||
|
||||
func validateIfStmt(stmt []byte) error {
|
||||
exprStr := fmt.Sprintf("func () { if %s {} }", stmt)
|
||||
_, err := goparser.ParseExpr(exprStr)
|
||||
return err
|
||||
}
|
||||
|
||||
func validateSwitchStmt(stmt []byte) error {
|
||||
exprStr := fmt.Sprintf("func () { switch %s {} }", stmt)
|
||||
_, err := goparser.ParseExpr(exprStr)
|
||||
return err
|
||||
}
|
||||
|
||||
func validateCaseStmt(switchValue string, stmt []byte) error {
|
||||
exprStr := fmt.Sprintf("func () { switch %s {case %s:} }", switchValue, stmt)
|
||||
_, err := goparser.ParseExpr(exprStr)
|
||||
return err
|
||||
}
|
||||
|
||||
func validateFuncCode(code []byte) error {
|
||||
exprStr := fmt.Sprintf("func () { for { %s\n } }", code)
|
||||
_, err := goparser.ParseExpr(exprStr)
|
||||
return err
|
||||
}
|
||||
|
||||
func validateTemplateCode(code []byte) error {
|
||||
codeStr := fmt.Sprintf("package foo\nvar _ = a\n%s", code)
|
||||
fset := gotoken.NewFileSet()
|
||||
_, err := goparser.ParseFile(fset, "", codeStr, 0)
|
||||
return err
|
||||
}
|
||||
|
||||
func validatePackageName(code []byte) error {
|
||||
codeStr := fmt.Sprintf("package %s", code)
|
||||
fset := gotoken.NewFileSet()
|
||||
_, err := goparser.ParseFile(fset, "", codeStr, 0)
|
||||
return err
|
||||
}
|
||||
|
||||
func validateImport(code []byte) error {
|
||||
codeStr := fmt.Sprintf("package foo\nimport %s", code)
|
||||
fset := gotoken.NewFileSet()
|
||||
f, err := goparser.ParseFile(fset, "", codeStr, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, d := range f.Decls {
|
||||
gd, ok := d.(*ast.GenDecl)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected code found: %T. Expecting ast.GenDecl", d)
|
||||
}
|
||||
for _, s := range gd.Specs {
|
||||
if _, ok := s.(*ast.ImportSpec); !ok {
|
||||
return fmt.Errorf("unexpected code found: %T. Expecting ast.ImportSpec", s)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
467
vendor/github.com/valyala/quicktemplate/parser/scanner.go
generated
vendored
Normal file
467
vendor/github.com/valyala/quicktemplate/parser/scanner.go
generated
vendored
Normal file
@@ -0,0 +1,467 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// token ids
|
||||
const (
|
||||
text = iota
|
||||
tagName
|
||||
tagContents
|
||||
)
|
||||
|
||||
var tokenStrMap = map[int]string{
|
||||
text: "text",
|
||||
tagName: "tagName",
|
||||
tagContents: "tagContents",
|
||||
}
|
||||
|
||||
func tokenIDToStr(id int) string {
|
||||
str := tokenStrMap[id]
|
||||
if str == "" {
|
||||
panic(fmt.Sprintf("unknown tokenID=%d", id))
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
type token struct {
|
||||
ID int
|
||||
Value []byte
|
||||
|
||||
line int
|
||||
pos int
|
||||
}
|
||||
|
||||
func (t *token) init(id, line, pos int) {
|
||||
t.ID = id
|
||||
t.Value = t.Value[:0]
|
||||
|
||||
t.line = line
|
||||
t.pos = pos
|
||||
}
|
||||
|
||||
func (t *token) String() string {
|
||||
return fmt.Sprintf("Token %q, value %q", tokenIDToStr(t.ID), t.Value)
|
||||
}
|
||||
|
||||
type scanner struct {
|
||||
r *bufio.Reader
|
||||
t token
|
||||
c byte
|
||||
err error
|
||||
|
||||
filePath string
|
||||
|
||||
line int
|
||||
lineStr []byte
|
||||
|
||||
nextTokenID int
|
||||
|
||||
capture bool
|
||||
capturedValue []byte
|
||||
|
||||
collapseSpaceDepth int
|
||||
stripSpaceDepth int
|
||||
stripToNewLine bool
|
||||
rewind bool
|
||||
}
|
||||
|
||||
var tailOfLine = regexp.MustCompile(`^[[:blank:]]*(?:\r*\n)?`)
|
||||
var prevBlank = regexp.MustCompile(`[[:blank:]]+$`)
|
||||
|
||||
func newScanner(r io.Reader, filePath string) *scanner {
|
||||
// Substitute backslashes with forward slashes in filePath
|
||||
// for the sake of consistency on different platforms (windows, linux).
|
||||
// See https://github.com/valyala/quicktemplate/issues/62.
|
||||
filePath = strings.Replace(filePath, "\\", "/", -1)
|
||||
|
||||
return &scanner{
|
||||
r: bufio.NewReader(r),
|
||||
filePath: filePath,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *scanner) Rewind() {
|
||||
if s.rewind {
|
||||
panic("BUG: duplicate Rewind call")
|
||||
}
|
||||
s.rewind = true
|
||||
}
|
||||
|
||||
func (s *scanner) Next() bool {
|
||||
if s.rewind {
|
||||
s.rewind = false
|
||||
return true
|
||||
}
|
||||
|
||||
for {
|
||||
if !s.scanToken() {
|
||||
return false
|
||||
}
|
||||
switch s.t.ID {
|
||||
case text:
|
||||
if s.stripToNewLine {
|
||||
s.t.Value = tailOfLine.ReplaceAll(s.t.Value, nil)
|
||||
s.stripToNewLine = false
|
||||
}
|
||||
if len(s.t.Value) == 0 {
|
||||
// skip empty text
|
||||
continue
|
||||
}
|
||||
case tagName:
|
||||
switch string(s.t.Value) {
|
||||
case "comment":
|
||||
if !s.skipComment() {
|
||||
return false
|
||||
}
|
||||
continue
|
||||
case "plain":
|
||||
if !s.readPlain() {
|
||||
return false
|
||||
}
|
||||
if len(s.t.Value) == 0 {
|
||||
// skip empty text
|
||||
continue
|
||||
}
|
||||
case "collapsespace":
|
||||
if !s.readTagContents() {
|
||||
return false
|
||||
}
|
||||
s.collapseSpaceDepth++
|
||||
continue
|
||||
case "stripspace":
|
||||
if !s.readTagContents() {
|
||||
return false
|
||||
}
|
||||
s.stripSpaceDepth++
|
||||
continue
|
||||
case "endcollapsespace":
|
||||
if s.collapseSpaceDepth == 0 {
|
||||
s.err = fmt.Errorf("endcollapsespace tag found without the corresponding collapsespace tag")
|
||||
return false
|
||||
}
|
||||
if !s.readTagContents() {
|
||||
return false
|
||||
}
|
||||
s.collapseSpaceDepth--
|
||||
continue
|
||||
case "endstripspace":
|
||||
if s.stripSpaceDepth == 0 {
|
||||
s.err = fmt.Errorf("endstripspace tag found without the corresponding stripspace tag")
|
||||
return false
|
||||
}
|
||||
if !s.readTagContents() {
|
||||
return false
|
||||
}
|
||||
s.stripSpaceDepth--
|
||||
continue
|
||||
case "space":
|
||||
if !s.readTagContents() {
|
||||
return false
|
||||
}
|
||||
s.t.init(text, s.t.line, s.t.pos)
|
||||
s.t.Value = append(s.t.Value[:0], ' ')
|
||||
return true
|
||||
case "newline":
|
||||
if !s.readTagContents() {
|
||||
return false
|
||||
}
|
||||
s.t.init(text, s.t.line, s.t.pos)
|
||||
s.t.Value = append(s.t.Value[:0], '\n')
|
||||
return true
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func (s *scanner) readPlain() bool {
|
||||
if !s.readTagContents() {
|
||||
return false
|
||||
}
|
||||
startLine := s.line
|
||||
startPos := s.pos()
|
||||
s.startCapture()
|
||||
ok := s.skipUntilTag("endplain")
|
||||
v := s.stopCapture()
|
||||
s.t.init(text, startLine, startPos)
|
||||
if ok {
|
||||
n := bytes.LastIndex(v, strTagOpen)
|
||||
v = v[:n]
|
||||
s.t.Value = append(s.t.Value[:0], v...)
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
var strTagOpen = []byte("{%")
|
||||
|
||||
func (s *scanner) skipComment() bool {
|
||||
if !s.readTagContents() {
|
||||
return false
|
||||
}
|
||||
return s.skipUntilTag("endcomment")
|
||||
}
|
||||
|
||||
func (s *scanner) skipUntilTag(tagName string) bool {
|
||||
ok := false
|
||||
for {
|
||||
if !s.nextByte() {
|
||||
break
|
||||
}
|
||||
if s.c != '{' {
|
||||
continue
|
||||
}
|
||||
if !s.nextByte() {
|
||||
break
|
||||
}
|
||||
if s.c != '%' {
|
||||
s.unreadByte('~')
|
||||
continue
|
||||
}
|
||||
ok = s.readTagName()
|
||||
s.nextTokenID = text
|
||||
if !ok {
|
||||
s.err = nil
|
||||
continue
|
||||
}
|
||||
if string(s.t.Value) == tagName {
|
||||
ok = s.readTagContents()
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
s.err = fmt.Errorf("cannot find %q tag: %s", tagName, s.err)
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
func (s *scanner) scanToken() bool {
|
||||
switch s.nextTokenID {
|
||||
case text:
|
||||
return s.readText()
|
||||
case tagName:
|
||||
return s.readTagName()
|
||||
case tagContents:
|
||||
return s.readTagContents()
|
||||
default:
|
||||
panic(fmt.Sprintf("BUG: unknown nextTokenID %d", s.nextTokenID))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *scanner) readText() bool {
|
||||
s.t.init(text, s.line, s.pos())
|
||||
ok := false
|
||||
for {
|
||||
if !s.nextByte() {
|
||||
ok = (len(s.t.Value) > 0)
|
||||
break
|
||||
}
|
||||
if s.c != '{' {
|
||||
s.appendByte()
|
||||
continue
|
||||
}
|
||||
if !s.nextByte() {
|
||||
s.appendByte()
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
if s.c == '%' {
|
||||
s.nextTokenID = tagName
|
||||
ok = true
|
||||
if !s.nextByte() {
|
||||
s.appendByte()
|
||||
break
|
||||
}
|
||||
if s.c != '-' {
|
||||
s.unreadByte(s.c)
|
||||
break
|
||||
}
|
||||
s.t.Value = prevBlank.ReplaceAll(s.t.Value, nil)
|
||||
break
|
||||
}
|
||||
s.unreadByte('{')
|
||||
s.appendByte()
|
||||
}
|
||||
if s.stripSpaceDepth > 0 {
|
||||
s.t.Value = stripSpace(s.t.Value)
|
||||
} else if s.collapseSpaceDepth > 0 {
|
||||
s.t.Value = collapseSpace(s.t.Value)
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
func (s *scanner) readTagName() bool {
|
||||
s.skipSpace()
|
||||
s.t.init(tagName, s.line, s.pos())
|
||||
for {
|
||||
if s.isSpace() || s.c == '%' {
|
||||
if s.c == '%' {
|
||||
s.unreadByte('~')
|
||||
}
|
||||
s.nextTokenID = tagContents
|
||||
return true
|
||||
}
|
||||
if (s.c >= 'a' && s.c <= 'z') || (s.c >= 'A' && s.c <= 'Z') || (s.c >= '0' && s.c <= '9') || s.c == '=' || s.c == '.' {
|
||||
s.appendByte()
|
||||
if !s.nextByte() {
|
||||
return false
|
||||
}
|
||||
continue
|
||||
}
|
||||
if s.c == '-' {
|
||||
s.unreadByte(s.c)
|
||||
s.nextTokenID = tagContents
|
||||
return true
|
||||
}
|
||||
s.err = fmt.Errorf("unexpected character: '%c'", s.c)
|
||||
s.unreadByte('~')
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (s *scanner) readTagContents() bool {
|
||||
s.skipSpace()
|
||||
s.t.init(tagContents, s.line, s.pos())
|
||||
for {
|
||||
if s.c != '%' {
|
||||
s.appendByte()
|
||||
if !s.nextByte() {
|
||||
return false
|
||||
}
|
||||
continue
|
||||
}
|
||||
if !s.nextByte() {
|
||||
s.appendByte()
|
||||
return false
|
||||
}
|
||||
if s.c == '}' {
|
||||
if bytes.HasSuffix(s.t.Value, []byte("-")) {
|
||||
s.t.Value = s.t.Value[:len(s.t.Value)-1]
|
||||
s.stripToNewLine = true
|
||||
}
|
||||
s.nextTokenID = text
|
||||
s.t.Value = stripTrailingSpace(s.t.Value)
|
||||
return true
|
||||
}
|
||||
s.unreadByte('%')
|
||||
s.appendByte()
|
||||
if !s.nextByte() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *scanner) skipSpace() {
|
||||
for s.nextByte() && s.isSpace() {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *scanner) isSpace() bool {
|
||||
return isSpace(s.c)
|
||||
}
|
||||
|
||||
func (s *scanner) nextByte() bool {
|
||||
if s.err != nil {
|
||||
return false
|
||||
}
|
||||
c, err := s.r.ReadByte()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
s.err = err
|
||||
return false
|
||||
}
|
||||
if c == '\n' {
|
||||
s.line++
|
||||
s.lineStr = s.lineStr[:0]
|
||||
} else {
|
||||
s.lineStr = append(s.lineStr, c)
|
||||
}
|
||||
s.c = c
|
||||
if s.capture {
|
||||
s.capturedValue = append(s.capturedValue, c)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *scanner) startCapture() {
|
||||
s.capture = true
|
||||
s.capturedValue = s.capturedValue[:0]
|
||||
}
|
||||
|
||||
func (s *scanner) stopCapture() []byte {
|
||||
s.capture = false
|
||||
v := s.capturedValue
|
||||
s.capturedValue = s.capturedValue[:0]
|
||||
return v
|
||||
}
|
||||
|
||||
func (s *scanner) Token() *token {
|
||||
return &s.t
|
||||
}
|
||||
|
||||
func (s *scanner) LastError() error {
|
||||
if s.err == nil {
|
||||
return nil
|
||||
}
|
||||
if s.err == io.ErrUnexpectedEOF && s.t.ID == text {
|
||||
if s.collapseSpaceDepth > 0 {
|
||||
return fmt.Errorf("missing endcollapsespace tag at %s", s.Context())
|
||||
}
|
||||
if s.stripSpaceDepth > 0 {
|
||||
return fmt.Errorf("missing endstripspace tag at %s", s.Context())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("error when reading %s at %s: %s",
|
||||
tokenIDToStr(s.t.ID), s.Context(), s.err)
|
||||
}
|
||||
|
||||
func (s *scanner) appendByte() {
|
||||
s.t.Value = append(s.t.Value, s.c)
|
||||
}
|
||||
|
||||
func (s *scanner) unreadByte(c byte) {
|
||||
if err := s.r.UnreadByte(); err != nil {
|
||||
panic(fmt.Sprintf("BUG: bufio.Reader.UnreadByte returned non-nil error: %s", err))
|
||||
}
|
||||
if s.capture {
|
||||
s.capturedValue = s.capturedValue[:len(s.capturedValue)-1]
|
||||
}
|
||||
if s.c == '\n' {
|
||||
s.line--
|
||||
s.lineStr = s.lineStr[:0] // TODO: use correct line
|
||||
} else {
|
||||
s.lineStr = s.lineStr[:len(s.lineStr)-1]
|
||||
}
|
||||
s.c = c
|
||||
}
|
||||
|
||||
func (s *scanner) pos() int {
|
||||
return len(s.lineStr)
|
||||
}
|
||||
|
||||
func (s *scanner) Context() string {
|
||||
t := s.Token()
|
||||
return fmt.Sprintf("file %q, line %d, pos %d, token %s, last line %s",
|
||||
s.filePath, t.line+1, t.pos, snippet(t.Value), snippet(s.lineStr))
|
||||
}
|
||||
|
||||
func (s *scanner) WriteLineComment(w io.Writer) {
|
||||
fmt.Fprintf(w, "//line %s:%d\n", s.filePath, s.t.line+1)
|
||||
}
|
||||
|
||||
func snippet(s []byte) string {
|
||||
if len(s) <= 40 {
|
||||
return fmt.Sprintf("%q", s)
|
||||
}
|
||||
return fmt.Sprintf("%q ... %q", s[:20], s[len(s)-20:])
|
||||
}
|
||||
96
vendor/github.com/valyala/quicktemplate/parser/util.go
generated
vendored
Normal file
96
vendor/github.com/valyala/quicktemplate/parser/util.go
generated
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// mangleSuffix is used for mangling quicktemplate-specific names
|
||||
// in the generated code, so they don't clash with user-provided names.
|
||||
const mangleSuffix = "422016"
|
||||
|
||||
func stripLeadingSpace(b []byte) []byte {
|
||||
for len(b) > 0 && isSpace(b[0]) {
|
||||
b = b[1:]
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func stripTrailingSpace(b []byte) []byte {
|
||||
for len(b) > 0 && isSpace(b[len(b)-1]) {
|
||||
b = b[:len(b)-1]
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func collapseSpace(b []byte) []byte {
|
||||
return stripSpaceExt(b, true)
|
||||
}
|
||||
|
||||
func stripSpace(b []byte) []byte {
|
||||
return stripSpaceExt(b, false)
|
||||
}
|
||||
|
||||
func stripSpaceExt(b []byte, isCollapse bool) []byte {
|
||||
if len(b) == 0 {
|
||||
return b
|
||||
}
|
||||
|
||||
var dst []byte
|
||||
if isCollapse && isSpace(b[0]) {
|
||||
dst = append(dst, ' ')
|
||||
}
|
||||
isLastSpace := isSpace(b[len(b)-1])
|
||||
for len(b) > 0 {
|
||||
n := bytes.IndexByte(b, '\n')
|
||||
if n < 0 {
|
||||
n = len(b)
|
||||
}
|
||||
z := b[:n]
|
||||
if n == len(b) {
|
||||
b = b[n:]
|
||||
} else {
|
||||
b = b[n+1:]
|
||||
}
|
||||
z = stripLeadingSpace(z)
|
||||
z = stripTrailingSpace(z)
|
||||
if len(z) == 0 {
|
||||
continue
|
||||
}
|
||||
dst = append(dst, z...)
|
||||
if isCollapse {
|
||||
dst = append(dst, ' ')
|
||||
}
|
||||
}
|
||||
if isCollapse && !isLastSpace && len(dst) > 0 {
|
||||
dst = dst[:len(dst)-1]
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func isSpace(c byte) bool {
|
||||
return unicode.IsSpace(rune(c))
|
||||
}
|
||||
|
||||
func isUpper(c byte) bool {
|
||||
return unicode.IsUpper(rune(c))
|
||||
}
|
||||
|
||||
func readFile(cwd, filename string) ([]byte, error) {
|
||||
if len(filename) == 0 {
|
||||
return nil, errors.New("filename cannot be empty")
|
||||
}
|
||||
if filename[0] != '/' {
|
||||
cwdAbs, err := filepath.Abs(cwd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dir, _ := filepath.Split(cwdAbs)
|
||||
filename = filepath.Join(dir, filename)
|
||||
}
|
||||
|
||||
return ioutil.ReadFile(filename)
|
||||
}
|
||||
22
vendor/github.com/valyala/quicktemplate/qtc/LICENSE
generated
vendored
Normal file
22
vendor/github.com/valyala/quicktemplate/qtc/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Aliaksandr Valialkin, VertaMedia
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
33
vendor/github.com/valyala/quicktemplate/qtc/README.md
generated
vendored
Normal file
33
vendor/github.com/valyala/quicktemplate/qtc/README.md
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
# qtc
|
||||
|
||||
Template compiler (converter) for [quicktemplate](https://github.com/valyala/quicktemplate).
|
||||
Converts quicktemplate files into Go code. By default these files
|
||||
have `.qtpl` extension.
|
||||
|
||||
# Usage
|
||||
|
||||
```
|
||||
$ go get -u github.com/valyala/quicktemplate/qtc
|
||||
$ qtc -h
|
||||
```
|
||||
|
||||
`qtc` may be called either directly or via [go generate](https://blog.golang.org/generate).
|
||||
The latter case is preffered. Just put the following line near the `main` function:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
//go:generate qtc -dir=path/to/directory/with/templates
|
||||
|
||||
func main() {
|
||||
// main code here
|
||||
}
|
||||
```
|
||||
|
||||
Then run `go generate` whenever you need re-generating template code.
|
||||
Directory with templates may contain arbirary number of subdirectories -
|
||||
`qtc` generates template code recursively for each subdirectory.
|
||||
|
||||
Directories with templates may also contain arbitrary `.go` files - contents
|
||||
of these files may be used inside templates. Such Go files usually contain
|
||||
various helper functions and structs.
|
||||
174
vendor/github.com/valyala/quicktemplate/qtc/main.go
generated
vendored
Normal file
174
vendor/github.com/valyala/quicktemplate/qtc/main.go
generated
vendored
Normal file
@@ -0,0 +1,174 @@
|
||||
// Command qtc is a compiler for quicktemplate files.
|
||||
//
|
||||
// See https://github.com/valyala/quicktemplate/qtc for details.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"go/format"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/valyala/quicktemplate/parser"
|
||||
)
|
||||
|
||||
var (
|
||||
dir = flag.String("dir", ".", "Path to directory with template files to compile. "+
|
||||
"Only files with ext extension are compiled. See ext flag for details.\n"+
|
||||
"The compiler recursively processes all the subdirectories.\n"+
|
||||
"Compiled template files are placed near the original file with .go extension added.")
|
||||
|
||||
file = flag.String("file", "", "Path to template file to compile.\n"+
|
||||
"Flags -dir and -ext are ignored if file is set.\n"+
|
||||
"The compiled file will be placed near the original file with .go extension added.")
|
||||
ext = flag.String("ext", "qtpl", "Only files with this extension are compiled")
|
||||
skipLineComments = flag.Bool("skipLineComments", false, "Don't write line comments")
|
||||
)
|
||||
|
||||
var logger = log.New(os.Stderr, "qtc: ", log.LstdFlags)
|
||||
|
||||
var filesCompiled int
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
if len(*file) > 0 {
|
||||
compileSingleFile(*file)
|
||||
return
|
||||
}
|
||||
|
||||
if len(*ext) == 0 {
|
||||
logger.Fatalf("ext cannot be empty")
|
||||
}
|
||||
if len(*dir) == 0 {
|
||||
*dir = "."
|
||||
}
|
||||
if (*ext)[0] != '.' {
|
||||
*ext = "." + *ext
|
||||
}
|
||||
|
||||
logger.Printf("Compiling *%s template files in directory %q", *ext, *dir)
|
||||
compileDir(*dir)
|
||||
logger.Printf("Total files compiled: %d", filesCompiled)
|
||||
}
|
||||
|
||||
func compileSingleFile(filename string) {
|
||||
fi, err := os.Stat(filename)
|
||||
if err != nil {
|
||||
logger.Fatalf("cannot stat file %q: %s", filename, err)
|
||||
}
|
||||
if fi.IsDir() {
|
||||
logger.Fatalf("cannot compile directory %q. Use -dir flag", filename)
|
||||
}
|
||||
compileFile(filename)
|
||||
}
|
||||
|
||||
func compileDir(path string) {
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
logger.Fatalf("cannot compile files in %q: %s", path, err)
|
||||
}
|
||||
if !fi.IsDir() {
|
||||
logger.Fatalf("cannot compile files in %q: it is not directory", path)
|
||||
}
|
||||
d, err := os.Open(path)
|
||||
if err != nil {
|
||||
logger.Fatalf("cannot compile files in %q: %s", path, err)
|
||||
}
|
||||
defer d.Close()
|
||||
|
||||
fis, err := d.Readdir(-1)
|
||||
if err != nil {
|
||||
logger.Fatalf("cannot read files in %q: %s", path, err)
|
||||
}
|
||||
|
||||
var names []string
|
||||
for _, fi = range fis {
|
||||
name := fi.Name()
|
||||
if name == "." || name == ".." {
|
||||
continue
|
||||
}
|
||||
if !fi.IsDir() {
|
||||
names = append(names, name)
|
||||
} else {
|
||||
subPath := filepath.Join(path, name)
|
||||
compileDir(subPath)
|
||||
}
|
||||
}
|
||||
sort.Strings(names)
|
||||
|
||||
for _, name := range names {
|
||||
if strings.HasSuffix(name, *ext) {
|
||||
filename := filepath.Join(path, name)
|
||||
compileFile(filename)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func compileFile(infile string) {
|
||||
outfile := infile + ".go"
|
||||
logger.Printf("Compiling %q to %q...", infile, outfile)
|
||||
|
||||
inf, err := os.Open(infile)
|
||||
if err != nil {
|
||||
logger.Fatalf("cannot open file %q: %s", infile, err)
|
||||
}
|
||||
|
||||
tmpfile := outfile + ".tmp"
|
||||
outf, err := os.Create(tmpfile)
|
||||
if err != nil {
|
||||
logger.Fatalf("cannot create file %q: %s", tmpfile, err)
|
||||
}
|
||||
|
||||
packageName, err := getPackageName(infile)
|
||||
if err != nil {
|
||||
logger.Fatalf("cannot determine package name for %q: %s", infile, err)
|
||||
}
|
||||
|
||||
parseFunc := parser.Parse
|
||||
if *skipLineComments {
|
||||
parseFunc = parser.ParseNoLineComments
|
||||
}
|
||||
|
||||
if err = parseFunc(outf, inf, infile, packageName); err != nil {
|
||||
logger.Fatalf("error when parsing file %q: %s", infile, err)
|
||||
}
|
||||
|
||||
if err = outf.Close(); err != nil {
|
||||
logger.Fatalf("error when closing file %q: %s", tmpfile, err)
|
||||
}
|
||||
if err = inf.Close(); err != nil {
|
||||
logger.Fatalf("error when closing file %q: %s", infile, err)
|
||||
}
|
||||
|
||||
// prettify the output file
|
||||
uglyCode, err := ioutil.ReadFile(tmpfile)
|
||||
if err != nil {
|
||||
logger.Fatalf("cannot read file %q: %s", tmpfile, err)
|
||||
}
|
||||
prettyCode, err := format.Source(uglyCode)
|
||||
if err != nil {
|
||||
logger.Fatalf("error when formatting compiled code for %q: %s. See %q for details", infile, err, tmpfile)
|
||||
}
|
||||
if err = ioutil.WriteFile(outfile, prettyCode, 0666); err != nil {
|
||||
logger.Fatalf("error when writing file %q: %s", outfile, err)
|
||||
}
|
||||
if err = os.Remove(tmpfile); err != nil {
|
||||
logger.Fatalf("error when removing file %q: %s", tmpfile, err)
|
||||
}
|
||||
|
||||
filesCompiled++
|
||||
}
|
||||
|
||||
func getPackageName(filename string) (string, error) {
|
||||
filenameAbs, err := filepath.Abs(filename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
dir, _ := filepath.Split(filenameAbs)
|
||||
return filepath.Base(dir), nil
|
||||
}
|
||||
6
vendor/modules.txt
vendored
6
vendor/modules.txt
vendored
@@ -560,9 +560,13 @@ github.com/puzpuzpuz/xsync/v3
|
||||
# github.com/rivo/uniseg v0.4.7
|
||||
## explicit; go 1.18
|
||||
github.com/rivo/uniseg
|
||||
# github.com/rogpeppe/go-internal v1.14.1
|
||||
## explicit; go 1.23
|
||||
# github.com/russross/blackfriday/v2 v2.1.0
|
||||
## explicit
|
||||
github.com/russross/blackfriday/v2
|
||||
# github.com/spf13/pflag v1.0.6
|
||||
## explicit; go 1.12
|
||||
# github.com/stretchr/testify v1.10.0
|
||||
## explicit; go 1.17
|
||||
github.com/stretchr/testify/assert
|
||||
@@ -593,6 +597,8 @@ github.com/valyala/histogram
|
||||
# github.com/valyala/quicktemplate v1.8.0
|
||||
## explicit; go 1.17
|
||||
github.com/valyala/quicktemplate
|
||||
github.com/valyala/quicktemplate/parser
|
||||
github.com/valyala/quicktemplate/qtc
|
||||
# github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1
|
||||
## explicit; go 1.15
|
||||
github.com/xrash/smetrics
|
||||
|
||||
Reference in New Issue
Block a user