Compare commits

...

3 Commits

Author SHA1 Message Date
Zakhar Bessarab
1d6340fe83 deps: go mod vendor after 1334eee4
Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-06-10 18:29:25 +04:00
Zakhar Bessarab
1334eee433 make/tools: switch only qtc to "go tool"
Restore other tools to migrate step by step.

Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-06-10 18:28:31 +04:00
Zakhar Bessarab
2d67e14d59 make/tools: switch tooling to use go tool for external tools
Go 1.24 added first class support for management of tools via built-in `go tool` commands - https://tip.golang.org/doc/go1.24#tools

Switching tools management to go tooling addresses the following:
- version drift - previously make commands did not verify specific version of the tool
- global installation of dependencies - dependencies are now well scoped and do not require any globally installed parts

This commit sets up tooling to use `go tool` pattern for the same make targets as previously so there are no changes in workflow needed for existing users.

Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-06-06 15:04:44 +04:00
11 changed files with 1936 additions and 11 deletions

View File

@@ -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
View File

@@ -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
View File

@@ -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=

View 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")
}
}
}

View 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
}

View 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
View 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
View 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
View 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
View 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
View File

@@ -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