mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2026-06-27 20:47:28 +03:00
Compare commits
3 Commits
make/use-g
...
v1.119.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb54075c23 | ||
|
|
08f5220bc3 | ||
|
|
f9015da6eb |
11
Makefile
11
Makefile
@@ -528,6 +528,8 @@ vet:
|
||||
|
||||
check-all: fmt vet golangci-lint govulncheck
|
||||
|
||||
clean-checkers: remove-golangci-lint remove-govulncheck
|
||||
|
||||
test:
|
||||
GOEXPERIMENT=synctest go test ./lib/... ./app/...
|
||||
|
||||
@@ -572,11 +574,12 @@ 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:
|
||||
go tool qtc
|
||||
quicktemplate-gen: install-qtc
|
||||
qtc
|
||||
|
||||
install-qtc:
|
||||
which qtc || go install github.com/valyala/quicktemplate/qtc@latest
|
||||
|
||||
golangci-lint:
|
||||
GOEXPERIMENT=synctest go tool golangci-lint run
|
||||
|
||||
golangci-lint: install-golangci-lint
|
||||
GOEXPERIMENT=synctest golangci-lint run
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -35,10 +35,10 @@
|
||||
<meta property="og:title" content="UI for VictoriaLogs">
|
||||
<meta property="og:url" content="https://victoriametrics.com/products/victorialogs/">
|
||||
<meta property="og:description" content="Explore your log data with VictoriaLogs UI">
|
||||
<script type="module" crossorigin src="./assets/index-BaRvaPfA.js"></script>
|
||||
<script type="module" crossorigin src="./assets/index-DhqzKCNf.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="./assets/vendor-D8IJGiEn.js">
|
||||
<link rel="stylesheet" crossorigin href="./assets/vendor-D1GxaB_c.css">
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-C85_NB5q.css">
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-D5re9hC6.css">
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
||||
@@ -818,6 +818,7 @@ func QueryHandler(qt *querytracer.Tracer, startTime time.Time, w http.ResponseWr
|
||||
LookbackDelta: lookbackDelta,
|
||||
RoundDigits: getRoundDigits(r),
|
||||
EnforcedTagFilterss: etfs,
|
||||
CacheTagFilters: etfs,
|
||||
GetRequestURI: func() string {
|
||||
return httpserver.GetRequestURI(r)
|
||||
},
|
||||
@@ -927,6 +928,7 @@ func queryRangeHandler(qt *querytracer.Tracer, startTime time.Time, w http.Respo
|
||||
LookbackDelta: lookbackDelta,
|
||||
RoundDigits: getRoundDigits(r),
|
||||
EnforcedTagFilterss: etfs,
|
||||
CacheTagFilters: etfs,
|
||||
GetRequestURI: func() string {
|
||||
return httpserver.GetRequestURI(r)
|
||||
},
|
||||
|
||||
@@ -140,6 +140,13 @@ type EvalConfig struct {
|
||||
// EnforcedTagFilterss may contain additional label filters to use in the query.
|
||||
EnforcedTagFilterss [][]storage.TagFilter
|
||||
|
||||
// CacheTagFilters stores the original tag-filter sets and extra_label from the request.
|
||||
// The slice is never modified after creation and is used only to build
|
||||
// the query-cache key.
|
||||
//
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9001
|
||||
CacheTagFilters [][]storage.TagFilter
|
||||
|
||||
// The callback, which returns the request URI during logging.
|
||||
// The request URI isn't stored here because its' construction may take non-trivial amounts of CPU.
|
||||
GetRequestURI func() string
|
||||
@@ -166,6 +173,7 @@ func copyEvalConfig(src *EvalConfig) *EvalConfig {
|
||||
ec.LookbackDelta = src.LookbackDelta
|
||||
ec.RoundDigits = src.RoundDigits
|
||||
ec.EnforcedTagFilterss = src.EnforcedTagFilterss
|
||||
ec.CacheTagFilters = src.CacheTagFilters
|
||||
ec.GetRequestURI = src.GetRequestURI
|
||||
ec.QueryStats = src.QueryStats
|
||||
|
||||
|
||||
@@ -291,7 +291,7 @@ func (rrc *rollupResultCache) GetSeries(qt *querytracer.Tracer, ec *EvalConfig,
|
||||
bb := bbPool.Get()
|
||||
defer bbPool.Put(bb)
|
||||
|
||||
bb.B = marshalRollupResultCacheKeyForSeries(bb.B[:0], expr, window, ec.Step, ec.EnforcedTagFilterss)
|
||||
bb.B = marshalRollupResultCacheKeyForSeries(bb.B[:0], expr, window, ec.Step, ec.CacheTagFilters)
|
||||
metainfoBuf := rrc.c.Get(nil, bb.B)
|
||||
if len(metainfoBuf) == 0 {
|
||||
qt.Printf("nothing found")
|
||||
@@ -313,7 +313,7 @@ func (rrc *rollupResultCache) GetSeries(qt *querytracer.Tracer, ec *EvalConfig,
|
||||
if !ok {
|
||||
mi.RemoveKey(key)
|
||||
metainfoBuf = mi.Marshal(metainfoBuf[:0])
|
||||
bb.B = marshalRollupResultCacheKeyForSeries(bb.B[:0], expr, window, ec.Step, ec.EnforcedTagFilterss)
|
||||
bb.B = marshalRollupResultCacheKeyForSeries(bb.B[:0], expr, window, ec.Step, ec.CacheTagFilters)
|
||||
rrc.c.Set(bb.B, metainfoBuf)
|
||||
return nil, ec.Start
|
||||
}
|
||||
@@ -419,7 +419,7 @@ func (rrc *rollupResultCache) PutSeries(qt *querytracer.Tracer, ec *EvalConfig,
|
||||
metainfoBuf := bbPool.Get()
|
||||
defer bbPool.Put(metainfoBuf)
|
||||
|
||||
metainfoKey.B = marshalRollupResultCacheKeyForSeries(metainfoKey.B[:0], expr, window, ec.Step, ec.EnforcedTagFilterss)
|
||||
metainfoKey.B = marshalRollupResultCacheKeyForSeries(metainfoKey.B[:0], expr, window, ec.Step, ec.CacheTagFilters)
|
||||
metainfoBuf.B = rrc.c.Get(metainfoBuf.B[:0], metainfoKey.B)
|
||||
var mi rollupResultCacheMetainfo
|
||||
if len(metainfoBuf.B) > 0 {
|
||||
|
||||
209
app/vmselect/vmui/assets/index-D-ssBbZq.js
Normal file
209
app/vmselect/vmui/assets/index-D-ssBbZq.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -36,10 +36,10 @@
|
||||
<meta property="og:title" content="UI for VictoriaMetrics">
|
||||
<meta property="og:url" content="https://victoriametrics.com/">
|
||||
<meta property="og:description" content="Explore and troubleshoot your VictoriaMetrics data">
|
||||
<script type="module" crossorigin src="./assets/index-xmjGcv4-.js"></script>
|
||||
<script type="module" crossorigin src="./assets/index-D-ssBbZq.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="./assets/vendor-D8IJGiEn.js">
|
||||
<link rel="stylesheet" crossorigin href="./assets/vendor-D1GxaB_c.css">
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-C85_NB5q.css">
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-D5re9hC6.css">
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
||||
78
apptest/tests/rollup_result_cache_test.go
Normal file
78
apptest/tests/rollup_result_cache_test.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/apptest"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
)
|
||||
|
||||
func TestClusterRollupResultCache(t *testing.T) {
|
||||
os.RemoveAll(t.Name())
|
||||
|
||||
cmpOpt := cmpopts.IgnoreFields(apptest.PrometheusAPIV1QueryResponse{}, "Status", "Data.ResultType")
|
||||
|
||||
tc := apptest.NewTestCase(t)
|
||||
defer tc.Stop()
|
||||
vmstorage := tc.MustStartVmstorage("vmstorage", []string{
|
||||
"-storageDataPath=" + tc.Dir() + "/vmstorage",
|
||||
"-retentionPeriod=100y",
|
||||
})
|
||||
vminsert := tc.MustStartVminsert("vminsert", []string{
|
||||
"-storageNode=" + vmstorage.VminsertAddr(),
|
||||
})
|
||||
vmselect := tc.MustStartVmselect("vmselect", []string{
|
||||
"-storageNode=" + vmstorage.VmselectAddr(),
|
||||
"-search.tenantCacheExpireDuration=0",
|
||||
})
|
||||
|
||||
var tenantLabelsSamples = []string{
|
||||
`foo_bar{vm_account_id="5"} 1.00 1652169720000`, // 2022-05-10T08:00:00Z'
|
||||
`foo_bar{vm_account_id="5",vm_project_id="15"} 3.00 1652169720000`, // 2022-05-10T08:02:00Z
|
||||
}
|
||||
|
||||
vminsert.PrometheusAPIV1ImportPrometheus(t, tenantLabelsSamples, apptest.QueryOpts{Tenant: "multitenant"})
|
||||
vmstorage.ForceFlush(t)
|
||||
|
||||
want := apptest.NewPrometheusAPIV1QueryResponse(t,
|
||||
`{"data":
|
||||
{"result":[
|
||||
{"metric":{"__name__":"foo_bar","vm_account_id":"5","vm_project_id": "0"},"values":[[1652169720,"1"],[1652169780,"1"]]},
|
||||
{"metric":{"__name__":"foo_bar","vm_account_id":"5","vm_project_id":"15"},"values":[[1652169720,"3"],[1652169780,"3"]]}
|
||||
]
|
||||
}
|
||||
}`,
|
||||
)
|
||||
|
||||
got := vmselect.PrometheusAPIV1QueryRange(t, `foo_bar{}`, apptest.QueryOpts{
|
||||
Tenant: "multitenant",
|
||||
Start: "2022-05-10T07:59:00.000Z",
|
||||
End: "2022-05-10T08:05:00.000Z",
|
||||
Step: "1m",
|
||||
ExtraFilters: []string{`{vm_account_id="5",vm_project_id="15"}`, `{vm_account_id="5",vm_project_id="0"}`},
|
||||
})
|
||||
if diff := cmp.Diff(want, got, cmpOpt); diff != "" {
|
||||
t.Errorf("unexpected response (-want, +got):\n%s", diff)
|
||||
}
|
||||
|
||||
want = apptest.NewPrometheusAPIV1QueryResponse(t,
|
||||
`{"data":
|
||||
{"result":[]}
|
||||
}`,
|
||||
)
|
||||
|
||||
got = vmselect.PrometheusAPIV1QueryRange(t, `foo_bar{}`, apptest.QueryOpts{
|
||||
Tenant: "multitenant",
|
||||
Start: "2022-05-10T07:59:00.000Z",
|
||||
End: "2022-05-10T08:05:00.000Z",
|
||||
Step: "1m",
|
||||
ExtraFilters: []string{`{vm_account_id="99",vm_project_id="99"}`},
|
||||
})
|
||||
if diff := cmp.Diff(want, got, cmpOpt); diff != "" {
|
||||
t.Errorf("unexpected response (-want, +got):\n%s", diff)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,6 +18,10 @@ See also [LTS releases](https://docs.victoriametrics.com/victoriametrics/lts-rel
|
||||
|
||||
## tip
|
||||
|
||||
## [v1.119.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.119.0)
|
||||
|
||||
Released at 2025-06-06
|
||||
|
||||
* FEATURE: improve performance on systems with many CPU cores by removing the top sources of [false sharing](https://en.wikipedia.org/wiki/False_sharing) at global variables. See [#8682](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8682). Thanks to @tIGO for raising this issue and for the initial attempt to fix it at the [PR #8683](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/8683).
|
||||
* FEATURE: [vmgateway](https://docs.victoriametrics.com/victoriametrics/vmgateway/): add an option to use mTLS for connections to `-write.url` and `-read.url`. See [#8841](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8841).
|
||||
* FEATURE: [vmselect](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/) in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/): dynamically adjusts the concurrent dial limit between 8 and 64 based on `-search.maxConcurrentRequests`. Additionally, goroutines now have the opportunity to access available connections while awaiting the dial limit token. This enables faster connection establishment when sudden requests arrive and reduces the blocking time during the availability check for connections. See [#8922](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/8922)
|
||||
@@ -38,6 +42,7 @@ See also [LTS releases](https://docs.victoriametrics.com/victoriametrics/lts-rel
|
||||
* BUGFIX: [vmctl](https://docs.victoriametrics.com/victoriametrics/vmctl/): enable dual-stack network mode for `vmctl`connections by default. This allows connecting to IPv6 endpoints as it was not possible previously. See [#9116](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9116).
|
||||
* BUGFIX: [vmalert-tool](https://docs.victoriametrics.com/victoriametrics/vmalert-tool/): fix access conflicts for the temporary test folder when multiple users run tests on the same host. Thanks to @evkuzin for the [PR 9015](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/9015).
|
||||
* BUGFIX: [alerts](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/deployment/docker/rules): fix the alerting rule `ScrapePoolHasNoTargets`. Previously, it may cause false positive in [sharding mode](https://docs.victoriametrics.com/victoriametrics/vmagent/#scraping-big-number-of-targets).
|
||||
* BUGFIX: [vmselect](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/) in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/): include full list of query filters to build a cache key for [multitenant read](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#multitenancy-via-labels) queries. Previously, cache key did not include tenancy related labels so cache entries could be shared between tenants breaking tenant isolation. See [#9002](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9002) for details.
|
||||
|
||||
## [v1.118.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.118.0)
|
||||
|
||||
|
||||
4
go.mod
4
go.mod
@@ -135,9 +135,7 @@ 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
|
||||
@@ -182,5 +180,3 @@ 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.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
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/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.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
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/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
205
vendor/github.com/valyala/quicktemplate/parser/functype.go
generated
vendored
@@ -1,205 +0,0 @@
|
||||
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
921
vendor/github.com/valyala/quicktemplate/parser/parser.go
generated
vendored
@@ -1,921 +0,0 @@
|
||||
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
467
vendor/github.com/valyala/quicktemplate/parser/scanner.go
generated
vendored
@@ -1,467 +0,0 @@
|
||||
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
96
vendor/github.com/valyala/quicktemplate/parser/util.go
generated
vendored
@@ -1,96 +0,0 @@
|
||||
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
22
vendor/github.com/valyala/quicktemplate/qtc/LICENSE
generated
vendored
@@ -1,22 +0,0 @@
|
||||
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
33
vendor/github.com/valyala/quicktemplate/qtc/README.md
generated
vendored
@@ -1,33 +0,0 @@
|
||||
# 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
174
vendor/github.com/valyala/quicktemplate/qtc/main.go
generated
vendored
@@ -1,174 +0,0 @@
|
||||
// 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,13 +560,9 @@ 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
|
||||
@@ -597,8 +593,6 @@ 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