mirror of
https://github.com/NotAShelf/goblin.git
synced 2024-11-22 13:20:45 +00:00
chore: provide vendor directory
This commit is contained in:
parent
d321f8ffe7
commit
288c1ee1d1
1003 changed files with 407928 additions and 0 deletions
20
vendor/github.com/beorn7/perks/LICENSE
generated
vendored
Normal file
20
vendor/github.com/beorn7/perks/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
Copyright (C) 2013 Blake Mizerany
|
||||||
|
|
||||||
|
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.
|
2388
vendor/github.com/beorn7/perks/quantile/exampledata.txt
generated
vendored
Normal file
2388
vendor/github.com/beorn7/perks/quantile/exampledata.txt
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
316
vendor/github.com/beorn7/perks/quantile/stream.go
generated
vendored
Normal file
316
vendor/github.com/beorn7/perks/quantile/stream.go
generated
vendored
Normal file
|
@ -0,0 +1,316 @@
|
||||||
|
// Package quantile computes approximate quantiles over an unbounded data
|
||||||
|
// stream within low memory and CPU bounds.
|
||||||
|
//
|
||||||
|
// A small amount of accuracy is traded to achieve the above properties.
|
||||||
|
//
|
||||||
|
// Multiple streams can be merged before calling Query to generate a single set
|
||||||
|
// of results. This is meaningful when the streams represent the same type of
|
||||||
|
// data. See Merge and Samples.
|
||||||
|
//
|
||||||
|
// For more detailed information about the algorithm used, see:
|
||||||
|
//
|
||||||
|
// Effective Computation of Biased Quantiles over Data Streams
|
||||||
|
//
|
||||||
|
// http://www.cs.rutgers.edu/~muthu/bquant.pdf
|
||||||
|
package quantile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Sample holds an observed value and meta information for compression. JSON
|
||||||
|
// tags have been added for convenience.
|
||||||
|
type Sample struct {
|
||||||
|
Value float64 `json:",string"`
|
||||||
|
Width float64 `json:",string"`
|
||||||
|
Delta float64 `json:",string"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Samples represents a slice of samples. It implements sort.Interface.
|
||||||
|
type Samples []Sample
|
||||||
|
|
||||||
|
func (a Samples) Len() int { return len(a) }
|
||||||
|
func (a Samples) Less(i, j int) bool { return a[i].Value < a[j].Value }
|
||||||
|
func (a Samples) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
|
||||||
|
type invariant func(s *stream, r float64) float64
|
||||||
|
|
||||||
|
// NewLowBiased returns an initialized Stream for low-biased quantiles
|
||||||
|
// (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but
|
||||||
|
// error guarantees can still be given even for the lower ranks of the data
|
||||||
|
// distribution.
|
||||||
|
//
|
||||||
|
// The provided epsilon is a relative error, i.e. the true quantile of a value
|
||||||
|
// returned by a query is guaranteed to be within (1±Epsilon)*Quantile.
|
||||||
|
//
|
||||||
|
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error
|
||||||
|
// properties.
|
||||||
|
func NewLowBiased(epsilon float64) *Stream {
|
||||||
|
ƒ := func(s *stream, r float64) float64 {
|
||||||
|
return 2 * epsilon * r
|
||||||
|
}
|
||||||
|
return newStream(ƒ)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHighBiased returns an initialized Stream for high-biased quantiles
|
||||||
|
// (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but
|
||||||
|
// error guarantees can still be given even for the higher ranks of the data
|
||||||
|
// distribution.
|
||||||
|
//
|
||||||
|
// The provided epsilon is a relative error, i.e. the true quantile of a value
|
||||||
|
// returned by a query is guaranteed to be within 1-(1±Epsilon)*(1-Quantile).
|
||||||
|
//
|
||||||
|
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error
|
||||||
|
// properties.
|
||||||
|
func NewHighBiased(epsilon float64) *Stream {
|
||||||
|
ƒ := func(s *stream, r float64) float64 {
|
||||||
|
return 2 * epsilon * (s.n - r)
|
||||||
|
}
|
||||||
|
return newStream(ƒ)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTargeted returns an initialized Stream concerned with a particular set of
|
||||||
|
// quantile values that are supplied a priori. Knowing these a priori reduces
|
||||||
|
// space and computation time. The targets map maps the desired quantiles to
|
||||||
|
// their absolute errors, i.e. the true quantile of a value returned by a query
|
||||||
|
// is guaranteed to be within (Quantile±Epsilon).
|
||||||
|
//
|
||||||
|
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error properties.
|
||||||
|
func NewTargeted(targetMap map[float64]float64) *Stream {
|
||||||
|
// Convert map to slice to avoid slow iterations on a map.
|
||||||
|
// ƒ is called on the hot path, so converting the map to a slice
|
||||||
|
// beforehand results in significant CPU savings.
|
||||||
|
targets := targetMapToSlice(targetMap)
|
||||||
|
|
||||||
|
ƒ := func(s *stream, r float64) float64 {
|
||||||
|
var m = math.MaxFloat64
|
||||||
|
var f float64
|
||||||
|
for _, t := range targets {
|
||||||
|
if t.quantile*s.n <= r {
|
||||||
|
f = (2 * t.epsilon * r) / t.quantile
|
||||||
|
} else {
|
||||||
|
f = (2 * t.epsilon * (s.n - r)) / (1 - t.quantile)
|
||||||
|
}
|
||||||
|
if f < m {
|
||||||
|
m = f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
return newStream(ƒ)
|
||||||
|
}
|
||||||
|
|
||||||
|
type target struct {
|
||||||
|
quantile float64
|
||||||
|
epsilon float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func targetMapToSlice(targetMap map[float64]float64) []target {
|
||||||
|
targets := make([]target, 0, len(targetMap))
|
||||||
|
|
||||||
|
for quantile, epsilon := range targetMap {
|
||||||
|
t := target{
|
||||||
|
quantile: quantile,
|
||||||
|
epsilon: epsilon,
|
||||||
|
}
|
||||||
|
targets = append(targets, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
return targets
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stream computes quantiles for a stream of float64s. It is not thread-safe by
|
||||||
|
// design. Take care when using across multiple goroutines.
|
||||||
|
type Stream struct {
|
||||||
|
*stream
|
||||||
|
b Samples
|
||||||
|
sorted bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStream(ƒ invariant) *Stream {
|
||||||
|
x := &stream{ƒ: ƒ}
|
||||||
|
return &Stream{x, make(Samples, 0, 500), true}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert inserts v into the stream.
|
||||||
|
func (s *Stream) Insert(v float64) {
|
||||||
|
s.insert(Sample{Value: v, Width: 1})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stream) insert(sample Sample) {
|
||||||
|
s.b = append(s.b, sample)
|
||||||
|
s.sorted = false
|
||||||
|
if len(s.b) == cap(s.b) {
|
||||||
|
s.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query returns the computed qth percentiles value. If s was created with
|
||||||
|
// NewTargeted, and q is not in the set of quantiles provided a priori, Query
|
||||||
|
// will return an unspecified result.
|
||||||
|
func (s *Stream) Query(q float64) float64 {
|
||||||
|
if !s.flushed() {
|
||||||
|
// Fast path when there hasn't been enough data for a flush;
|
||||||
|
// this also yields better accuracy for small sets of data.
|
||||||
|
l := len(s.b)
|
||||||
|
if l == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
i := int(math.Ceil(float64(l) * q))
|
||||||
|
if i > 0 {
|
||||||
|
i -= 1
|
||||||
|
}
|
||||||
|
s.maybeSort()
|
||||||
|
return s.b[i].Value
|
||||||
|
}
|
||||||
|
s.flush()
|
||||||
|
return s.stream.query(q)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge merges samples into the underlying streams samples. This is handy when
|
||||||
|
// merging multiple streams from separate threads, database shards, etc.
|
||||||
|
//
|
||||||
|
// ATTENTION: This method is broken and does not yield correct results. The
|
||||||
|
// underlying algorithm is not capable of merging streams correctly.
|
||||||
|
func (s *Stream) Merge(samples Samples) {
|
||||||
|
sort.Sort(samples)
|
||||||
|
s.stream.merge(samples)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset reinitializes and clears the list reusing the samples buffer memory.
|
||||||
|
func (s *Stream) Reset() {
|
||||||
|
s.stream.reset()
|
||||||
|
s.b = s.b[:0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Samples returns stream samples held by s.
|
||||||
|
func (s *Stream) Samples() Samples {
|
||||||
|
if !s.flushed() {
|
||||||
|
return s.b
|
||||||
|
}
|
||||||
|
s.flush()
|
||||||
|
return s.stream.samples()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count returns the total number of samples observed in the stream
|
||||||
|
// since initialization.
|
||||||
|
func (s *Stream) Count() int {
|
||||||
|
return len(s.b) + s.stream.count()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stream) flush() {
|
||||||
|
s.maybeSort()
|
||||||
|
s.stream.merge(s.b)
|
||||||
|
s.b = s.b[:0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stream) maybeSort() {
|
||||||
|
if !s.sorted {
|
||||||
|
s.sorted = true
|
||||||
|
sort.Sort(s.b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stream) flushed() bool {
|
||||||
|
return len(s.stream.l) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type stream struct {
|
||||||
|
n float64
|
||||||
|
l []Sample
|
||||||
|
ƒ invariant
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stream) reset() {
|
||||||
|
s.l = s.l[:0]
|
||||||
|
s.n = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stream) insert(v float64) {
|
||||||
|
s.merge(Samples{{v, 1, 0}})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stream) merge(samples Samples) {
|
||||||
|
// TODO(beorn7): This tries to merge not only individual samples, but
|
||||||
|
// whole summaries. The paper doesn't mention merging summaries at
|
||||||
|
// all. Unittests show that the merging is inaccurate. Find out how to
|
||||||
|
// do merges properly.
|
||||||
|
var r float64
|
||||||
|
i := 0
|
||||||
|
for _, sample := range samples {
|
||||||
|
for ; i < len(s.l); i++ {
|
||||||
|
c := s.l[i]
|
||||||
|
if c.Value > sample.Value {
|
||||||
|
// Insert at position i.
|
||||||
|
s.l = append(s.l, Sample{})
|
||||||
|
copy(s.l[i+1:], s.l[i:])
|
||||||
|
s.l[i] = Sample{
|
||||||
|
sample.Value,
|
||||||
|
sample.Width,
|
||||||
|
math.Max(sample.Delta, math.Floor(s.ƒ(s, r))-1),
|
||||||
|
// TODO(beorn7): How to calculate delta correctly?
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
goto inserted
|
||||||
|
}
|
||||||
|
r += c.Width
|
||||||
|
}
|
||||||
|
s.l = append(s.l, Sample{sample.Value, sample.Width, 0})
|
||||||
|
i++
|
||||||
|
inserted:
|
||||||
|
s.n += sample.Width
|
||||||
|
r += sample.Width
|
||||||
|
}
|
||||||
|
s.compress()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stream) count() int {
|
||||||
|
return int(s.n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stream) query(q float64) float64 {
|
||||||
|
t := math.Ceil(q * s.n)
|
||||||
|
t += math.Ceil(s.ƒ(s, t) / 2)
|
||||||
|
p := s.l[0]
|
||||||
|
var r float64
|
||||||
|
for _, c := range s.l[1:] {
|
||||||
|
r += p.Width
|
||||||
|
if r+c.Width+c.Delta > t {
|
||||||
|
return p.Value
|
||||||
|
}
|
||||||
|
p = c
|
||||||
|
}
|
||||||
|
return p.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stream) compress() {
|
||||||
|
if len(s.l) < 2 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
x := s.l[len(s.l)-1]
|
||||||
|
xi := len(s.l) - 1
|
||||||
|
r := s.n - 1 - x.Width
|
||||||
|
|
||||||
|
for i := len(s.l) - 2; i >= 0; i-- {
|
||||||
|
c := s.l[i]
|
||||||
|
if c.Width+x.Width+x.Delta <= s.ƒ(s, r) {
|
||||||
|
x.Width += c.Width
|
||||||
|
s.l[xi] = x
|
||||||
|
// Remove element at i.
|
||||||
|
copy(s.l[i:], s.l[i+1:])
|
||||||
|
s.l = s.l[:len(s.l)-1]
|
||||||
|
xi -= 1
|
||||||
|
} else {
|
||||||
|
x = c
|
||||||
|
xi = i
|
||||||
|
}
|
||||||
|
r -= c.Width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stream) samples() Samples {
|
||||||
|
samples := make(Samples, len(s.l))
|
||||||
|
copy(samples, s.l)
|
||||||
|
return samples
|
||||||
|
}
|
22
vendor/github.com/cespare/xxhash/v2/LICENSE.txt
generated
vendored
Normal file
22
vendor/github.com/cespare/xxhash/v2/LICENSE.txt
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
Copyright (c) 2016 Caleb Spare
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
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.
|
72
vendor/github.com/cespare/xxhash/v2/README.md
generated
vendored
Normal file
72
vendor/github.com/cespare/xxhash/v2/README.md
generated
vendored
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
# xxhash
|
||||||
|
|
||||||
|
[![Go Reference](https://pkg.go.dev/badge/github.com/cespare/xxhash/v2.svg)](https://pkg.go.dev/github.com/cespare/xxhash/v2)
|
||||||
|
[![Test](https://github.com/cespare/xxhash/actions/workflows/test.yml/badge.svg)](https://github.com/cespare/xxhash/actions/workflows/test.yml)
|
||||||
|
|
||||||
|
xxhash is a Go implementation of the 64-bit [xxHash] algorithm, XXH64. This is a
|
||||||
|
high-quality hashing algorithm that is much faster than anything in the Go
|
||||||
|
standard library.
|
||||||
|
|
||||||
|
This package provides a straightforward API:
|
||||||
|
|
||||||
|
```
|
||||||
|
func Sum64(b []byte) uint64
|
||||||
|
func Sum64String(s string) uint64
|
||||||
|
type Digest struct{ ... }
|
||||||
|
func New() *Digest
|
||||||
|
```
|
||||||
|
|
||||||
|
The `Digest` type implements hash.Hash64. Its key methods are:
|
||||||
|
|
||||||
|
```
|
||||||
|
func (*Digest) Write([]byte) (int, error)
|
||||||
|
func (*Digest) WriteString(string) (int, error)
|
||||||
|
func (*Digest) Sum64() uint64
|
||||||
|
```
|
||||||
|
|
||||||
|
The package is written with optimized pure Go and also contains even faster
|
||||||
|
assembly implementations for amd64 and arm64. If desired, the `purego` build tag
|
||||||
|
opts into using the Go code even on those architectures.
|
||||||
|
|
||||||
|
[xxHash]: http://cyan4973.github.io/xxHash/
|
||||||
|
|
||||||
|
## Compatibility
|
||||||
|
|
||||||
|
This package is in a module and the latest code is in version 2 of the module.
|
||||||
|
You need a version of Go with at least "minimal module compatibility" to use
|
||||||
|
github.com/cespare/xxhash/v2:
|
||||||
|
|
||||||
|
* 1.9.7+ for Go 1.9
|
||||||
|
* 1.10.3+ for Go 1.10
|
||||||
|
* Go 1.11 or later
|
||||||
|
|
||||||
|
I recommend using the latest release of Go.
|
||||||
|
|
||||||
|
## Benchmarks
|
||||||
|
|
||||||
|
Here are some quick benchmarks comparing the pure-Go and assembly
|
||||||
|
implementations of Sum64.
|
||||||
|
|
||||||
|
| input size | purego | asm |
|
||||||
|
| ---------- | --------- | --------- |
|
||||||
|
| 4 B | 1.3 GB/s | 1.2 GB/s |
|
||||||
|
| 16 B | 2.9 GB/s | 3.5 GB/s |
|
||||||
|
| 100 B | 6.9 GB/s | 8.1 GB/s |
|
||||||
|
| 4 KB | 11.7 GB/s | 16.7 GB/s |
|
||||||
|
| 10 MB | 12.0 GB/s | 17.3 GB/s |
|
||||||
|
|
||||||
|
These numbers were generated on Ubuntu 20.04 with an Intel Xeon Platinum 8252C
|
||||||
|
CPU using the following commands under Go 1.19.2:
|
||||||
|
|
||||||
|
```
|
||||||
|
benchstat <(go test -tags purego -benchtime 500ms -count 15 -bench 'Sum64$')
|
||||||
|
benchstat <(go test -benchtime 500ms -count 15 -bench 'Sum64$')
|
||||||
|
```
|
||||||
|
|
||||||
|
## Projects using this package
|
||||||
|
|
||||||
|
- [InfluxDB](https://github.com/influxdata/influxdb)
|
||||||
|
- [Prometheus](https://github.com/prometheus/prometheus)
|
||||||
|
- [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics)
|
||||||
|
- [FreeCache](https://github.com/coocood/freecache)
|
||||||
|
- [FastCache](https://github.com/VictoriaMetrics/fastcache)
|
10
vendor/github.com/cespare/xxhash/v2/testall.sh
generated
vendored
Normal file
10
vendor/github.com/cespare/xxhash/v2/testall.sh
generated
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -eu -o pipefail
|
||||||
|
|
||||||
|
# Small convenience script for running the tests with various combinations of
|
||||||
|
# arch/tags. This assumes we're running on amd64 and have qemu available.
|
||||||
|
|
||||||
|
go test ./...
|
||||||
|
go test -tags purego ./...
|
||||||
|
GOARCH=arm64 go test
|
||||||
|
GOARCH=arm64 go test -tags purego
|
228
vendor/github.com/cespare/xxhash/v2/xxhash.go
generated
vendored
Normal file
228
vendor/github.com/cespare/xxhash/v2/xxhash.go
generated
vendored
Normal file
|
@ -0,0 +1,228 @@
|
||||||
|
// Package xxhash implements the 64-bit variant of xxHash (XXH64) as described
|
||||||
|
// at http://cyan4973.github.io/xxHash/.
|
||||||
|
package xxhash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"math/bits"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
prime1 uint64 = 11400714785074694791
|
||||||
|
prime2 uint64 = 14029467366897019727
|
||||||
|
prime3 uint64 = 1609587929392839161
|
||||||
|
prime4 uint64 = 9650029242287828579
|
||||||
|
prime5 uint64 = 2870177450012600261
|
||||||
|
)
|
||||||
|
|
||||||
|
// Store the primes in an array as well.
|
||||||
|
//
|
||||||
|
// The consts are used when possible in Go code to avoid MOVs but we need a
|
||||||
|
// contiguous array of the assembly code.
|
||||||
|
var primes = [...]uint64{prime1, prime2, prime3, prime4, prime5}
|
||||||
|
|
||||||
|
// Digest implements hash.Hash64.
|
||||||
|
type Digest struct {
|
||||||
|
v1 uint64
|
||||||
|
v2 uint64
|
||||||
|
v3 uint64
|
||||||
|
v4 uint64
|
||||||
|
total uint64
|
||||||
|
mem [32]byte
|
||||||
|
n int // how much of mem is used
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new Digest that computes the 64-bit xxHash algorithm.
|
||||||
|
func New() *Digest {
|
||||||
|
var d Digest
|
||||||
|
d.Reset()
|
||||||
|
return &d
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset clears the Digest's state so that it can be reused.
|
||||||
|
func (d *Digest) Reset() {
|
||||||
|
d.v1 = primes[0] + prime2
|
||||||
|
d.v2 = prime2
|
||||||
|
d.v3 = 0
|
||||||
|
d.v4 = -primes[0]
|
||||||
|
d.total = 0
|
||||||
|
d.n = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size always returns 8 bytes.
|
||||||
|
func (d *Digest) Size() int { return 8 }
|
||||||
|
|
||||||
|
// BlockSize always returns 32 bytes.
|
||||||
|
func (d *Digest) BlockSize() int { return 32 }
|
||||||
|
|
||||||
|
// Write adds more data to d. It always returns len(b), nil.
|
||||||
|
func (d *Digest) Write(b []byte) (n int, err error) {
|
||||||
|
n = len(b)
|
||||||
|
d.total += uint64(n)
|
||||||
|
|
||||||
|
memleft := d.mem[d.n&(len(d.mem)-1):]
|
||||||
|
|
||||||
|
if d.n+n < 32 {
|
||||||
|
// This new data doesn't even fill the current block.
|
||||||
|
copy(memleft, b)
|
||||||
|
d.n += n
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.n > 0 {
|
||||||
|
// Finish off the partial block.
|
||||||
|
c := copy(memleft, b)
|
||||||
|
d.v1 = round(d.v1, u64(d.mem[0:8]))
|
||||||
|
d.v2 = round(d.v2, u64(d.mem[8:16]))
|
||||||
|
d.v3 = round(d.v3, u64(d.mem[16:24]))
|
||||||
|
d.v4 = round(d.v4, u64(d.mem[24:32]))
|
||||||
|
b = b[c:]
|
||||||
|
d.n = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b) >= 32 {
|
||||||
|
// One or more full blocks left.
|
||||||
|
nw := writeBlocks(d, b)
|
||||||
|
b = b[nw:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store any remaining partial block.
|
||||||
|
copy(d.mem[:], b)
|
||||||
|
d.n = len(b)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sum appends the current hash to b and returns the resulting slice.
|
||||||
|
func (d *Digest) Sum(b []byte) []byte {
|
||||||
|
s := d.Sum64()
|
||||||
|
return append(
|
||||||
|
b,
|
||||||
|
byte(s>>56),
|
||||||
|
byte(s>>48),
|
||||||
|
byte(s>>40),
|
||||||
|
byte(s>>32),
|
||||||
|
byte(s>>24),
|
||||||
|
byte(s>>16),
|
||||||
|
byte(s>>8),
|
||||||
|
byte(s),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sum64 returns the current hash.
|
||||||
|
func (d *Digest) Sum64() uint64 {
|
||||||
|
var h uint64
|
||||||
|
|
||||||
|
if d.total >= 32 {
|
||||||
|
v1, v2, v3, v4 := d.v1, d.v2, d.v3, d.v4
|
||||||
|
h = rol1(v1) + rol7(v2) + rol12(v3) + rol18(v4)
|
||||||
|
h = mergeRound(h, v1)
|
||||||
|
h = mergeRound(h, v2)
|
||||||
|
h = mergeRound(h, v3)
|
||||||
|
h = mergeRound(h, v4)
|
||||||
|
} else {
|
||||||
|
h = d.v3 + prime5
|
||||||
|
}
|
||||||
|
|
||||||
|
h += d.total
|
||||||
|
|
||||||
|
b := d.mem[:d.n&(len(d.mem)-1)]
|
||||||
|
for ; len(b) >= 8; b = b[8:] {
|
||||||
|
k1 := round(0, u64(b[:8]))
|
||||||
|
h ^= k1
|
||||||
|
h = rol27(h)*prime1 + prime4
|
||||||
|
}
|
||||||
|
if len(b) >= 4 {
|
||||||
|
h ^= uint64(u32(b[:4])) * prime1
|
||||||
|
h = rol23(h)*prime2 + prime3
|
||||||
|
b = b[4:]
|
||||||
|
}
|
||||||
|
for ; len(b) > 0; b = b[1:] {
|
||||||
|
h ^= uint64(b[0]) * prime5
|
||||||
|
h = rol11(h) * prime1
|
||||||
|
}
|
||||||
|
|
||||||
|
h ^= h >> 33
|
||||||
|
h *= prime2
|
||||||
|
h ^= h >> 29
|
||||||
|
h *= prime3
|
||||||
|
h ^= h >> 32
|
||||||
|
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
magic = "xxh\x06"
|
||||||
|
marshaledSize = len(magic) + 8*5 + 32
|
||||||
|
)
|
||||||
|
|
||||||
|
// MarshalBinary implements the encoding.BinaryMarshaler interface.
|
||||||
|
func (d *Digest) MarshalBinary() ([]byte, error) {
|
||||||
|
b := make([]byte, 0, marshaledSize)
|
||||||
|
b = append(b, magic...)
|
||||||
|
b = appendUint64(b, d.v1)
|
||||||
|
b = appendUint64(b, d.v2)
|
||||||
|
b = appendUint64(b, d.v3)
|
||||||
|
b = appendUint64(b, d.v4)
|
||||||
|
b = appendUint64(b, d.total)
|
||||||
|
b = append(b, d.mem[:d.n]...)
|
||||||
|
b = b[:len(b)+len(d.mem)-d.n]
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
|
||||||
|
func (d *Digest) UnmarshalBinary(b []byte) error {
|
||||||
|
if len(b) < len(magic) || string(b[:len(magic)]) != magic {
|
||||||
|
return errors.New("xxhash: invalid hash state identifier")
|
||||||
|
}
|
||||||
|
if len(b) != marshaledSize {
|
||||||
|
return errors.New("xxhash: invalid hash state size")
|
||||||
|
}
|
||||||
|
b = b[len(magic):]
|
||||||
|
b, d.v1 = consumeUint64(b)
|
||||||
|
b, d.v2 = consumeUint64(b)
|
||||||
|
b, d.v3 = consumeUint64(b)
|
||||||
|
b, d.v4 = consumeUint64(b)
|
||||||
|
b, d.total = consumeUint64(b)
|
||||||
|
copy(d.mem[:], b)
|
||||||
|
d.n = int(d.total % uint64(len(d.mem)))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendUint64(b []byte, x uint64) []byte {
|
||||||
|
var a [8]byte
|
||||||
|
binary.LittleEndian.PutUint64(a[:], x)
|
||||||
|
return append(b, a[:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func consumeUint64(b []byte) ([]byte, uint64) {
|
||||||
|
x := u64(b)
|
||||||
|
return b[8:], x
|
||||||
|
}
|
||||||
|
|
||||||
|
func u64(b []byte) uint64 { return binary.LittleEndian.Uint64(b) }
|
||||||
|
func u32(b []byte) uint32 { return binary.LittleEndian.Uint32(b) }
|
||||||
|
|
||||||
|
func round(acc, input uint64) uint64 {
|
||||||
|
acc += input * prime2
|
||||||
|
acc = rol31(acc)
|
||||||
|
acc *= prime1
|
||||||
|
return acc
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeRound(acc, val uint64) uint64 {
|
||||||
|
val = round(0, val)
|
||||||
|
acc ^= val
|
||||||
|
acc = acc*prime1 + prime4
|
||||||
|
return acc
|
||||||
|
}
|
||||||
|
|
||||||
|
func rol1(x uint64) uint64 { return bits.RotateLeft64(x, 1) }
|
||||||
|
func rol7(x uint64) uint64 { return bits.RotateLeft64(x, 7) }
|
||||||
|
func rol11(x uint64) uint64 { return bits.RotateLeft64(x, 11) }
|
||||||
|
func rol12(x uint64) uint64 { return bits.RotateLeft64(x, 12) }
|
||||||
|
func rol18(x uint64) uint64 { return bits.RotateLeft64(x, 18) }
|
||||||
|
func rol23(x uint64) uint64 { return bits.RotateLeft64(x, 23) }
|
||||||
|
func rol27(x uint64) uint64 { return bits.RotateLeft64(x, 27) }
|
||||||
|
func rol31(x uint64) uint64 { return bits.RotateLeft64(x, 31) }
|
209
vendor/github.com/cespare/xxhash/v2/xxhash_amd64.s
generated
vendored
Normal file
209
vendor/github.com/cespare/xxhash/v2/xxhash_amd64.s
generated
vendored
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
//go:build !appengine && gc && !purego
|
||||||
|
// +build !appengine
|
||||||
|
// +build gc
|
||||||
|
// +build !purego
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
// Registers:
|
||||||
|
#define h AX
|
||||||
|
#define d AX
|
||||||
|
#define p SI // pointer to advance through b
|
||||||
|
#define n DX
|
||||||
|
#define end BX // loop end
|
||||||
|
#define v1 R8
|
||||||
|
#define v2 R9
|
||||||
|
#define v3 R10
|
||||||
|
#define v4 R11
|
||||||
|
#define x R12
|
||||||
|
#define prime1 R13
|
||||||
|
#define prime2 R14
|
||||||
|
#define prime4 DI
|
||||||
|
|
||||||
|
#define round(acc, x) \
|
||||||
|
IMULQ prime2, x \
|
||||||
|
ADDQ x, acc \
|
||||||
|
ROLQ $31, acc \
|
||||||
|
IMULQ prime1, acc
|
||||||
|
|
||||||
|
// round0 performs the operation x = round(0, x).
|
||||||
|
#define round0(x) \
|
||||||
|
IMULQ prime2, x \
|
||||||
|
ROLQ $31, x \
|
||||||
|
IMULQ prime1, x
|
||||||
|
|
||||||
|
// mergeRound applies a merge round on the two registers acc and x.
|
||||||
|
// It assumes that prime1, prime2, and prime4 have been loaded.
|
||||||
|
#define mergeRound(acc, x) \
|
||||||
|
round0(x) \
|
||||||
|
XORQ x, acc \
|
||||||
|
IMULQ prime1, acc \
|
||||||
|
ADDQ prime4, acc
|
||||||
|
|
||||||
|
// blockLoop processes as many 32-byte blocks as possible,
|
||||||
|
// updating v1, v2, v3, and v4. It assumes that there is at least one block
|
||||||
|
// to process.
|
||||||
|
#define blockLoop() \
|
||||||
|
loop: \
|
||||||
|
MOVQ +0(p), x \
|
||||||
|
round(v1, x) \
|
||||||
|
MOVQ +8(p), x \
|
||||||
|
round(v2, x) \
|
||||||
|
MOVQ +16(p), x \
|
||||||
|
round(v3, x) \
|
||||||
|
MOVQ +24(p), x \
|
||||||
|
round(v4, x) \
|
||||||
|
ADDQ $32, p \
|
||||||
|
CMPQ p, end \
|
||||||
|
JLE loop
|
||||||
|
|
||||||
|
// func Sum64(b []byte) uint64
|
||||||
|
TEXT ·Sum64(SB), NOSPLIT|NOFRAME, $0-32
|
||||||
|
// Load fixed primes.
|
||||||
|
MOVQ ·primes+0(SB), prime1
|
||||||
|
MOVQ ·primes+8(SB), prime2
|
||||||
|
MOVQ ·primes+24(SB), prime4
|
||||||
|
|
||||||
|
// Load slice.
|
||||||
|
MOVQ b_base+0(FP), p
|
||||||
|
MOVQ b_len+8(FP), n
|
||||||
|
LEAQ (p)(n*1), end
|
||||||
|
|
||||||
|
// The first loop limit will be len(b)-32.
|
||||||
|
SUBQ $32, end
|
||||||
|
|
||||||
|
// Check whether we have at least one block.
|
||||||
|
CMPQ n, $32
|
||||||
|
JLT noBlocks
|
||||||
|
|
||||||
|
// Set up initial state (v1, v2, v3, v4).
|
||||||
|
MOVQ prime1, v1
|
||||||
|
ADDQ prime2, v1
|
||||||
|
MOVQ prime2, v2
|
||||||
|
XORQ v3, v3
|
||||||
|
XORQ v4, v4
|
||||||
|
SUBQ prime1, v4
|
||||||
|
|
||||||
|
blockLoop()
|
||||||
|
|
||||||
|
MOVQ v1, h
|
||||||
|
ROLQ $1, h
|
||||||
|
MOVQ v2, x
|
||||||
|
ROLQ $7, x
|
||||||
|
ADDQ x, h
|
||||||
|
MOVQ v3, x
|
||||||
|
ROLQ $12, x
|
||||||
|
ADDQ x, h
|
||||||
|
MOVQ v4, x
|
||||||
|
ROLQ $18, x
|
||||||
|
ADDQ x, h
|
||||||
|
|
||||||
|
mergeRound(h, v1)
|
||||||
|
mergeRound(h, v2)
|
||||||
|
mergeRound(h, v3)
|
||||||
|
mergeRound(h, v4)
|
||||||
|
|
||||||
|
JMP afterBlocks
|
||||||
|
|
||||||
|
noBlocks:
|
||||||
|
MOVQ ·primes+32(SB), h
|
||||||
|
|
||||||
|
afterBlocks:
|
||||||
|
ADDQ n, h
|
||||||
|
|
||||||
|
ADDQ $24, end
|
||||||
|
CMPQ p, end
|
||||||
|
JG try4
|
||||||
|
|
||||||
|
loop8:
|
||||||
|
MOVQ (p), x
|
||||||
|
ADDQ $8, p
|
||||||
|
round0(x)
|
||||||
|
XORQ x, h
|
||||||
|
ROLQ $27, h
|
||||||
|
IMULQ prime1, h
|
||||||
|
ADDQ prime4, h
|
||||||
|
|
||||||
|
CMPQ p, end
|
||||||
|
JLE loop8
|
||||||
|
|
||||||
|
try4:
|
||||||
|
ADDQ $4, end
|
||||||
|
CMPQ p, end
|
||||||
|
JG try1
|
||||||
|
|
||||||
|
MOVL (p), x
|
||||||
|
ADDQ $4, p
|
||||||
|
IMULQ prime1, x
|
||||||
|
XORQ x, h
|
||||||
|
|
||||||
|
ROLQ $23, h
|
||||||
|
IMULQ prime2, h
|
||||||
|
ADDQ ·primes+16(SB), h
|
||||||
|
|
||||||
|
try1:
|
||||||
|
ADDQ $4, end
|
||||||
|
CMPQ p, end
|
||||||
|
JGE finalize
|
||||||
|
|
||||||
|
loop1:
|
||||||
|
MOVBQZX (p), x
|
||||||
|
ADDQ $1, p
|
||||||
|
IMULQ ·primes+32(SB), x
|
||||||
|
XORQ x, h
|
||||||
|
ROLQ $11, h
|
||||||
|
IMULQ prime1, h
|
||||||
|
|
||||||
|
CMPQ p, end
|
||||||
|
JL loop1
|
||||||
|
|
||||||
|
finalize:
|
||||||
|
MOVQ h, x
|
||||||
|
SHRQ $33, x
|
||||||
|
XORQ x, h
|
||||||
|
IMULQ prime2, h
|
||||||
|
MOVQ h, x
|
||||||
|
SHRQ $29, x
|
||||||
|
XORQ x, h
|
||||||
|
IMULQ ·primes+16(SB), h
|
||||||
|
MOVQ h, x
|
||||||
|
SHRQ $32, x
|
||||||
|
XORQ x, h
|
||||||
|
|
||||||
|
MOVQ h, ret+24(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
// func writeBlocks(d *Digest, b []byte) int
|
||||||
|
TEXT ·writeBlocks(SB), NOSPLIT|NOFRAME, $0-40
|
||||||
|
// Load fixed primes needed for round.
|
||||||
|
MOVQ ·primes+0(SB), prime1
|
||||||
|
MOVQ ·primes+8(SB), prime2
|
||||||
|
|
||||||
|
// Load slice.
|
||||||
|
MOVQ b_base+8(FP), p
|
||||||
|
MOVQ b_len+16(FP), n
|
||||||
|
LEAQ (p)(n*1), end
|
||||||
|
SUBQ $32, end
|
||||||
|
|
||||||
|
// Load vN from d.
|
||||||
|
MOVQ s+0(FP), d
|
||||||
|
MOVQ 0(d), v1
|
||||||
|
MOVQ 8(d), v2
|
||||||
|
MOVQ 16(d), v3
|
||||||
|
MOVQ 24(d), v4
|
||||||
|
|
||||||
|
// We don't need to check the loop condition here; this function is
|
||||||
|
// always called with at least one block of data to process.
|
||||||
|
blockLoop()
|
||||||
|
|
||||||
|
// Copy vN back to d.
|
||||||
|
MOVQ v1, 0(d)
|
||||||
|
MOVQ v2, 8(d)
|
||||||
|
MOVQ v3, 16(d)
|
||||||
|
MOVQ v4, 24(d)
|
||||||
|
|
||||||
|
// The number of bytes written is p minus the old base pointer.
|
||||||
|
SUBQ b_base+8(FP), p
|
||||||
|
MOVQ p, ret+32(FP)
|
||||||
|
|
||||||
|
RET
|
183
vendor/github.com/cespare/xxhash/v2/xxhash_arm64.s
generated
vendored
Normal file
183
vendor/github.com/cespare/xxhash/v2/xxhash_arm64.s
generated
vendored
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
//go:build !appengine && gc && !purego
|
||||||
|
// +build !appengine
|
||||||
|
// +build gc
|
||||||
|
// +build !purego
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
// Registers:
|
||||||
|
#define digest R1
|
||||||
|
#define h R2 // return value
|
||||||
|
#define p R3 // input pointer
|
||||||
|
#define n R4 // input length
|
||||||
|
#define nblocks R5 // n / 32
|
||||||
|
#define prime1 R7
|
||||||
|
#define prime2 R8
|
||||||
|
#define prime3 R9
|
||||||
|
#define prime4 R10
|
||||||
|
#define prime5 R11
|
||||||
|
#define v1 R12
|
||||||
|
#define v2 R13
|
||||||
|
#define v3 R14
|
||||||
|
#define v4 R15
|
||||||
|
#define x1 R20
|
||||||
|
#define x2 R21
|
||||||
|
#define x3 R22
|
||||||
|
#define x4 R23
|
||||||
|
|
||||||
|
#define round(acc, x) \
|
||||||
|
MADD prime2, acc, x, acc \
|
||||||
|
ROR $64-31, acc \
|
||||||
|
MUL prime1, acc
|
||||||
|
|
||||||
|
// round0 performs the operation x = round(0, x).
|
||||||
|
#define round0(x) \
|
||||||
|
MUL prime2, x \
|
||||||
|
ROR $64-31, x \
|
||||||
|
MUL prime1, x
|
||||||
|
|
||||||
|
#define mergeRound(acc, x) \
|
||||||
|
round0(x) \
|
||||||
|
EOR x, acc \
|
||||||
|
MADD acc, prime4, prime1, acc
|
||||||
|
|
||||||
|
// blockLoop processes as many 32-byte blocks as possible,
|
||||||
|
// updating v1, v2, v3, and v4. It assumes that n >= 32.
|
||||||
|
#define blockLoop() \
|
||||||
|
LSR $5, n, nblocks \
|
||||||
|
PCALIGN $16 \
|
||||||
|
loop: \
|
||||||
|
LDP.P 16(p), (x1, x2) \
|
||||||
|
LDP.P 16(p), (x3, x4) \
|
||||||
|
round(v1, x1) \
|
||||||
|
round(v2, x2) \
|
||||||
|
round(v3, x3) \
|
||||||
|
round(v4, x4) \
|
||||||
|
SUB $1, nblocks \
|
||||||
|
CBNZ nblocks, loop
|
||||||
|
|
||||||
|
// func Sum64(b []byte) uint64
|
||||||
|
TEXT ·Sum64(SB), NOSPLIT|NOFRAME, $0-32
|
||||||
|
LDP b_base+0(FP), (p, n)
|
||||||
|
|
||||||
|
LDP ·primes+0(SB), (prime1, prime2)
|
||||||
|
LDP ·primes+16(SB), (prime3, prime4)
|
||||||
|
MOVD ·primes+32(SB), prime5
|
||||||
|
|
||||||
|
CMP $32, n
|
||||||
|
CSEL LT, prime5, ZR, h // if n < 32 { h = prime5 } else { h = 0 }
|
||||||
|
BLT afterLoop
|
||||||
|
|
||||||
|
ADD prime1, prime2, v1
|
||||||
|
MOVD prime2, v2
|
||||||
|
MOVD $0, v3
|
||||||
|
NEG prime1, v4
|
||||||
|
|
||||||
|
blockLoop()
|
||||||
|
|
||||||
|
ROR $64-1, v1, x1
|
||||||
|
ROR $64-7, v2, x2
|
||||||
|
ADD x1, x2
|
||||||
|
ROR $64-12, v3, x3
|
||||||
|
ROR $64-18, v4, x4
|
||||||
|
ADD x3, x4
|
||||||
|
ADD x2, x4, h
|
||||||
|
|
||||||
|
mergeRound(h, v1)
|
||||||
|
mergeRound(h, v2)
|
||||||
|
mergeRound(h, v3)
|
||||||
|
mergeRound(h, v4)
|
||||||
|
|
||||||
|
afterLoop:
|
||||||
|
ADD n, h
|
||||||
|
|
||||||
|
TBZ $4, n, try8
|
||||||
|
LDP.P 16(p), (x1, x2)
|
||||||
|
|
||||||
|
round0(x1)
|
||||||
|
|
||||||
|
// NOTE: here and below, sequencing the EOR after the ROR (using a
|
||||||
|
// rotated register) is worth a small but measurable speedup for small
|
||||||
|
// inputs.
|
||||||
|
ROR $64-27, h
|
||||||
|
EOR x1 @> 64-27, h, h
|
||||||
|
MADD h, prime4, prime1, h
|
||||||
|
|
||||||
|
round0(x2)
|
||||||
|
ROR $64-27, h
|
||||||
|
EOR x2 @> 64-27, h, h
|
||||||
|
MADD h, prime4, prime1, h
|
||||||
|
|
||||||
|
try8:
|
||||||
|
TBZ $3, n, try4
|
||||||
|
MOVD.P 8(p), x1
|
||||||
|
|
||||||
|
round0(x1)
|
||||||
|
ROR $64-27, h
|
||||||
|
EOR x1 @> 64-27, h, h
|
||||||
|
MADD h, prime4, prime1, h
|
||||||
|
|
||||||
|
try4:
|
||||||
|
TBZ $2, n, try2
|
||||||
|
MOVWU.P 4(p), x2
|
||||||
|
|
||||||
|
MUL prime1, x2
|
||||||
|
ROR $64-23, h
|
||||||
|
EOR x2 @> 64-23, h, h
|
||||||
|
MADD h, prime3, prime2, h
|
||||||
|
|
||||||
|
try2:
|
||||||
|
TBZ $1, n, try1
|
||||||
|
MOVHU.P 2(p), x3
|
||||||
|
AND $255, x3, x1
|
||||||
|
LSR $8, x3, x2
|
||||||
|
|
||||||
|
MUL prime5, x1
|
||||||
|
ROR $64-11, h
|
||||||
|
EOR x1 @> 64-11, h, h
|
||||||
|
MUL prime1, h
|
||||||
|
|
||||||
|
MUL prime5, x2
|
||||||
|
ROR $64-11, h
|
||||||
|
EOR x2 @> 64-11, h, h
|
||||||
|
MUL prime1, h
|
||||||
|
|
||||||
|
try1:
|
||||||
|
TBZ $0, n, finalize
|
||||||
|
MOVBU (p), x4
|
||||||
|
|
||||||
|
MUL prime5, x4
|
||||||
|
ROR $64-11, h
|
||||||
|
EOR x4 @> 64-11, h, h
|
||||||
|
MUL prime1, h
|
||||||
|
|
||||||
|
finalize:
|
||||||
|
EOR h >> 33, h
|
||||||
|
MUL prime2, h
|
||||||
|
EOR h >> 29, h
|
||||||
|
MUL prime3, h
|
||||||
|
EOR h >> 32, h
|
||||||
|
|
||||||
|
MOVD h, ret+24(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
// func writeBlocks(d *Digest, b []byte) int
|
||||||
|
TEXT ·writeBlocks(SB), NOSPLIT|NOFRAME, $0-40
|
||||||
|
LDP ·primes+0(SB), (prime1, prime2)
|
||||||
|
|
||||||
|
// Load state. Assume v[1-4] are stored contiguously.
|
||||||
|
MOVD d+0(FP), digest
|
||||||
|
LDP 0(digest), (v1, v2)
|
||||||
|
LDP 16(digest), (v3, v4)
|
||||||
|
|
||||||
|
LDP b_base+8(FP), (p, n)
|
||||||
|
|
||||||
|
blockLoop()
|
||||||
|
|
||||||
|
// Store updated state.
|
||||||
|
STP (v1, v2), 0(digest)
|
||||||
|
STP (v3, v4), 16(digest)
|
||||||
|
|
||||||
|
BIC $31, n
|
||||||
|
MOVD n, ret+32(FP)
|
||||||
|
RET
|
15
vendor/github.com/cespare/xxhash/v2/xxhash_asm.go
generated
vendored
Normal file
15
vendor/github.com/cespare/xxhash/v2/xxhash_asm.go
generated
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
//go:build (amd64 || arm64) && !appengine && gc && !purego
|
||||||
|
// +build amd64 arm64
|
||||||
|
// +build !appengine
|
||||||
|
// +build gc
|
||||||
|
// +build !purego
|
||||||
|
|
||||||
|
package xxhash
|
||||||
|
|
||||||
|
// Sum64 computes the 64-bit xxHash digest of b.
|
||||||
|
//
|
||||||
|
//go:noescape
|
||||||
|
func Sum64(b []byte) uint64
|
||||||
|
|
||||||
|
//go:noescape
|
||||||
|
func writeBlocks(d *Digest, b []byte) int
|
76
vendor/github.com/cespare/xxhash/v2/xxhash_other.go
generated
vendored
Normal file
76
vendor/github.com/cespare/xxhash/v2/xxhash_other.go
generated
vendored
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
//go:build (!amd64 && !arm64) || appengine || !gc || purego
|
||||||
|
// +build !amd64,!arm64 appengine !gc purego
|
||||||
|
|
||||||
|
package xxhash
|
||||||
|
|
||||||
|
// Sum64 computes the 64-bit xxHash digest of b.
|
||||||
|
func Sum64(b []byte) uint64 {
|
||||||
|
// A simpler version would be
|
||||||
|
// d := New()
|
||||||
|
// d.Write(b)
|
||||||
|
// return d.Sum64()
|
||||||
|
// but this is faster, particularly for small inputs.
|
||||||
|
|
||||||
|
n := len(b)
|
||||||
|
var h uint64
|
||||||
|
|
||||||
|
if n >= 32 {
|
||||||
|
v1 := primes[0] + prime2
|
||||||
|
v2 := prime2
|
||||||
|
v3 := uint64(0)
|
||||||
|
v4 := -primes[0]
|
||||||
|
for len(b) >= 32 {
|
||||||
|
v1 = round(v1, u64(b[0:8:len(b)]))
|
||||||
|
v2 = round(v2, u64(b[8:16:len(b)]))
|
||||||
|
v3 = round(v3, u64(b[16:24:len(b)]))
|
||||||
|
v4 = round(v4, u64(b[24:32:len(b)]))
|
||||||
|
b = b[32:len(b):len(b)]
|
||||||
|
}
|
||||||
|
h = rol1(v1) + rol7(v2) + rol12(v3) + rol18(v4)
|
||||||
|
h = mergeRound(h, v1)
|
||||||
|
h = mergeRound(h, v2)
|
||||||
|
h = mergeRound(h, v3)
|
||||||
|
h = mergeRound(h, v4)
|
||||||
|
} else {
|
||||||
|
h = prime5
|
||||||
|
}
|
||||||
|
|
||||||
|
h += uint64(n)
|
||||||
|
|
||||||
|
for ; len(b) >= 8; b = b[8:] {
|
||||||
|
k1 := round(0, u64(b[:8]))
|
||||||
|
h ^= k1
|
||||||
|
h = rol27(h)*prime1 + prime4
|
||||||
|
}
|
||||||
|
if len(b) >= 4 {
|
||||||
|
h ^= uint64(u32(b[:4])) * prime1
|
||||||
|
h = rol23(h)*prime2 + prime3
|
||||||
|
b = b[4:]
|
||||||
|
}
|
||||||
|
for ; len(b) > 0; b = b[1:] {
|
||||||
|
h ^= uint64(b[0]) * prime5
|
||||||
|
h = rol11(h) * prime1
|
||||||
|
}
|
||||||
|
|
||||||
|
h ^= h >> 33
|
||||||
|
h *= prime2
|
||||||
|
h ^= h >> 29
|
||||||
|
h *= prime3
|
||||||
|
h ^= h >> 32
|
||||||
|
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeBlocks(d *Digest, b []byte) int {
|
||||||
|
v1, v2, v3, v4 := d.v1, d.v2, d.v3, d.v4
|
||||||
|
n := len(b)
|
||||||
|
for len(b) >= 32 {
|
||||||
|
v1 = round(v1, u64(b[0:8:len(b)]))
|
||||||
|
v2 = round(v2, u64(b[8:16:len(b)]))
|
||||||
|
v3 = round(v3, u64(b[16:24:len(b)]))
|
||||||
|
v4 = round(v4, u64(b[24:32:len(b)]))
|
||||||
|
b = b[32:len(b):len(b)]
|
||||||
|
}
|
||||||
|
d.v1, d.v2, d.v3, d.v4 = v1, v2, v3, v4
|
||||||
|
return n - len(b)
|
||||||
|
}
|
16
vendor/github.com/cespare/xxhash/v2/xxhash_safe.go
generated
vendored
Normal file
16
vendor/github.com/cespare/xxhash/v2/xxhash_safe.go
generated
vendored
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
//go:build appengine
|
||||||
|
// +build appengine
|
||||||
|
|
||||||
|
// This file contains the safe implementations of otherwise unsafe-using code.
|
||||||
|
|
||||||
|
package xxhash
|
||||||
|
|
||||||
|
// Sum64String computes the 64-bit xxHash digest of s.
|
||||||
|
func Sum64String(s string) uint64 {
|
||||||
|
return Sum64([]byte(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteString adds more data to d. It always returns len(s), nil.
|
||||||
|
func (d *Digest) WriteString(s string) (n int, err error) {
|
||||||
|
return d.Write([]byte(s))
|
||||||
|
}
|
58
vendor/github.com/cespare/xxhash/v2/xxhash_unsafe.go
generated
vendored
Normal file
58
vendor/github.com/cespare/xxhash/v2/xxhash_unsafe.go
generated
vendored
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
//go:build !appengine
|
||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
// This file encapsulates usage of unsafe.
|
||||||
|
// xxhash_safe.go contains the safe implementations.
|
||||||
|
|
||||||
|
package xxhash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// In the future it's possible that compiler optimizations will make these
|
||||||
|
// XxxString functions unnecessary by realizing that calls such as
|
||||||
|
// Sum64([]byte(s)) don't need to copy s. See https://go.dev/issue/2205.
|
||||||
|
// If that happens, even if we keep these functions they can be replaced with
|
||||||
|
// the trivial safe code.
|
||||||
|
|
||||||
|
// NOTE: The usual way of doing an unsafe string-to-[]byte conversion is:
|
||||||
|
//
|
||||||
|
// var b []byte
|
||||||
|
// bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
|
||||||
|
// bh.Data = (*reflect.StringHeader)(unsafe.Pointer(&s)).Data
|
||||||
|
// bh.Len = len(s)
|
||||||
|
// bh.Cap = len(s)
|
||||||
|
//
|
||||||
|
// Unfortunately, as of Go 1.15.3 the inliner's cost model assigns a high enough
|
||||||
|
// weight to this sequence of expressions that any function that uses it will
|
||||||
|
// not be inlined. Instead, the functions below use a different unsafe
|
||||||
|
// conversion designed to minimize the inliner weight and allow both to be
|
||||||
|
// inlined. There is also a test (TestInlining) which verifies that these are
|
||||||
|
// inlined.
|
||||||
|
//
|
||||||
|
// See https://github.com/golang/go/issues/42739 for discussion.
|
||||||
|
|
||||||
|
// Sum64String computes the 64-bit xxHash digest of s.
|
||||||
|
// It may be faster than Sum64([]byte(s)) by avoiding a copy.
|
||||||
|
func Sum64String(s string) uint64 {
|
||||||
|
b := *(*[]byte)(unsafe.Pointer(&sliceHeader{s, len(s)}))
|
||||||
|
return Sum64(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteString adds more data to d. It always returns len(s), nil.
|
||||||
|
// It may be faster than Write([]byte(s)) by avoiding a copy.
|
||||||
|
func (d *Digest) WriteString(s string) (n int, err error) {
|
||||||
|
d.Write(*(*[]byte)(unsafe.Pointer(&sliceHeader{s, len(s)})))
|
||||||
|
// d.Write always returns len(s), nil.
|
||||||
|
// Ignoring the return output and returning these fixed values buys a
|
||||||
|
// savings of 6 in the inliner's cost model.
|
||||||
|
return len(s), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sliceHeader is similar to reflect.SliceHeader, but it assumes that the layout
|
||||||
|
// of the first two words is the same as the layout of a string.
|
||||||
|
type sliceHeader struct {
|
||||||
|
s string
|
||||||
|
cap int
|
||||||
|
}
|
12
vendor/github.com/fsnotify/fsnotify/.editorconfig
generated
vendored
Normal file
12
vendor/github.com/fsnotify/fsnotify/.editorconfig
generated
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*.go]
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = 4
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.{yml,yaml}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
1
vendor/github.com/fsnotify/fsnotify/.gitattributes
generated
vendored
Normal file
1
vendor/github.com/fsnotify/fsnotify/.gitattributes
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
go.sum linguist-generated
|
6
vendor/github.com/fsnotify/fsnotify/.gitignore
generated
vendored
Normal file
6
vendor/github.com/fsnotify/fsnotify/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
# go test -c output
|
||||||
|
*.test
|
||||||
|
*.test.exe
|
||||||
|
|
||||||
|
# Output of go build ./cmd/fsnotify
|
||||||
|
/fsnotify
|
2
vendor/github.com/fsnotify/fsnotify/.mailmap
generated
vendored
Normal file
2
vendor/github.com/fsnotify/fsnotify/.mailmap
generated
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Chris Howey <howeyc@gmail.com> <chris@howey.me>
|
||||||
|
Nathan Youngman <git@nathany.com> <4566+nathany@users.noreply.github.com>
|
470
vendor/github.com/fsnotify/fsnotify/CHANGELOG.md
generated
vendored
Normal file
470
vendor/github.com/fsnotify/fsnotify/CHANGELOG.md
generated
vendored
Normal file
|
@ -0,0 +1,470 @@
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
Nothing yet.
|
||||||
|
|
||||||
|
## [1.6.0] - 2022-10-13
|
||||||
|
|
||||||
|
This version of fsnotify needs Go 1.16 (this was already the case since 1.5.1,
|
||||||
|
but not documented). It also increases the minimum Linux version to 2.6.32.
|
||||||
|
|
||||||
|
### Additions
|
||||||
|
|
||||||
|
- all: add `Event.Has()` and `Op.Has()` ([#477])
|
||||||
|
|
||||||
|
This makes checking events a lot easier; for example:
|
||||||
|
|
||||||
|
if event.Op&Write == Write && !(event.Op&Remove == Remove) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Becomes:
|
||||||
|
|
||||||
|
if event.Has(Write) && !event.Has(Remove) {
|
||||||
|
}
|
||||||
|
|
||||||
|
- all: add cmd/fsnotify ([#463])
|
||||||
|
|
||||||
|
A command-line utility for testing and some examples.
|
||||||
|
|
||||||
|
### Changes and fixes
|
||||||
|
|
||||||
|
- inotify: don't ignore events for files that don't exist ([#260], [#470])
|
||||||
|
|
||||||
|
Previously the inotify watcher would call `os.Lstat()` to check if a file
|
||||||
|
still exists before emitting events.
|
||||||
|
|
||||||
|
This was inconsistent with other platforms and resulted in inconsistent event
|
||||||
|
reporting (e.g. when a file is quickly removed and re-created), and generally
|
||||||
|
a source of confusion. It was added in 2013 to fix a memory leak that no
|
||||||
|
longer exists.
|
||||||
|
|
||||||
|
- all: return `ErrNonExistentWatch` when `Remove()` is called on a path that's
|
||||||
|
not watched ([#460])
|
||||||
|
|
||||||
|
- inotify: replace epoll() with non-blocking inotify ([#434])
|
||||||
|
|
||||||
|
Non-blocking inotify was not generally available at the time this library was
|
||||||
|
written in 2014, but now it is. As a result, the minimum Linux version is
|
||||||
|
bumped from 2.6.27 to 2.6.32. This hugely simplifies the code and is faster.
|
||||||
|
|
||||||
|
- kqueue: don't check for events every 100ms ([#480])
|
||||||
|
|
||||||
|
The watcher would wake up every 100ms, even when there was nothing to do. Now
|
||||||
|
it waits until there is something to do.
|
||||||
|
|
||||||
|
- macos: retry opening files on EINTR ([#475])
|
||||||
|
|
||||||
|
- kqueue: skip unreadable files ([#479])
|
||||||
|
|
||||||
|
kqueue requires a file descriptor for every file in a directory; this would
|
||||||
|
fail if a file was unreadable by the current user. Now these files are simply
|
||||||
|
skipped.
|
||||||
|
|
||||||
|
- windows: fix renaming a watched directory if the parent is also watched ([#370])
|
||||||
|
|
||||||
|
- windows: increase buffer size from 4K to 64K ([#485])
|
||||||
|
|
||||||
|
- windows: close file handle on Remove() ([#288])
|
||||||
|
|
||||||
|
- kqueue: put pathname in the error if watching a file fails ([#471])
|
||||||
|
|
||||||
|
- inotify, windows: calling Close() more than once could race ([#465])
|
||||||
|
|
||||||
|
- kqueue: improve Close() performance ([#233])
|
||||||
|
|
||||||
|
- all: various documentation additions and clarifications.
|
||||||
|
|
||||||
|
[#233]: https://github.com/fsnotify/fsnotify/pull/233
|
||||||
|
[#260]: https://github.com/fsnotify/fsnotify/pull/260
|
||||||
|
[#288]: https://github.com/fsnotify/fsnotify/pull/288
|
||||||
|
[#370]: https://github.com/fsnotify/fsnotify/pull/370
|
||||||
|
[#434]: https://github.com/fsnotify/fsnotify/pull/434
|
||||||
|
[#460]: https://github.com/fsnotify/fsnotify/pull/460
|
||||||
|
[#463]: https://github.com/fsnotify/fsnotify/pull/463
|
||||||
|
[#465]: https://github.com/fsnotify/fsnotify/pull/465
|
||||||
|
[#470]: https://github.com/fsnotify/fsnotify/pull/470
|
||||||
|
[#471]: https://github.com/fsnotify/fsnotify/pull/471
|
||||||
|
[#475]: https://github.com/fsnotify/fsnotify/pull/475
|
||||||
|
[#477]: https://github.com/fsnotify/fsnotify/pull/477
|
||||||
|
[#479]: https://github.com/fsnotify/fsnotify/pull/479
|
||||||
|
[#480]: https://github.com/fsnotify/fsnotify/pull/480
|
||||||
|
[#485]: https://github.com/fsnotify/fsnotify/pull/485
|
||||||
|
|
||||||
|
## [1.5.4] - 2022-04-25
|
||||||
|
|
||||||
|
* Windows: add missing defer to `Watcher.WatchList` [#447](https://github.com/fsnotify/fsnotify/pull/447)
|
||||||
|
* go.mod: use latest x/sys [#444](https://github.com/fsnotify/fsnotify/pull/444)
|
||||||
|
* Fix compilation for OpenBSD [#443](https://github.com/fsnotify/fsnotify/pull/443)
|
||||||
|
|
||||||
|
## [1.5.3] - 2022-04-22
|
||||||
|
|
||||||
|
* This version is retracted. An incorrect branch is published accidentally [#445](https://github.com/fsnotify/fsnotify/issues/445)
|
||||||
|
|
||||||
|
## [1.5.2] - 2022-04-21
|
||||||
|
|
||||||
|
* Add a feature to return the directories and files that are being monitored [#374](https://github.com/fsnotify/fsnotify/pull/374)
|
||||||
|
* Fix potential crash on windows if `raw.FileNameLength` exceeds `syscall.MAX_PATH` [#361](https://github.com/fsnotify/fsnotify/pull/361)
|
||||||
|
* Allow build on unsupported GOOS [#424](https://github.com/fsnotify/fsnotify/pull/424)
|
||||||
|
* Don't set `poller.fd` twice in `newFdPoller` [#406](https://github.com/fsnotify/fsnotify/pull/406)
|
||||||
|
* fix go vet warnings: call to `(*T).Fatalf` from a non-test goroutine [#416](https://github.com/fsnotify/fsnotify/pull/416)
|
||||||
|
|
||||||
|
## [1.5.1] - 2021-08-24
|
||||||
|
|
||||||
|
* Revert Add AddRaw to not follow symlinks [#394](https://github.com/fsnotify/fsnotify/pull/394)
|
||||||
|
|
||||||
|
## [1.5.0] - 2021-08-20
|
||||||
|
|
||||||
|
* Go: Increase minimum required version to Go 1.12 [#381](https://github.com/fsnotify/fsnotify/pull/381)
|
||||||
|
* Feature: Add AddRaw method which does not follow symlinks when adding a watch [#289](https://github.com/fsnotify/fsnotify/pull/298)
|
||||||
|
* Windows: Follow symlinks by default like on all other systems [#289](https://github.com/fsnotify/fsnotify/pull/289)
|
||||||
|
* CI: Use GitHub Actions for CI and cover go 1.12-1.17
|
||||||
|
[#378](https://github.com/fsnotify/fsnotify/pull/378)
|
||||||
|
[#381](https://github.com/fsnotify/fsnotify/pull/381)
|
||||||
|
[#385](https://github.com/fsnotify/fsnotify/pull/385)
|
||||||
|
* Go 1.14+: Fix unsafe pointer conversion [#325](https://github.com/fsnotify/fsnotify/pull/325)
|
||||||
|
|
||||||
|
## [1.4.9] - 2020-03-11
|
||||||
|
|
||||||
|
* Move example usage to the readme #329. This may resolve #328.
|
||||||
|
|
||||||
|
## [1.4.8] - 2020-03-10
|
||||||
|
|
||||||
|
* CI: test more go versions (@nathany 1d13583d846ea9d66dcabbfefbfb9d8e6fb05216)
|
||||||
|
* Tests: Queued inotify events could have been read by the test before max_queued_events was hit (@matthias-stone #265)
|
||||||
|
* Tests: t.Fatalf -> t.Errorf in go routines (@gdey #266)
|
||||||
|
* CI: Less verbosity (@nathany #267)
|
||||||
|
* Tests: Darwin: Exchangedata is deprecated on 10.13 (@nathany #267)
|
||||||
|
* Tests: Check if channels are closed in the example (@alexeykazakov #244)
|
||||||
|
* CI: Only run golint on latest version of go and fix issues (@cpuguy83 #284)
|
||||||
|
* CI: Add windows to travis matrix (@cpuguy83 #284)
|
||||||
|
* Docs: Remover appveyor badge (@nathany 11844c0959f6fff69ba325d097fce35bd85a8e93)
|
||||||
|
* Linux: create epoll and pipe fds with close-on-exec (@JohannesEbke #219)
|
||||||
|
* Linux: open files with close-on-exec (@linxiulei #273)
|
||||||
|
* Docs: Plan to support fanotify (@nathany ab058b44498e8b7566a799372a39d150d9ea0119 )
|
||||||
|
* Project: Add go.mod (@nathany #309)
|
||||||
|
* Project: Revise editor config (@nathany #309)
|
||||||
|
* Project: Update copyright for 2019 (@nathany #309)
|
||||||
|
* CI: Drop go1.8 from CI matrix (@nathany #309)
|
||||||
|
* Docs: Updating the FAQ section for supportability with NFS & FUSE filesystems (@Pratik32 4bf2d1fec78374803a39307bfb8d340688f4f28e )
|
||||||
|
|
||||||
|
## [1.4.7] - 2018-01-09
|
||||||
|
|
||||||
|
* BSD/macOS: Fix possible deadlock on closing the watcher on kqueue (thanks @nhooyr and @glycerine)
|
||||||
|
* Tests: Fix missing verb on format string (thanks @rchiossi)
|
||||||
|
* Linux: Fix deadlock in Remove (thanks @aarondl)
|
||||||
|
* Linux: Watch.Add improvements (avoid race, fix consistency, reduce garbage) (thanks @twpayne)
|
||||||
|
* Docs: Moved FAQ into the README (thanks @vahe)
|
||||||
|
* Linux: Properly handle inotify's IN_Q_OVERFLOW event (thanks @zeldovich)
|
||||||
|
* Docs: replace references to OS X with macOS
|
||||||
|
|
||||||
|
## [1.4.2] - 2016-10-10
|
||||||
|
|
||||||
|
* Linux: use InotifyInit1 with IN_CLOEXEC to stop leaking a file descriptor to a child process when using fork/exec [#178](https://github.com/fsnotify/fsnotify/pull/178) (thanks @pattyshack)
|
||||||
|
|
||||||
|
## [1.4.1] - 2016-10-04
|
||||||
|
|
||||||
|
* Fix flaky inotify stress test on Linux [#177](https://github.com/fsnotify/fsnotify/pull/177) (thanks @pattyshack)
|
||||||
|
|
||||||
|
## [1.4.0] - 2016-10-01
|
||||||
|
|
||||||
|
* add a String() method to Event.Op [#165](https://github.com/fsnotify/fsnotify/pull/165) (thanks @oozie)
|
||||||
|
|
||||||
|
## [1.3.1] - 2016-06-28
|
||||||
|
|
||||||
|
* Windows: fix for double backslash when watching the root of a drive [#151](https://github.com/fsnotify/fsnotify/issues/151) (thanks @brunoqc)
|
||||||
|
|
||||||
|
## [1.3.0] - 2016-04-19
|
||||||
|
|
||||||
|
* Support linux/arm64 by [patching](https://go-review.googlesource.com/#/c/21971/) x/sys/unix and switching to to it from syscall (thanks @suihkulokki) [#135](https://github.com/fsnotify/fsnotify/pull/135)
|
||||||
|
|
||||||
|
## [1.2.10] - 2016-03-02
|
||||||
|
|
||||||
|
* Fix golint errors in windows.go [#121](https://github.com/fsnotify/fsnotify/pull/121) (thanks @tiffanyfj)
|
||||||
|
|
||||||
|
## [1.2.9] - 2016-01-13
|
||||||
|
|
||||||
|
kqueue: Fix logic for CREATE after REMOVE [#111](https://github.com/fsnotify/fsnotify/pull/111) (thanks @bep)
|
||||||
|
|
||||||
|
## [1.2.8] - 2015-12-17
|
||||||
|
|
||||||
|
* kqueue: fix race condition in Close [#105](https://github.com/fsnotify/fsnotify/pull/105) (thanks @djui for reporting the issue and @ppknap for writing a failing test)
|
||||||
|
* inotify: fix race in test
|
||||||
|
* enable race detection for continuous integration (Linux, Mac, Windows)
|
||||||
|
|
||||||
|
## [1.2.5] - 2015-10-17
|
||||||
|
|
||||||
|
* inotify: use epoll_create1 for arm64 support (requires Linux 2.6.27 or later) [#100](https://github.com/fsnotify/fsnotify/pull/100) (thanks @suihkulokki)
|
||||||
|
* inotify: fix path leaks [#73](https://github.com/fsnotify/fsnotify/pull/73) (thanks @chamaken)
|
||||||
|
* kqueue: watch for rename events on subdirectories [#83](https://github.com/fsnotify/fsnotify/pull/83) (thanks @guotie)
|
||||||
|
* kqueue: avoid infinite loops from symlinks cycles [#101](https://github.com/fsnotify/fsnotify/pull/101) (thanks @illicitonion)
|
||||||
|
|
||||||
|
## [1.2.1] - 2015-10-14
|
||||||
|
|
||||||
|
* kqueue: don't watch named pipes [#98](https://github.com/fsnotify/fsnotify/pull/98) (thanks @evanphx)
|
||||||
|
|
||||||
|
## [1.2.0] - 2015-02-08
|
||||||
|
|
||||||
|
* inotify: use epoll to wake up readEvents [#66](https://github.com/fsnotify/fsnotify/pull/66) (thanks @PieterD)
|
||||||
|
* inotify: closing watcher should now always shut down goroutine [#63](https://github.com/fsnotify/fsnotify/pull/63) (thanks @PieterD)
|
||||||
|
* kqueue: close kqueue after removing watches, fixes [#59](https://github.com/fsnotify/fsnotify/issues/59)
|
||||||
|
|
||||||
|
## [1.1.1] - 2015-02-05
|
||||||
|
|
||||||
|
* inotify: Retry read on EINTR [#61](https://github.com/fsnotify/fsnotify/issues/61) (thanks @PieterD)
|
||||||
|
|
||||||
|
## [1.1.0] - 2014-12-12
|
||||||
|
|
||||||
|
* kqueue: rework internals [#43](https://github.com/fsnotify/fsnotify/pull/43)
|
||||||
|
* add low-level functions
|
||||||
|
* only need to store flags on directories
|
||||||
|
* less mutexes [#13](https://github.com/fsnotify/fsnotify/issues/13)
|
||||||
|
* done can be an unbuffered channel
|
||||||
|
* remove calls to os.NewSyscallError
|
||||||
|
* More efficient string concatenation for Event.String() [#52](https://github.com/fsnotify/fsnotify/pull/52) (thanks @mdlayher)
|
||||||
|
* kqueue: fix regression in rework causing subdirectories to be watched [#48](https://github.com/fsnotify/fsnotify/issues/48)
|
||||||
|
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
|
||||||
|
|
||||||
|
## [1.0.4] - 2014-09-07
|
||||||
|
|
||||||
|
* kqueue: add dragonfly to the build tags.
|
||||||
|
* Rename source code files, rearrange code so exported APIs are at the top.
|
||||||
|
* Add done channel to example code. [#37](https://github.com/fsnotify/fsnotify/pull/37) (thanks @chenyukang)
|
||||||
|
|
||||||
|
## [1.0.3] - 2014-08-19
|
||||||
|
|
||||||
|
* [Fix] Windows MOVED_TO now translates to Create like on BSD and Linux. [#36](https://github.com/fsnotify/fsnotify/issues/36)
|
||||||
|
|
||||||
|
## [1.0.2] - 2014-08-17
|
||||||
|
|
||||||
|
* [Fix] Missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
|
||||||
|
* [Fix] Make ./path and path equivalent. (thanks @zhsso)
|
||||||
|
|
||||||
|
## [1.0.0] - 2014-08-15
|
||||||
|
|
||||||
|
* [API] Remove AddWatch on Windows, use Add.
|
||||||
|
* Improve documentation for exported identifiers. [#30](https://github.com/fsnotify/fsnotify/issues/30)
|
||||||
|
* Minor updates based on feedback from golint.
|
||||||
|
|
||||||
|
## dev / 2014-07-09
|
||||||
|
|
||||||
|
* Moved to [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify).
|
||||||
|
* Use os.NewSyscallError instead of returning errno (thanks @hariharan-uno)
|
||||||
|
|
||||||
|
## dev / 2014-07-04
|
||||||
|
|
||||||
|
* kqueue: fix incorrect mutex used in Close()
|
||||||
|
* Update example to demonstrate usage of Op.
|
||||||
|
|
||||||
|
## dev / 2014-06-28
|
||||||
|
|
||||||
|
* [API] Don't set the Write Op for attribute notifications [#4](https://github.com/fsnotify/fsnotify/issues/4)
|
||||||
|
* Fix for String() method on Event (thanks Alex Brainman)
|
||||||
|
* Don't build on Plan 9 or Solaris (thanks @4ad)
|
||||||
|
|
||||||
|
## dev / 2014-06-21
|
||||||
|
|
||||||
|
* Events channel of type Event rather than *Event.
|
||||||
|
* [internal] use syscall constants directly for inotify and kqueue.
|
||||||
|
* [internal] kqueue: rename events to kevents and fileEvent to event.
|
||||||
|
|
||||||
|
## dev / 2014-06-19
|
||||||
|
|
||||||
|
* Go 1.3+ required on Windows (uses syscall.ERROR_MORE_DATA internally).
|
||||||
|
* [internal] remove cookie from Event struct (unused).
|
||||||
|
* [internal] Event struct has the same definition across every OS.
|
||||||
|
* [internal] remove internal watch and removeWatch methods.
|
||||||
|
|
||||||
|
## dev / 2014-06-12
|
||||||
|
|
||||||
|
* [API] Renamed Watch() to Add() and RemoveWatch() to Remove().
|
||||||
|
* [API] Pluralized channel names: Events and Errors.
|
||||||
|
* [API] Renamed FileEvent struct to Event.
|
||||||
|
* [API] Op constants replace methods like IsCreate().
|
||||||
|
|
||||||
|
## dev / 2014-06-12
|
||||||
|
|
||||||
|
* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
|
||||||
|
|
||||||
|
## dev / 2014-05-23
|
||||||
|
|
||||||
|
* [API] Remove current implementation of WatchFlags.
|
||||||
|
* current implementation doesn't take advantage of OS for efficiency
|
||||||
|
* provides little benefit over filtering events as they are received, but has extra bookkeeping and mutexes
|
||||||
|
* no tests for the current implementation
|
||||||
|
* not fully implemented on Windows [#93](https://github.com/howeyc/fsnotify/issues/93#issuecomment-39285195)
|
||||||
|
|
||||||
|
## [0.9.3] - 2014-12-31
|
||||||
|
|
||||||
|
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
|
||||||
|
|
||||||
|
## [0.9.2] - 2014-08-17
|
||||||
|
|
||||||
|
* [Backport] Fix missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
|
||||||
|
|
||||||
|
## [0.9.1] - 2014-06-12
|
||||||
|
|
||||||
|
* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
|
||||||
|
|
||||||
|
## [0.9.0] - 2014-01-17
|
||||||
|
|
||||||
|
* IsAttrib() for events that only concern a file's metadata [#79][] (thanks @abustany)
|
||||||
|
* [Fix] kqueue: fix deadlock [#77][] (thanks @cespare)
|
||||||
|
* [NOTICE] Development has moved to `code.google.com/p/go.exp/fsnotify` in preparation for inclusion in the Go standard library.
|
||||||
|
|
||||||
|
## [0.8.12] - 2013-11-13
|
||||||
|
|
||||||
|
* [API] Remove FD_SET and friends from Linux adapter
|
||||||
|
|
||||||
|
## [0.8.11] - 2013-11-02
|
||||||
|
|
||||||
|
* [Doc] Add Changelog [#72][] (thanks @nathany)
|
||||||
|
* [Doc] Spotlight and double modify events on macOS [#62][] (reported by @paulhammond)
|
||||||
|
|
||||||
|
## [0.8.10] - 2013-10-19
|
||||||
|
|
||||||
|
* [Fix] kqueue: remove file watches when parent directory is removed [#71][] (reported by @mdwhatcott)
|
||||||
|
* [Fix] kqueue: race between Close and readEvents [#70][] (reported by @bernerdschaefer)
|
||||||
|
* [Doc] specify OS-specific limits in README (thanks @debrando)
|
||||||
|
|
||||||
|
## [0.8.9] - 2013-09-08
|
||||||
|
|
||||||
|
* [Doc] Contributing (thanks @nathany)
|
||||||
|
* [Doc] update package path in example code [#63][] (thanks @paulhammond)
|
||||||
|
* [Doc] GoCI badge in README (Linux only) [#60][]
|
||||||
|
* [Doc] Cross-platform testing with Vagrant [#59][] (thanks @nathany)
|
||||||
|
|
||||||
|
## [0.8.8] - 2013-06-17
|
||||||
|
|
||||||
|
* [Fix] Windows: handle `ERROR_MORE_DATA` on Windows [#49][] (thanks @jbowtie)
|
||||||
|
|
||||||
|
## [0.8.7] - 2013-06-03
|
||||||
|
|
||||||
|
* [API] Make syscall flags internal
|
||||||
|
* [Fix] inotify: ignore event changes
|
||||||
|
* [Fix] race in symlink test [#45][] (reported by @srid)
|
||||||
|
* [Fix] tests on Windows
|
||||||
|
* lower case error messages
|
||||||
|
|
||||||
|
## [0.8.6] - 2013-05-23
|
||||||
|
|
||||||
|
* kqueue: Use EVT_ONLY flag on Darwin
|
||||||
|
* [Doc] Update README with full example
|
||||||
|
|
||||||
|
## [0.8.5] - 2013-05-09
|
||||||
|
|
||||||
|
* [Fix] inotify: allow monitoring of "broken" symlinks (thanks @tsg)
|
||||||
|
|
||||||
|
## [0.8.4] - 2013-04-07
|
||||||
|
|
||||||
|
* [Fix] kqueue: watch all file events [#40][] (thanks @ChrisBuchholz)
|
||||||
|
|
||||||
|
## [0.8.3] - 2013-03-13
|
||||||
|
|
||||||
|
* [Fix] inoitfy/kqueue memory leak [#36][] (reported by @nbkolchin)
|
||||||
|
* [Fix] kqueue: use fsnFlags for watching a directory [#33][] (reported by @nbkolchin)
|
||||||
|
|
||||||
|
## [0.8.2] - 2013-02-07
|
||||||
|
|
||||||
|
* [Doc] add Authors
|
||||||
|
* [Fix] fix data races for map access [#29][] (thanks @fsouza)
|
||||||
|
|
||||||
|
## [0.8.1] - 2013-01-09
|
||||||
|
|
||||||
|
* [Fix] Windows path separators
|
||||||
|
* [Doc] BSD License
|
||||||
|
|
||||||
|
## [0.8.0] - 2012-11-09
|
||||||
|
|
||||||
|
* kqueue: directory watching improvements (thanks @vmirage)
|
||||||
|
* inotify: add `IN_MOVED_TO` [#25][] (requested by @cpisto)
|
||||||
|
* [Fix] kqueue: deleting watched directory [#24][] (reported by @jakerr)
|
||||||
|
|
||||||
|
## [0.7.4] - 2012-10-09
|
||||||
|
|
||||||
|
* [Fix] inotify: fixes from https://codereview.appspot.com/5418045/ (ugorji)
|
||||||
|
* [Fix] kqueue: preserve watch flags when watching for delete [#21][] (reported by @robfig)
|
||||||
|
* [Fix] kqueue: watch the directory even if it isn't a new watch (thanks @robfig)
|
||||||
|
* [Fix] kqueue: modify after recreation of file
|
||||||
|
|
||||||
|
## [0.7.3] - 2012-09-27
|
||||||
|
|
||||||
|
* [Fix] kqueue: watch with an existing folder inside the watched folder (thanks @vmirage)
|
||||||
|
* [Fix] kqueue: no longer get duplicate CREATE events
|
||||||
|
|
||||||
|
## [0.7.2] - 2012-09-01
|
||||||
|
|
||||||
|
* kqueue: events for created directories
|
||||||
|
|
||||||
|
## [0.7.1] - 2012-07-14
|
||||||
|
|
||||||
|
* [Fix] for renaming files
|
||||||
|
|
||||||
|
## [0.7.0] - 2012-07-02
|
||||||
|
|
||||||
|
* [Feature] FSNotify flags
|
||||||
|
* [Fix] inotify: Added file name back to event path
|
||||||
|
|
||||||
|
## [0.6.0] - 2012-06-06
|
||||||
|
|
||||||
|
* kqueue: watch files after directory created (thanks @tmc)
|
||||||
|
|
||||||
|
## [0.5.1] - 2012-05-22
|
||||||
|
|
||||||
|
* [Fix] inotify: remove all watches before Close()
|
||||||
|
|
||||||
|
## [0.5.0] - 2012-05-03
|
||||||
|
|
||||||
|
* [API] kqueue: return errors during watch instead of sending over channel
|
||||||
|
* kqueue: match symlink behavior on Linux
|
||||||
|
* inotify: add `DELETE_SELF` (requested by @taralx)
|
||||||
|
* [Fix] kqueue: handle EINTR (reported by @robfig)
|
||||||
|
* [Doc] Godoc example [#1][] (thanks @davecheney)
|
||||||
|
|
||||||
|
## [0.4.0] - 2012-03-30
|
||||||
|
|
||||||
|
* Go 1 released: build with go tool
|
||||||
|
* [Feature] Windows support using winfsnotify
|
||||||
|
* Windows does not have attribute change notifications
|
||||||
|
* Roll attribute notifications into IsModify
|
||||||
|
|
||||||
|
## [0.3.0] - 2012-02-19
|
||||||
|
|
||||||
|
* kqueue: add files when watch directory
|
||||||
|
|
||||||
|
## [0.2.0] - 2011-12-30
|
||||||
|
|
||||||
|
* update to latest Go weekly code
|
||||||
|
|
||||||
|
## [0.1.0] - 2011-10-19
|
||||||
|
|
||||||
|
* kqueue: add watch on file creation to match inotify
|
||||||
|
* kqueue: create file event
|
||||||
|
* inotify: ignore `IN_IGNORED` events
|
||||||
|
* event String()
|
||||||
|
* linux: common FileEvent functions
|
||||||
|
* initial commit
|
||||||
|
|
||||||
|
[#79]: https://github.com/howeyc/fsnotify/pull/79
|
||||||
|
[#77]: https://github.com/howeyc/fsnotify/pull/77
|
||||||
|
[#72]: https://github.com/howeyc/fsnotify/issues/72
|
||||||
|
[#71]: https://github.com/howeyc/fsnotify/issues/71
|
||||||
|
[#70]: https://github.com/howeyc/fsnotify/issues/70
|
||||||
|
[#63]: https://github.com/howeyc/fsnotify/issues/63
|
||||||
|
[#62]: https://github.com/howeyc/fsnotify/issues/62
|
||||||
|
[#60]: https://github.com/howeyc/fsnotify/issues/60
|
||||||
|
[#59]: https://github.com/howeyc/fsnotify/issues/59
|
||||||
|
[#49]: https://github.com/howeyc/fsnotify/issues/49
|
||||||
|
[#45]: https://github.com/howeyc/fsnotify/issues/45
|
||||||
|
[#40]: https://github.com/howeyc/fsnotify/issues/40
|
||||||
|
[#36]: https://github.com/howeyc/fsnotify/issues/36
|
||||||
|
[#33]: https://github.com/howeyc/fsnotify/issues/33
|
||||||
|
[#29]: https://github.com/howeyc/fsnotify/issues/29
|
||||||
|
[#25]: https://github.com/howeyc/fsnotify/issues/25
|
||||||
|
[#24]: https://github.com/howeyc/fsnotify/issues/24
|
||||||
|
[#21]: https://github.com/howeyc/fsnotify/issues/21
|
26
vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md
generated
vendored
Normal file
26
vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md
generated
vendored
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
Thank you for your interest in contributing to fsnotify! We try to review and
|
||||||
|
merge PRs in a reasonable timeframe, but please be aware that:
|
||||||
|
|
||||||
|
- To avoid "wasted" work, please discus changes on the issue tracker first. You
|
||||||
|
can just send PRs, but they may end up being rejected for one reason or the
|
||||||
|
other.
|
||||||
|
|
||||||
|
- fsnotify is a cross-platform library, and changes must work reasonably well on
|
||||||
|
all supported platforms.
|
||||||
|
|
||||||
|
- Changes will need to be compatible; old code should still compile, and the
|
||||||
|
runtime behaviour can't change in ways that are likely to lead to problems for
|
||||||
|
users.
|
||||||
|
|
||||||
|
Testing
|
||||||
|
-------
|
||||||
|
Just `go test ./...` runs all the tests; the CI runs this on all supported
|
||||||
|
platforms. Testing different platforms locally can be done with something like
|
||||||
|
[goon] or [Vagrant], but this isn't super-easy to set up at the moment.
|
||||||
|
|
||||||
|
Use the `-short` flag to make the "stress test" run faster.
|
||||||
|
|
||||||
|
|
||||||
|
[goon]: https://github.com/arp242/goon
|
||||||
|
[Vagrant]: https://www.vagrantup.com/
|
||||||
|
[integration_test.go]: /integration_test.go
|
25
vendor/github.com/fsnotify/fsnotify/LICENSE
generated
vendored
Normal file
25
vendor/github.com/fsnotify/fsnotify/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
Copyright © 2012 The Go Authors. All rights reserved.
|
||||||
|
Copyright © fsnotify Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer in the documentation and/or
|
||||||
|
other materials provided with the distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its contributors may be used
|
||||||
|
to endorse or promote products derived from this software without specific
|
||||||
|
prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
161
vendor/github.com/fsnotify/fsnotify/README.md
generated
vendored
Normal file
161
vendor/github.com/fsnotify/fsnotify/README.md
generated
vendored
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
fsnotify is a Go library to provide cross-platform filesystem notifications on
|
||||||
|
Windows, Linux, macOS, and BSD systems.
|
||||||
|
|
||||||
|
Go 1.16 or newer is required; the full documentation is at
|
||||||
|
https://pkg.go.dev/github.com/fsnotify/fsnotify
|
||||||
|
|
||||||
|
**It's best to read the documentation at pkg.go.dev, as it's pinned to the last
|
||||||
|
released version, whereas this README is for the last development version which
|
||||||
|
may include additions/changes.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Platform support:
|
||||||
|
|
||||||
|
| Adapter | OS | Status |
|
||||||
|
| --------------------- | ---------------| -------------------------------------------------------------|
|
||||||
|
| inotify | Linux 2.6.32+ | Supported |
|
||||||
|
| kqueue | BSD, macOS | Supported |
|
||||||
|
| ReadDirectoryChangesW | Windows | Supported |
|
||||||
|
| FSEvents | macOS | [Planned](https://github.com/fsnotify/fsnotify/issues/11) |
|
||||||
|
| FEN | Solaris 11 | [In Progress](https://github.com/fsnotify/fsnotify/pull/371) |
|
||||||
|
| fanotify | Linux 5.9+ | [Maybe](https://github.com/fsnotify/fsnotify/issues/114) |
|
||||||
|
| USN Journals | Windows | [Maybe](https://github.com/fsnotify/fsnotify/issues/53) |
|
||||||
|
| Polling | *All* | [Maybe](https://github.com/fsnotify/fsnotify/issues/9) |
|
||||||
|
|
||||||
|
Linux and macOS should include Android and iOS, but these are currently untested.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
A basic example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Create new watcher.
|
||||||
|
watcher, err := fsnotify.NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer watcher.Close()
|
||||||
|
|
||||||
|
// Start listening for events.
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event, ok := <-watcher.Events:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Println("event:", event)
|
||||||
|
if event.Has(fsnotify.Write) {
|
||||||
|
log.Println("modified file:", event.Name)
|
||||||
|
}
|
||||||
|
case err, ok := <-watcher.Errors:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Println("error:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Add a path.
|
||||||
|
err = watcher.Add("/tmp")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block main goroutine forever.
|
||||||
|
<-make(chan struct{})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Some more examples can be found in [cmd/fsnotify](cmd/fsnotify), which can be
|
||||||
|
run with:
|
||||||
|
|
||||||
|
% go run ./cmd/fsnotify
|
||||||
|
|
||||||
|
FAQ
|
||||||
|
---
|
||||||
|
### Will a file still be watched when it's moved to another directory?
|
||||||
|
No, not unless you are watching the location it was moved to.
|
||||||
|
|
||||||
|
### Are subdirectories watched too?
|
||||||
|
No, you must add watches for any directory you want to watch (a recursive
|
||||||
|
watcher is on the roadmap: [#18]).
|
||||||
|
|
||||||
|
[#18]: https://github.com/fsnotify/fsnotify/issues/18
|
||||||
|
|
||||||
|
### Do I have to watch the Error and Event channels in a goroutine?
|
||||||
|
As of now, yes (you can read both channels in the same goroutine using `select`,
|
||||||
|
you don't need a separate goroutine for both channels; see the example).
|
||||||
|
|
||||||
|
### Why don't notifications work with NFS, SMB, FUSE, /proc, or /sys?
|
||||||
|
fsnotify requires support from underlying OS to work. The current NFS and SMB
|
||||||
|
protocols does not provide network level support for file notifications, and
|
||||||
|
neither do the /proc and /sys virtual filesystems.
|
||||||
|
|
||||||
|
This could be fixed with a polling watcher ([#9]), but it's not yet implemented.
|
||||||
|
|
||||||
|
[#9]: https://github.com/fsnotify/fsnotify/issues/9
|
||||||
|
|
||||||
|
Platform-specific notes
|
||||||
|
-----------------------
|
||||||
|
### Linux
|
||||||
|
When a file is removed a REMOVE event won't be emitted until all file
|
||||||
|
descriptors are closed; it will emit a CHMOD instead:
|
||||||
|
|
||||||
|
fp := os.Open("file")
|
||||||
|
os.Remove("file") // CHMOD
|
||||||
|
fp.Close() // REMOVE
|
||||||
|
|
||||||
|
This is the event that inotify sends, so not much can be changed about this.
|
||||||
|
|
||||||
|
The `fs.inotify.max_user_watches` sysctl variable specifies the upper limit for
|
||||||
|
the number of watches per user, and `fs.inotify.max_user_instances` specifies
|
||||||
|
the maximum number of inotify instances per user. Every Watcher you create is an
|
||||||
|
"instance", and every path you add is a "watch".
|
||||||
|
|
||||||
|
These are also exposed in `/proc` as `/proc/sys/fs/inotify/max_user_watches` and
|
||||||
|
`/proc/sys/fs/inotify/max_user_instances`
|
||||||
|
|
||||||
|
To increase them you can use `sysctl` or write the value to proc file:
|
||||||
|
|
||||||
|
# The default values on Linux 5.18
|
||||||
|
sysctl fs.inotify.max_user_watches=124983
|
||||||
|
sysctl fs.inotify.max_user_instances=128
|
||||||
|
|
||||||
|
To make the changes persist on reboot edit `/etc/sysctl.conf` or
|
||||||
|
`/usr/lib/sysctl.d/50-default.conf` (details differ per Linux distro; check your
|
||||||
|
distro's documentation):
|
||||||
|
|
||||||
|
fs.inotify.max_user_watches=124983
|
||||||
|
fs.inotify.max_user_instances=128
|
||||||
|
|
||||||
|
Reaching the limit will result in a "no space left on device" or "too many open
|
||||||
|
files" error.
|
||||||
|
|
||||||
|
### kqueue (macOS, all BSD systems)
|
||||||
|
kqueue requires opening a file descriptor for every file that's being watched;
|
||||||
|
so if you're watching a directory with five files then that's six file
|
||||||
|
descriptors. You will run in to your system's "max open files" limit faster on
|
||||||
|
these platforms.
|
||||||
|
|
||||||
|
The sysctl variables `kern.maxfiles` and `kern.maxfilesperproc` can be used to
|
||||||
|
control the maximum number of open files.
|
||||||
|
|
||||||
|
### macOS
|
||||||
|
Spotlight indexing on macOS can result in multiple events (see [#15]). A temporary
|
||||||
|
workaround is to add your folder(s) to the *Spotlight Privacy settings* until we
|
||||||
|
have a native FSEvents implementation (see [#11]).
|
||||||
|
|
||||||
|
[#11]: https://github.com/fsnotify/fsnotify/issues/11
|
||||||
|
[#15]: https://github.com/fsnotify/fsnotify/issues/15
|
162
vendor/github.com/fsnotify/fsnotify/backend_fen.go
generated
vendored
Normal file
162
vendor/github.com/fsnotify/fsnotify/backend_fen.go
generated
vendored
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
//go:build solaris
|
||||||
|
// +build solaris
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Watcher watches a set of paths, delivering events on a channel.
|
||||||
|
//
|
||||||
|
// A watcher should not be copied (e.g. pass it by pointer, rather than by
|
||||||
|
// value).
|
||||||
|
//
|
||||||
|
// # Linux notes
|
||||||
|
//
|
||||||
|
// When a file is removed a Remove event won't be emitted until all file
|
||||||
|
// descriptors are closed, and deletes will always emit a Chmod. For example:
|
||||||
|
//
|
||||||
|
// fp := os.Open("file")
|
||||||
|
// os.Remove("file") // Triggers Chmod
|
||||||
|
// fp.Close() // Triggers Remove
|
||||||
|
//
|
||||||
|
// This is the event that inotify sends, so not much can be changed about this.
|
||||||
|
//
|
||||||
|
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
|
||||||
|
// for the number of watches per user, and fs.inotify.max_user_instances
|
||||||
|
// specifies the maximum number of inotify instances per user. Every Watcher you
|
||||||
|
// create is an "instance", and every path you add is a "watch".
|
||||||
|
//
|
||||||
|
// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
|
||||||
|
// /proc/sys/fs/inotify/max_user_instances
|
||||||
|
//
|
||||||
|
// To increase them you can use sysctl or write the value to the /proc file:
|
||||||
|
//
|
||||||
|
// # Default values on Linux 5.18
|
||||||
|
// sysctl fs.inotify.max_user_watches=124983
|
||||||
|
// sysctl fs.inotify.max_user_instances=128
|
||||||
|
//
|
||||||
|
// To make the changes persist on reboot edit /etc/sysctl.conf or
|
||||||
|
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
|
||||||
|
// your distro's documentation):
|
||||||
|
//
|
||||||
|
// fs.inotify.max_user_watches=124983
|
||||||
|
// fs.inotify.max_user_instances=128
|
||||||
|
//
|
||||||
|
// Reaching the limit will result in a "no space left on device" or "too many open
|
||||||
|
// files" error.
|
||||||
|
//
|
||||||
|
// # kqueue notes (macOS, BSD)
|
||||||
|
//
|
||||||
|
// kqueue requires opening a file descriptor for every file that's being watched;
|
||||||
|
// so if you're watching a directory with five files then that's six file
|
||||||
|
// descriptors. You will run in to your system's "max open files" limit faster on
|
||||||
|
// these platforms.
|
||||||
|
//
|
||||||
|
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
|
||||||
|
// control the maximum number of open files, as well as /etc/login.conf on BSD
|
||||||
|
// systems.
|
||||||
|
//
|
||||||
|
// # macOS notes
|
||||||
|
//
|
||||||
|
// Spotlight indexing on macOS can result in multiple events (see [#15]). A
|
||||||
|
// temporary workaround is to add your folder(s) to the "Spotlight Privacy
|
||||||
|
// Settings" until we have a native FSEvents implementation (see [#11]).
|
||||||
|
//
|
||||||
|
// [#11]: https://github.com/fsnotify/fsnotify/issues/11
|
||||||
|
// [#15]: https://github.com/fsnotify/fsnotify/issues/15
|
||||||
|
type Watcher struct {
|
||||||
|
// Events sends the filesystem change events.
|
||||||
|
//
|
||||||
|
// fsnotify can send the following events; a "path" here can refer to a
|
||||||
|
// file, directory, symbolic link, or special file like a FIFO.
|
||||||
|
//
|
||||||
|
// fsnotify.Create A new path was created; this may be followed by one
|
||||||
|
// or more Write events if data also gets written to a
|
||||||
|
// file.
|
||||||
|
//
|
||||||
|
// fsnotify.Remove A path was removed.
|
||||||
|
//
|
||||||
|
// fsnotify.Rename A path was renamed. A rename is always sent with the
|
||||||
|
// old path as Event.Name, and a Create event will be
|
||||||
|
// sent with the new name. Renames are only sent for
|
||||||
|
// paths that are currently watched; e.g. moving an
|
||||||
|
// unmonitored file into a monitored directory will
|
||||||
|
// show up as just a Create. Similarly, renaming a file
|
||||||
|
// to outside a monitored directory will show up as
|
||||||
|
// only a Rename.
|
||||||
|
//
|
||||||
|
// fsnotify.Write A file or named pipe was written to. A Truncate will
|
||||||
|
// also trigger a Write. A single "write action"
|
||||||
|
// initiated by the user may show up as one or multiple
|
||||||
|
// writes, depending on when the system syncs things to
|
||||||
|
// disk. For example when compiling a large Go program
|
||||||
|
// you may get hundreds of Write events, so you
|
||||||
|
// probably want to wait until you've stopped receiving
|
||||||
|
// them (see the dedup example in cmd/fsnotify).
|
||||||
|
//
|
||||||
|
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
|
||||||
|
// when a file is removed (or more accurately, when a
|
||||||
|
// link to an inode is removed). On kqueue it's sent
|
||||||
|
// and on kqueue when a file is truncated. On Windows
|
||||||
|
// it's never sent.
|
||||||
|
Events chan Event
|
||||||
|
|
||||||
|
// Errors sends any errors.
|
||||||
|
Errors chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWatcher creates a new Watcher.
|
||||||
|
func NewWatcher() (*Watcher, error) {
|
||||||
|
return nil, errors.New("FEN based watcher not yet supported for fsnotify\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close removes all watches and closes the events channel.
|
||||||
|
func (w *Watcher) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add starts monitoring the path for changes.
|
||||||
|
//
|
||||||
|
// A path can only be watched once; attempting to watch it more than once will
|
||||||
|
// return an error. Paths that do not yet exist on the filesystem cannot be
|
||||||
|
// added. A watch will be automatically removed if the path is deleted.
|
||||||
|
//
|
||||||
|
// A path will remain watched if it gets renamed to somewhere else on the same
|
||||||
|
// filesystem, but the monitor will get removed if the path gets deleted and
|
||||||
|
// re-created, or if it's moved to a different filesystem.
|
||||||
|
//
|
||||||
|
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
|
||||||
|
// filesystems (/proc, /sys, etc.) generally don't work.
|
||||||
|
//
|
||||||
|
// # Watching directories
|
||||||
|
//
|
||||||
|
// All files in a directory are monitored, including new files that are created
|
||||||
|
// after the watcher is started. Subdirectories are not watched (i.e. it's
|
||||||
|
// non-recursive).
|
||||||
|
//
|
||||||
|
// # Watching files
|
||||||
|
//
|
||||||
|
// Watching individual files (rather than directories) is generally not
|
||||||
|
// recommended as many tools update files atomically. Instead of "just" writing
|
||||||
|
// to the file a temporary file will be written to first, and if successful the
|
||||||
|
// temporary file is moved to to destination removing the original, or some
|
||||||
|
// variant thereof. The watcher on the original file is now lost, as it no
|
||||||
|
// longer exists.
|
||||||
|
//
|
||||||
|
// Instead, watch the parent directory and use Event.Name to filter out files
|
||||||
|
// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
|
||||||
|
func (w *Watcher) Add(name string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove stops monitoring the path for changes.
|
||||||
|
//
|
||||||
|
// Directories are always removed non-recursively. For example, if you added
|
||||||
|
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
|
||||||
|
//
|
||||||
|
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
|
||||||
|
func (w *Watcher) Remove(name string) error {
|
||||||
|
return nil
|
||||||
|
}
|
459
vendor/github.com/fsnotify/fsnotify/backend_inotify.go
generated
vendored
Normal file
459
vendor/github.com/fsnotify/fsnotify/backend_inotify.go
generated
vendored
Normal file
|
@ -0,0 +1,459 @@
|
||||||
|
//go:build linux
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Watcher watches a set of paths, delivering events on a channel.
|
||||||
|
//
|
||||||
|
// A watcher should not be copied (e.g. pass it by pointer, rather than by
|
||||||
|
// value).
|
||||||
|
//
|
||||||
|
// # Linux notes
|
||||||
|
//
|
||||||
|
// When a file is removed a Remove event won't be emitted until all file
|
||||||
|
// descriptors are closed, and deletes will always emit a Chmod. For example:
|
||||||
|
//
|
||||||
|
// fp := os.Open("file")
|
||||||
|
// os.Remove("file") // Triggers Chmod
|
||||||
|
// fp.Close() // Triggers Remove
|
||||||
|
//
|
||||||
|
// This is the event that inotify sends, so not much can be changed about this.
|
||||||
|
//
|
||||||
|
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
|
||||||
|
// for the number of watches per user, and fs.inotify.max_user_instances
|
||||||
|
// specifies the maximum number of inotify instances per user. Every Watcher you
|
||||||
|
// create is an "instance", and every path you add is a "watch".
|
||||||
|
//
|
||||||
|
// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
|
||||||
|
// /proc/sys/fs/inotify/max_user_instances
|
||||||
|
//
|
||||||
|
// To increase them you can use sysctl or write the value to the /proc file:
|
||||||
|
//
|
||||||
|
// # Default values on Linux 5.18
|
||||||
|
// sysctl fs.inotify.max_user_watches=124983
|
||||||
|
// sysctl fs.inotify.max_user_instances=128
|
||||||
|
//
|
||||||
|
// To make the changes persist on reboot edit /etc/sysctl.conf or
|
||||||
|
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
|
||||||
|
// your distro's documentation):
|
||||||
|
//
|
||||||
|
// fs.inotify.max_user_watches=124983
|
||||||
|
// fs.inotify.max_user_instances=128
|
||||||
|
//
|
||||||
|
// Reaching the limit will result in a "no space left on device" or "too many open
|
||||||
|
// files" error.
|
||||||
|
//
|
||||||
|
// # kqueue notes (macOS, BSD)
|
||||||
|
//
|
||||||
|
// kqueue requires opening a file descriptor for every file that's being watched;
|
||||||
|
// so if you're watching a directory with five files then that's six file
|
||||||
|
// descriptors. You will run in to your system's "max open files" limit faster on
|
||||||
|
// these platforms.
|
||||||
|
//
|
||||||
|
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
|
||||||
|
// control the maximum number of open files, as well as /etc/login.conf on BSD
|
||||||
|
// systems.
|
||||||
|
//
|
||||||
|
// # macOS notes
|
||||||
|
//
|
||||||
|
// Spotlight indexing on macOS can result in multiple events (see [#15]). A
|
||||||
|
// temporary workaround is to add your folder(s) to the "Spotlight Privacy
|
||||||
|
// Settings" until we have a native FSEvents implementation (see [#11]).
|
||||||
|
//
|
||||||
|
// [#11]: https://github.com/fsnotify/fsnotify/issues/11
|
||||||
|
// [#15]: https://github.com/fsnotify/fsnotify/issues/15
|
||||||
|
type Watcher struct {
|
||||||
|
// Events sends the filesystem change events.
|
||||||
|
//
|
||||||
|
// fsnotify can send the following events; a "path" here can refer to a
|
||||||
|
// file, directory, symbolic link, or special file like a FIFO.
|
||||||
|
//
|
||||||
|
// fsnotify.Create A new path was created; this may be followed by one
|
||||||
|
// or more Write events if data also gets written to a
|
||||||
|
// file.
|
||||||
|
//
|
||||||
|
// fsnotify.Remove A path was removed.
|
||||||
|
//
|
||||||
|
// fsnotify.Rename A path was renamed. A rename is always sent with the
|
||||||
|
// old path as Event.Name, and a Create event will be
|
||||||
|
// sent with the new name. Renames are only sent for
|
||||||
|
// paths that are currently watched; e.g. moving an
|
||||||
|
// unmonitored file into a monitored directory will
|
||||||
|
// show up as just a Create. Similarly, renaming a file
|
||||||
|
// to outside a monitored directory will show up as
|
||||||
|
// only a Rename.
|
||||||
|
//
|
||||||
|
// fsnotify.Write A file or named pipe was written to. A Truncate will
|
||||||
|
// also trigger a Write. A single "write action"
|
||||||
|
// initiated by the user may show up as one or multiple
|
||||||
|
// writes, depending on when the system syncs things to
|
||||||
|
// disk. For example when compiling a large Go program
|
||||||
|
// you may get hundreds of Write events, so you
|
||||||
|
// probably want to wait until you've stopped receiving
|
||||||
|
// them (see the dedup example in cmd/fsnotify).
|
||||||
|
//
|
||||||
|
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
|
||||||
|
// when a file is removed (or more accurately, when a
|
||||||
|
// link to an inode is removed). On kqueue it's sent
|
||||||
|
// and on kqueue when a file is truncated. On Windows
|
||||||
|
// it's never sent.
|
||||||
|
Events chan Event
|
||||||
|
|
||||||
|
// Errors sends any errors.
|
||||||
|
Errors chan error
|
||||||
|
|
||||||
|
// Store fd here as os.File.Read() will no longer return on close after
|
||||||
|
// calling Fd(). See: https://github.com/golang/go/issues/26439
|
||||||
|
fd int
|
||||||
|
mu sync.Mutex // Map access
|
||||||
|
inotifyFile *os.File
|
||||||
|
watches map[string]*watch // Map of inotify watches (key: path)
|
||||||
|
paths map[int]string // Map of watched paths (key: watch descriptor)
|
||||||
|
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
|
||||||
|
doneResp chan struct{} // Channel to respond to Close
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWatcher creates a new Watcher.
|
||||||
|
func NewWatcher() (*Watcher, error) {
|
||||||
|
// Create inotify fd
|
||||||
|
// Need to set the FD to nonblocking mode in order for SetDeadline methods to work
|
||||||
|
// Otherwise, blocking i/o operations won't terminate on close
|
||||||
|
fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC | unix.IN_NONBLOCK)
|
||||||
|
if fd == -1 {
|
||||||
|
return nil, errno
|
||||||
|
}
|
||||||
|
|
||||||
|
w := &Watcher{
|
||||||
|
fd: fd,
|
||||||
|
inotifyFile: os.NewFile(uintptr(fd), ""),
|
||||||
|
watches: make(map[string]*watch),
|
||||||
|
paths: make(map[int]string),
|
||||||
|
Events: make(chan Event),
|
||||||
|
Errors: make(chan error),
|
||||||
|
done: make(chan struct{}),
|
||||||
|
doneResp: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
go w.readEvents()
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the event was sent, or false if watcher is closed.
|
||||||
|
func (w *Watcher) sendEvent(e Event) bool {
|
||||||
|
select {
|
||||||
|
case w.Events <- e:
|
||||||
|
return true
|
||||||
|
case <-w.done:
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the error was sent, or false if watcher is closed.
|
||||||
|
func (w *Watcher) sendError(err error) bool {
|
||||||
|
select {
|
||||||
|
case w.Errors <- err:
|
||||||
|
return true
|
||||||
|
case <-w.done:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Watcher) isClosed() bool {
|
||||||
|
select {
|
||||||
|
case <-w.done:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close removes all watches and closes the events channel.
|
||||||
|
func (w *Watcher) Close() error {
|
||||||
|
w.mu.Lock()
|
||||||
|
if w.isClosed() {
|
||||||
|
w.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send 'close' signal to goroutine, and set the Watcher to closed.
|
||||||
|
close(w.done)
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
// Causes any blocking reads to return with an error, provided the file
|
||||||
|
// still supports deadline operations.
|
||||||
|
err := w.inotifyFile.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for goroutine to close
|
||||||
|
<-w.doneResp
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add starts monitoring the path for changes.
|
||||||
|
//
|
||||||
|
// A path can only be watched once; attempting to watch it more than once will
|
||||||
|
// return an error. Paths that do not yet exist on the filesystem cannot be
|
||||||
|
// added. A watch will be automatically removed if the path is deleted.
|
||||||
|
//
|
||||||
|
// A path will remain watched if it gets renamed to somewhere else on the same
|
||||||
|
// filesystem, but the monitor will get removed if the path gets deleted and
|
||||||
|
// re-created, or if it's moved to a different filesystem.
|
||||||
|
//
|
||||||
|
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
|
||||||
|
// filesystems (/proc, /sys, etc.) generally don't work.
|
||||||
|
//
|
||||||
|
// # Watching directories
|
||||||
|
//
|
||||||
|
// All files in a directory are monitored, including new files that are created
|
||||||
|
// after the watcher is started. Subdirectories are not watched (i.e. it's
|
||||||
|
// non-recursive).
|
||||||
|
//
|
||||||
|
// # Watching files
|
||||||
|
//
|
||||||
|
// Watching individual files (rather than directories) is generally not
|
||||||
|
// recommended as many tools update files atomically. Instead of "just" writing
|
||||||
|
// to the file a temporary file will be written to first, and if successful the
|
||||||
|
// temporary file is moved to to destination removing the original, or some
|
||||||
|
// variant thereof. The watcher on the original file is now lost, as it no
|
||||||
|
// longer exists.
|
||||||
|
//
|
||||||
|
// Instead, watch the parent directory and use Event.Name to filter out files
|
||||||
|
// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
|
||||||
|
func (w *Watcher) Add(name string) error {
|
||||||
|
name = filepath.Clean(name)
|
||||||
|
if w.isClosed() {
|
||||||
|
return errors.New("inotify instance already closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
var flags uint32 = unix.IN_MOVED_TO | unix.IN_MOVED_FROM |
|
||||||
|
unix.IN_CREATE | unix.IN_ATTRIB | unix.IN_MODIFY |
|
||||||
|
unix.IN_MOVE_SELF | unix.IN_DELETE | unix.IN_DELETE_SELF
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
watchEntry := w.watches[name]
|
||||||
|
if watchEntry != nil {
|
||||||
|
flags |= watchEntry.flags | unix.IN_MASK_ADD
|
||||||
|
}
|
||||||
|
wd, errno := unix.InotifyAddWatch(w.fd, name, flags)
|
||||||
|
if wd == -1 {
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
|
||||||
|
if watchEntry == nil {
|
||||||
|
w.watches[name] = &watch{wd: uint32(wd), flags: flags}
|
||||||
|
w.paths[wd] = name
|
||||||
|
} else {
|
||||||
|
watchEntry.wd = uint32(wd)
|
||||||
|
watchEntry.flags = flags
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove stops monitoring the path for changes.
|
||||||
|
//
|
||||||
|
// Directories are always removed non-recursively. For example, if you added
|
||||||
|
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
|
||||||
|
//
|
||||||
|
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
|
||||||
|
func (w *Watcher) Remove(name string) error {
|
||||||
|
name = filepath.Clean(name)
|
||||||
|
|
||||||
|
// Fetch the watch.
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
watch, ok := w.watches[name]
|
||||||
|
|
||||||
|
// Remove it from inotify.
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%w: %s", ErrNonExistentWatch, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We successfully removed the watch if InotifyRmWatch doesn't return an
|
||||||
|
// error, we need to clean up our internal state to ensure it matches
|
||||||
|
// inotify's kernel state.
|
||||||
|
delete(w.paths, int(watch.wd))
|
||||||
|
delete(w.watches, name)
|
||||||
|
|
||||||
|
// inotify_rm_watch will return EINVAL if the file has been deleted;
|
||||||
|
// the inotify will already have been removed.
|
||||||
|
// watches and pathes are deleted in ignoreLinux() implicitly and asynchronously
|
||||||
|
// by calling inotify_rm_watch() below. e.g. readEvents() goroutine receives IN_IGNORE
|
||||||
|
// so that EINVAL means that the wd is being rm_watch()ed or its file removed
|
||||||
|
// by another thread and we have not received IN_IGNORE event.
|
||||||
|
success, errno := unix.InotifyRmWatch(w.fd, watch.wd)
|
||||||
|
if success == -1 {
|
||||||
|
// TODO: Perhaps it's not helpful to return an error here in every case;
|
||||||
|
// The only two possible errors are:
|
||||||
|
//
|
||||||
|
// - EBADF, which happens when w.fd is not a valid file descriptor
|
||||||
|
// of any kind.
|
||||||
|
// - EINVAL, which is when fd is not an inotify descriptor or wd
|
||||||
|
// is not a valid watch descriptor. Watch descriptors are
|
||||||
|
// invalidated when they are removed explicitly or implicitly;
|
||||||
|
// explicitly by inotify_rm_watch, implicitly when the file they
|
||||||
|
// are watching is deleted.
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WatchList returns all paths added with [Add] (and are not yet removed).
|
||||||
|
func (w *Watcher) WatchList() []string {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
entries := make([]string, 0, len(w.watches))
|
||||||
|
for pathname := range w.watches {
|
||||||
|
entries = append(entries, pathname)
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
type watch struct {
|
||||||
|
wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
|
||||||
|
flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readEvents reads from the inotify file descriptor, converts the
|
||||||
|
// received events into Event objects and sends them via the Events channel
|
||||||
|
func (w *Watcher) readEvents() {
|
||||||
|
defer func() {
|
||||||
|
close(w.doneResp)
|
||||||
|
close(w.Errors)
|
||||||
|
close(w.Events)
|
||||||
|
}()
|
||||||
|
|
||||||
|
var (
|
||||||
|
buf [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
|
||||||
|
errno error // Syscall errno
|
||||||
|
)
|
||||||
|
for {
|
||||||
|
// See if we have been closed.
|
||||||
|
if w.isClosed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := w.inotifyFile.Read(buf[:])
|
||||||
|
switch {
|
||||||
|
case errors.Unwrap(err) == os.ErrClosed:
|
||||||
|
return
|
||||||
|
case err != nil:
|
||||||
|
if !w.sendError(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if n < unix.SizeofInotifyEvent {
|
||||||
|
var err error
|
||||||
|
if n == 0 {
|
||||||
|
// If EOF is received. This should really never happen.
|
||||||
|
err = io.EOF
|
||||||
|
} else if n < 0 {
|
||||||
|
// If an error occurred while reading.
|
||||||
|
err = errno
|
||||||
|
} else {
|
||||||
|
// Read was too short.
|
||||||
|
err = errors.New("notify: short read in readEvents()")
|
||||||
|
}
|
||||||
|
if !w.sendError(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset uint32
|
||||||
|
// We don't know how many events we just read into the buffer
|
||||||
|
// While the offset points to at least one whole event...
|
||||||
|
for offset <= uint32(n-unix.SizeofInotifyEvent) {
|
||||||
|
var (
|
||||||
|
// Point "raw" to the event in the buffer
|
||||||
|
raw = (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset]))
|
||||||
|
mask = uint32(raw.Mask)
|
||||||
|
nameLen = uint32(raw.Len)
|
||||||
|
)
|
||||||
|
|
||||||
|
if mask&unix.IN_Q_OVERFLOW != 0 {
|
||||||
|
if !w.sendError(ErrEventOverflow) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the event happened to the watched directory or the watched file, the kernel
|
||||||
|
// doesn't append the filename to the event, but we would like to always fill the
|
||||||
|
// the "Name" field with a valid filename. We retrieve the path of the watch from
|
||||||
|
// the "paths" map.
|
||||||
|
w.mu.Lock()
|
||||||
|
name, ok := w.paths[int(raw.Wd)]
|
||||||
|
// IN_DELETE_SELF occurs when the file/directory being watched is removed.
|
||||||
|
// This is a sign to clean up the maps, otherwise we are no longer in sync
|
||||||
|
// with the inotify kernel state which has already deleted the watch
|
||||||
|
// automatically.
|
||||||
|
if ok && mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF {
|
||||||
|
delete(w.paths, int(raw.Wd))
|
||||||
|
delete(w.watches, name)
|
||||||
|
}
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
if nameLen > 0 {
|
||||||
|
// Point "bytes" at the first byte of the filename
|
||||||
|
bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))[:nameLen:nameLen]
|
||||||
|
// The filename is padded with NULL bytes. TrimRight() gets rid of those.
|
||||||
|
name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
|
||||||
|
}
|
||||||
|
|
||||||
|
event := w.newEvent(name, mask)
|
||||||
|
|
||||||
|
// Send the events that are not ignored on the events channel
|
||||||
|
if mask&unix.IN_IGNORED == 0 {
|
||||||
|
if !w.sendEvent(event) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to the next event in the buffer
|
||||||
|
offset += unix.SizeofInotifyEvent + nameLen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// newEvent returns an platform-independent Event based on an inotify mask.
|
||||||
|
func (w *Watcher) newEvent(name string, mask uint32) Event {
|
||||||
|
e := Event{Name: name}
|
||||||
|
if mask&unix.IN_CREATE == unix.IN_CREATE || mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO {
|
||||||
|
e.Op |= Create
|
||||||
|
}
|
||||||
|
if mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF || mask&unix.IN_DELETE == unix.IN_DELETE {
|
||||||
|
e.Op |= Remove
|
||||||
|
}
|
||||||
|
if mask&unix.IN_MODIFY == unix.IN_MODIFY {
|
||||||
|
e.Op |= Write
|
||||||
|
}
|
||||||
|
if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF || mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM {
|
||||||
|
e.Op |= Rename
|
||||||
|
}
|
||||||
|
if mask&unix.IN_ATTRIB == unix.IN_ATTRIB {
|
||||||
|
e.Op |= Chmod
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
707
vendor/github.com/fsnotify/fsnotify/backend_kqueue.go
generated
vendored
Normal file
707
vendor/github.com/fsnotify/fsnotify/backend_kqueue.go
generated
vendored
Normal file
|
@ -0,0 +1,707 @@
|
||||||
|
//go:build freebsd || openbsd || netbsd || dragonfly || darwin
|
||||||
|
// +build freebsd openbsd netbsd dragonfly darwin
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Watcher watches a set of paths, delivering events on a channel.
|
||||||
|
//
|
||||||
|
// A watcher should not be copied (e.g. pass it by pointer, rather than by
|
||||||
|
// value).
|
||||||
|
//
|
||||||
|
// # Linux notes
|
||||||
|
//
|
||||||
|
// When a file is removed a Remove event won't be emitted until all file
|
||||||
|
// descriptors are closed, and deletes will always emit a Chmod. For example:
|
||||||
|
//
|
||||||
|
// fp := os.Open("file")
|
||||||
|
// os.Remove("file") // Triggers Chmod
|
||||||
|
// fp.Close() // Triggers Remove
|
||||||
|
//
|
||||||
|
// This is the event that inotify sends, so not much can be changed about this.
|
||||||
|
//
|
||||||
|
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
|
||||||
|
// for the number of watches per user, and fs.inotify.max_user_instances
|
||||||
|
// specifies the maximum number of inotify instances per user. Every Watcher you
|
||||||
|
// create is an "instance", and every path you add is a "watch".
|
||||||
|
//
|
||||||
|
// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
|
||||||
|
// /proc/sys/fs/inotify/max_user_instances
|
||||||
|
//
|
||||||
|
// To increase them you can use sysctl or write the value to the /proc file:
|
||||||
|
//
|
||||||
|
// # Default values on Linux 5.18
|
||||||
|
// sysctl fs.inotify.max_user_watches=124983
|
||||||
|
// sysctl fs.inotify.max_user_instances=128
|
||||||
|
//
|
||||||
|
// To make the changes persist on reboot edit /etc/sysctl.conf or
|
||||||
|
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
|
||||||
|
// your distro's documentation):
|
||||||
|
//
|
||||||
|
// fs.inotify.max_user_watches=124983
|
||||||
|
// fs.inotify.max_user_instances=128
|
||||||
|
//
|
||||||
|
// Reaching the limit will result in a "no space left on device" or "too many open
|
||||||
|
// files" error.
|
||||||
|
//
|
||||||
|
// # kqueue notes (macOS, BSD)
|
||||||
|
//
|
||||||
|
// kqueue requires opening a file descriptor for every file that's being watched;
|
||||||
|
// so if you're watching a directory with five files then that's six file
|
||||||
|
// descriptors. You will run in to your system's "max open files" limit faster on
|
||||||
|
// these platforms.
|
||||||
|
//
|
||||||
|
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
|
||||||
|
// control the maximum number of open files, as well as /etc/login.conf on BSD
|
||||||
|
// systems.
|
||||||
|
//
|
||||||
|
// # macOS notes
|
||||||
|
//
|
||||||
|
// Spotlight indexing on macOS can result in multiple events (see [#15]). A
|
||||||
|
// temporary workaround is to add your folder(s) to the "Spotlight Privacy
|
||||||
|
// Settings" until we have a native FSEvents implementation (see [#11]).
|
||||||
|
//
|
||||||
|
// [#11]: https://github.com/fsnotify/fsnotify/issues/11
|
||||||
|
// [#15]: https://github.com/fsnotify/fsnotify/issues/15
|
||||||
|
type Watcher struct {
|
||||||
|
// Events sends the filesystem change events.
|
||||||
|
//
|
||||||
|
// fsnotify can send the following events; a "path" here can refer to a
|
||||||
|
// file, directory, symbolic link, or special file like a FIFO.
|
||||||
|
//
|
||||||
|
// fsnotify.Create A new path was created; this may be followed by one
|
||||||
|
// or more Write events if data also gets written to a
|
||||||
|
// file.
|
||||||
|
//
|
||||||
|
// fsnotify.Remove A path was removed.
|
||||||
|
//
|
||||||
|
// fsnotify.Rename A path was renamed. A rename is always sent with the
|
||||||
|
// old path as Event.Name, and a Create event will be
|
||||||
|
// sent with the new name. Renames are only sent for
|
||||||
|
// paths that are currently watched; e.g. moving an
|
||||||
|
// unmonitored file into a monitored directory will
|
||||||
|
// show up as just a Create. Similarly, renaming a file
|
||||||
|
// to outside a monitored directory will show up as
|
||||||
|
// only a Rename.
|
||||||
|
//
|
||||||
|
// fsnotify.Write A file or named pipe was written to. A Truncate will
|
||||||
|
// also trigger a Write. A single "write action"
|
||||||
|
// initiated by the user may show up as one or multiple
|
||||||
|
// writes, depending on when the system syncs things to
|
||||||
|
// disk. For example when compiling a large Go program
|
||||||
|
// you may get hundreds of Write events, so you
|
||||||
|
// probably want to wait until you've stopped receiving
|
||||||
|
// them (see the dedup example in cmd/fsnotify).
|
||||||
|
//
|
||||||
|
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
|
||||||
|
// when a file is removed (or more accurately, when a
|
||||||
|
// link to an inode is removed). On kqueue it's sent
|
||||||
|
// and on kqueue when a file is truncated. On Windows
|
||||||
|
// it's never sent.
|
||||||
|
Events chan Event
|
||||||
|
|
||||||
|
// Errors sends any errors.
|
||||||
|
Errors chan error
|
||||||
|
|
||||||
|
done chan struct{}
|
||||||
|
kq int // File descriptor (as returned by the kqueue() syscall).
|
||||||
|
closepipe [2]int // Pipe used for closing.
|
||||||
|
mu sync.Mutex // Protects access to watcher data
|
||||||
|
watches map[string]int // Watched file descriptors (key: path).
|
||||||
|
watchesByDir map[string]map[int]struct{} // Watched file descriptors indexed by the parent directory (key: dirname(path)).
|
||||||
|
userWatches map[string]struct{} // Watches added with Watcher.Add()
|
||||||
|
dirFlags map[string]uint32 // Watched directories to fflags used in kqueue.
|
||||||
|
paths map[int]pathInfo // File descriptors to path names for processing kqueue events.
|
||||||
|
fileExists map[string]struct{} // Keep track of if we know this file exists (to stop duplicate create events).
|
||||||
|
isClosed bool // Set to true when Close() is first called
|
||||||
|
}
|
||||||
|
|
||||||
|
type pathInfo struct {
|
||||||
|
name string
|
||||||
|
isDir bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWatcher creates a new Watcher.
|
||||||
|
func NewWatcher() (*Watcher, error) {
|
||||||
|
kq, closepipe, err := newKqueue()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
w := &Watcher{
|
||||||
|
kq: kq,
|
||||||
|
closepipe: closepipe,
|
||||||
|
watches: make(map[string]int),
|
||||||
|
watchesByDir: make(map[string]map[int]struct{}),
|
||||||
|
dirFlags: make(map[string]uint32),
|
||||||
|
paths: make(map[int]pathInfo),
|
||||||
|
fileExists: make(map[string]struct{}),
|
||||||
|
userWatches: make(map[string]struct{}),
|
||||||
|
Events: make(chan Event),
|
||||||
|
Errors: make(chan error),
|
||||||
|
done: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
go w.readEvents()
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newKqueue creates a new kernel event queue and returns a descriptor.
|
||||||
|
//
|
||||||
|
// This registers a new event on closepipe, which will trigger an event when
|
||||||
|
// it's closed. This way we can use kevent() without timeout/polling; without
|
||||||
|
// the closepipe, it would block forever and we wouldn't be able to stop it at
|
||||||
|
// all.
|
||||||
|
func newKqueue() (kq int, closepipe [2]int, err error) {
|
||||||
|
kq, err = unix.Kqueue()
|
||||||
|
if kq == -1 {
|
||||||
|
return kq, closepipe, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the close pipe.
|
||||||
|
err = unix.Pipe(closepipe[:])
|
||||||
|
if err != nil {
|
||||||
|
unix.Close(kq)
|
||||||
|
return kq, closepipe, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register changes to listen on the closepipe.
|
||||||
|
changes := make([]unix.Kevent_t, 1)
|
||||||
|
// SetKevent converts int to the platform-specific types.
|
||||||
|
unix.SetKevent(&changes[0], closepipe[0], unix.EVFILT_READ,
|
||||||
|
unix.EV_ADD|unix.EV_ENABLE|unix.EV_ONESHOT)
|
||||||
|
|
||||||
|
ok, err := unix.Kevent(kq, changes, nil, nil)
|
||||||
|
if ok == -1 {
|
||||||
|
unix.Close(kq)
|
||||||
|
unix.Close(closepipe[0])
|
||||||
|
unix.Close(closepipe[1])
|
||||||
|
return kq, closepipe, err
|
||||||
|
}
|
||||||
|
return kq, closepipe, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the event was sent, or false if watcher is closed.
|
||||||
|
func (w *Watcher) sendEvent(e Event) bool {
|
||||||
|
select {
|
||||||
|
case w.Events <- e:
|
||||||
|
return true
|
||||||
|
case <-w.done:
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the error was sent, or false if watcher is closed.
|
||||||
|
func (w *Watcher) sendError(err error) bool {
|
||||||
|
select {
|
||||||
|
case w.Errors <- err:
|
||||||
|
return true
|
||||||
|
case <-w.done:
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close removes all watches and closes the events channel.
|
||||||
|
func (w *Watcher) Close() error {
|
||||||
|
w.mu.Lock()
|
||||||
|
if w.isClosed {
|
||||||
|
w.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
w.isClosed = true
|
||||||
|
|
||||||
|
// copy paths to remove while locked
|
||||||
|
pathsToRemove := make([]string, 0, len(w.watches))
|
||||||
|
for name := range w.watches {
|
||||||
|
pathsToRemove = append(pathsToRemove, name)
|
||||||
|
}
|
||||||
|
w.mu.Unlock() // Unlock before calling Remove, which also locks
|
||||||
|
for _, name := range pathsToRemove {
|
||||||
|
w.Remove(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send "quit" message to the reader goroutine.
|
||||||
|
unix.Close(w.closepipe[1])
|
||||||
|
close(w.done)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add starts monitoring the path for changes.
|
||||||
|
//
|
||||||
|
// A path can only be watched once; attempting to watch it more than once will
|
||||||
|
// return an error. Paths that do not yet exist on the filesystem cannot be
|
||||||
|
// added. A watch will be automatically removed if the path is deleted.
|
||||||
|
//
|
||||||
|
// A path will remain watched if it gets renamed to somewhere else on the same
|
||||||
|
// filesystem, but the monitor will get removed if the path gets deleted and
|
||||||
|
// re-created, or if it's moved to a different filesystem.
|
||||||
|
//
|
||||||
|
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
|
||||||
|
// filesystems (/proc, /sys, etc.) generally don't work.
|
||||||
|
//
|
||||||
|
// # Watching directories
|
||||||
|
//
|
||||||
|
// All files in a directory are monitored, including new files that are created
|
||||||
|
// after the watcher is started. Subdirectories are not watched (i.e. it's
|
||||||
|
// non-recursive).
|
||||||
|
//
|
||||||
|
// # Watching files
|
||||||
|
//
|
||||||
|
// Watching individual files (rather than directories) is generally not
|
||||||
|
// recommended as many tools update files atomically. Instead of "just" writing
|
||||||
|
// to the file a temporary file will be written to first, and if successful the
|
||||||
|
// temporary file is moved to to destination removing the original, or some
|
||||||
|
// variant thereof. The watcher on the original file is now lost, as it no
|
||||||
|
// longer exists.
|
||||||
|
//
|
||||||
|
// Instead, watch the parent directory and use Event.Name to filter out files
|
||||||
|
// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
|
||||||
|
func (w *Watcher) Add(name string) error {
|
||||||
|
w.mu.Lock()
|
||||||
|
w.userWatches[name] = struct{}{}
|
||||||
|
w.mu.Unlock()
|
||||||
|
_, err := w.addWatch(name, noteAllEvents)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove stops monitoring the path for changes.
|
||||||
|
//
|
||||||
|
// Directories are always removed non-recursively. For example, if you added
|
||||||
|
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
|
||||||
|
//
|
||||||
|
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
|
||||||
|
func (w *Watcher) Remove(name string) error {
|
||||||
|
name = filepath.Clean(name)
|
||||||
|
w.mu.Lock()
|
||||||
|
watchfd, ok := w.watches[name]
|
||||||
|
w.mu.Unlock()
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%w: %s", ErrNonExistentWatch, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := w.register([]int{watchfd}, unix.EV_DELETE, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
unix.Close(watchfd)
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
isDir := w.paths[watchfd].isDir
|
||||||
|
delete(w.watches, name)
|
||||||
|
delete(w.userWatches, name)
|
||||||
|
|
||||||
|
parentName := filepath.Dir(name)
|
||||||
|
delete(w.watchesByDir[parentName], watchfd)
|
||||||
|
|
||||||
|
if len(w.watchesByDir[parentName]) == 0 {
|
||||||
|
delete(w.watchesByDir, parentName)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(w.paths, watchfd)
|
||||||
|
delete(w.dirFlags, name)
|
||||||
|
delete(w.fileExists, name)
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
// Find all watched paths that are in this directory that are not external.
|
||||||
|
if isDir {
|
||||||
|
var pathsToRemove []string
|
||||||
|
w.mu.Lock()
|
||||||
|
for fd := range w.watchesByDir[name] {
|
||||||
|
path := w.paths[fd]
|
||||||
|
if _, ok := w.userWatches[path.name]; !ok {
|
||||||
|
pathsToRemove = append(pathsToRemove, path.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.mu.Unlock()
|
||||||
|
for _, name := range pathsToRemove {
|
||||||
|
// Since these are internal, not much sense in propagating error
|
||||||
|
// to the user, as that will just confuse them with an error about
|
||||||
|
// a path they did not explicitly watch themselves.
|
||||||
|
w.Remove(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WatchList returns all paths added with [Add] (and are not yet removed).
|
||||||
|
func (w *Watcher) WatchList() []string {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
entries := make([]string, 0, len(w.userWatches))
|
||||||
|
for pathname := range w.userWatches {
|
||||||
|
entries = append(entries, pathname)
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE)
|
||||||
|
const noteAllEvents = unix.NOTE_DELETE | unix.NOTE_WRITE | unix.NOTE_ATTRIB | unix.NOTE_RENAME
|
||||||
|
|
||||||
|
// addWatch adds name to the watched file set.
|
||||||
|
// The flags are interpreted as described in kevent(2).
|
||||||
|
// Returns the real path to the file which was added, if any, which may be different from the one passed in the case of symlinks.
|
||||||
|
func (w *Watcher) addWatch(name string, flags uint32) (string, error) {
|
||||||
|
var isDir bool
|
||||||
|
// Make ./name and name equivalent
|
||||||
|
name = filepath.Clean(name)
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
if w.isClosed {
|
||||||
|
w.mu.Unlock()
|
||||||
|
return "", errors.New("kevent instance already closed")
|
||||||
|
}
|
||||||
|
watchfd, alreadyWatching := w.watches[name]
|
||||||
|
// We already have a watch, but we can still override flags.
|
||||||
|
if alreadyWatching {
|
||||||
|
isDir = w.paths[watchfd].isDir
|
||||||
|
}
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
if !alreadyWatching {
|
||||||
|
fi, err := os.Lstat(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't watch sockets or named pipes
|
||||||
|
if (fi.Mode()&os.ModeSocket == os.ModeSocket) || (fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe) {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Follow Symlinks
|
||||||
|
//
|
||||||
|
// Linux can add unresolvable symlinks to the watch list without issue,
|
||||||
|
// and Windows can't do symlinks period. To maintain consistency, we
|
||||||
|
// will act like everything is fine if the link can't be resolved.
|
||||||
|
// There will simply be no file events for broken symlinks. Hence the
|
||||||
|
// returns of nil on errors.
|
||||||
|
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||||
|
name, err = filepath.EvalSymlinks(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
_, alreadyWatching = w.watches[name]
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
if alreadyWatching {
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err = os.Lstat(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retry on EINTR; open() can return EINTR in practice on macOS.
|
||||||
|
// See #354, and go issues 11180 and 39237.
|
||||||
|
for {
|
||||||
|
watchfd, err = unix.Open(name, openMode, 0)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if errors.Is(err, unix.EINTR) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
isDir = fi.IsDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
err := w.register([]int{watchfd}, unix.EV_ADD|unix.EV_CLEAR|unix.EV_ENABLE, flags)
|
||||||
|
if err != nil {
|
||||||
|
unix.Close(watchfd)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !alreadyWatching {
|
||||||
|
w.mu.Lock()
|
||||||
|
parentName := filepath.Dir(name)
|
||||||
|
w.watches[name] = watchfd
|
||||||
|
|
||||||
|
watchesByDir, ok := w.watchesByDir[parentName]
|
||||||
|
if !ok {
|
||||||
|
watchesByDir = make(map[int]struct{}, 1)
|
||||||
|
w.watchesByDir[parentName] = watchesByDir
|
||||||
|
}
|
||||||
|
watchesByDir[watchfd] = struct{}{}
|
||||||
|
|
||||||
|
w.paths[watchfd] = pathInfo{name: name, isDir: isDir}
|
||||||
|
w.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
if isDir {
|
||||||
|
// Watch the directory if it has not been watched before,
|
||||||
|
// or if it was watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles)
|
||||||
|
w.mu.Lock()
|
||||||
|
|
||||||
|
watchDir := (flags&unix.NOTE_WRITE) == unix.NOTE_WRITE &&
|
||||||
|
(!alreadyWatching || (w.dirFlags[name]&unix.NOTE_WRITE) != unix.NOTE_WRITE)
|
||||||
|
// Store flags so this watch can be updated later
|
||||||
|
w.dirFlags[name] = flags
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
if watchDir {
|
||||||
|
if err := w.watchDirectoryFiles(name); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readEvents reads from kqueue and converts the received kevents into
|
||||||
|
// Event values that it sends down the Events channel.
|
||||||
|
func (w *Watcher) readEvents() {
|
||||||
|
defer func() {
|
||||||
|
err := unix.Close(w.kq)
|
||||||
|
if err != nil {
|
||||||
|
w.Errors <- err
|
||||||
|
}
|
||||||
|
unix.Close(w.closepipe[0])
|
||||||
|
close(w.Events)
|
||||||
|
close(w.Errors)
|
||||||
|
}()
|
||||||
|
|
||||||
|
eventBuffer := make([]unix.Kevent_t, 10)
|
||||||
|
for closed := false; !closed; {
|
||||||
|
kevents, err := w.read(eventBuffer)
|
||||||
|
// EINTR is okay, the syscall was interrupted before timeout expired.
|
||||||
|
if err != nil && err != unix.EINTR {
|
||||||
|
if !w.sendError(fmt.Errorf("fsnotify.readEvents: %w", err)) {
|
||||||
|
closed = true
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush the events we received to the Events channel
|
||||||
|
for _, kevent := range kevents {
|
||||||
|
var (
|
||||||
|
watchfd = int(kevent.Ident)
|
||||||
|
mask = uint32(kevent.Fflags)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Shut down the loop when the pipe is closed, but only after all
|
||||||
|
// other events have been processed.
|
||||||
|
if watchfd == w.closepipe[0] {
|
||||||
|
closed = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
path := w.paths[watchfd]
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
event := w.newEvent(path.name, mask)
|
||||||
|
|
||||||
|
if path.isDir && !event.Has(Remove) {
|
||||||
|
// Double check to make sure the directory exists. This can
|
||||||
|
// happen when we do a rm -fr on a recursively watched folders
|
||||||
|
// and we receive a modification event first but the folder has
|
||||||
|
// been deleted and later receive the delete event.
|
||||||
|
if _, err := os.Lstat(event.Name); os.IsNotExist(err) {
|
||||||
|
event.Op |= Remove
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.Has(Rename) || event.Has(Remove) {
|
||||||
|
w.Remove(event.Name)
|
||||||
|
w.mu.Lock()
|
||||||
|
delete(w.fileExists, event.Name)
|
||||||
|
w.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
if path.isDir && event.Has(Write) && !event.Has(Remove) {
|
||||||
|
w.sendDirectoryChangeEvents(event.Name)
|
||||||
|
} else {
|
||||||
|
if !w.sendEvent(event) {
|
||||||
|
closed = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.Has(Remove) {
|
||||||
|
// Look for a file that may have overwritten this.
|
||||||
|
// For example, mv f1 f2 will delete f2, then create f2.
|
||||||
|
if path.isDir {
|
||||||
|
fileDir := filepath.Clean(event.Name)
|
||||||
|
w.mu.Lock()
|
||||||
|
_, found := w.watches[fileDir]
|
||||||
|
w.mu.Unlock()
|
||||||
|
if found {
|
||||||
|
// make sure the directory exists before we watch for changes. When we
|
||||||
|
// do a recursive watch and perform rm -fr, the parent directory might
|
||||||
|
// have gone missing, ignore the missing directory and let the
|
||||||
|
// upcoming delete event remove the watch from the parent directory.
|
||||||
|
if _, err := os.Lstat(fileDir); err == nil {
|
||||||
|
w.sendDirectoryChangeEvents(fileDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
filePath := filepath.Clean(event.Name)
|
||||||
|
if fileInfo, err := os.Lstat(filePath); err == nil {
|
||||||
|
w.sendFileCreatedEventIfNew(filePath, fileInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// newEvent returns an platform-independent Event based on kqueue Fflags.
|
||||||
|
func (w *Watcher) newEvent(name string, mask uint32) Event {
|
||||||
|
e := Event{Name: name}
|
||||||
|
if mask&unix.NOTE_DELETE == unix.NOTE_DELETE {
|
||||||
|
e.Op |= Remove
|
||||||
|
}
|
||||||
|
if mask&unix.NOTE_WRITE == unix.NOTE_WRITE {
|
||||||
|
e.Op |= Write
|
||||||
|
}
|
||||||
|
if mask&unix.NOTE_RENAME == unix.NOTE_RENAME {
|
||||||
|
e.Op |= Rename
|
||||||
|
}
|
||||||
|
if mask&unix.NOTE_ATTRIB == unix.NOTE_ATTRIB {
|
||||||
|
e.Op |= Chmod
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// watchDirectoryFiles to mimic inotify when adding a watch on a directory
|
||||||
|
func (w *Watcher) watchDirectoryFiles(dirPath string) error {
|
||||||
|
// Get all files
|
||||||
|
files, err := ioutil.ReadDir(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fileInfo := range files {
|
||||||
|
path := filepath.Join(dirPath, fileInfo.Name())
|
||||||
|
|
||||||
|
cleanPath, err := w.internalWatch(path, fileInfo)
|
||||||
|
if err != nil {
|
||||||
|
// No permission to read the file; that's not a problem: just skip.
|
||||||
|
// But do add it to w.fileExists to prevent it from being picked up
|
||||||
|
// as a "new" file later (it still shows up in the directory
|
||||||
|
// listing).
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, unix.EACCES) || errors.Is(err, unix.EPERM):
|
||||||
|
cleanPath = filepath.Clean(path)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("%q: %w", filepath.Join(dirPath, fileInfo.Name()), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
w.fileExists[cleanPath] = struct{}{}
|
||||||
|
w.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search the directory for new files and send an event for them.
|
||||||
|
//
|
||||||
|
// This functionality is to have the BSD watcher match the inotify, which sends
|
||||||
|
// a create event for files created in a watched directory.
|
||||||
|
func (w *Watcher) sendDirectoryChangeEvents(dir string) {
|
||||||
|
// Get all files
|
||||||
|
files, err := ioutil.ReadDir(dir)
|
||||||
|
if err != nil {
|
||||||
|
if !w.sendError(fmt.Errorf("fsnotify.sendDirectoryChangeEvents: %w", err)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for new files
|
||||||
|
for _, fi := range files {
|
||||||
|
err := w.sendFileCreatedEventIfNew(filepath.Join(dir, fi.Name()), fi)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendFileCreatedEvent sends a create event if the file isn't already being tracked.
|
||||||
|
func (w *Watcher) sendFileCreatedEventIfNew(filePath string, fileInfo os.FileInfo) (err error) {
|
||||||
|
w.mu.Lock()
|
||||||
|
_, doesExist := w.fileExists[filePath]
|
||||||
|
w.mu.Unlock()
|
||||||
|
if !doesExist {
|
||||||
|
if !w.sendEvent(Event{Name: filePath, Op: Create}) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// like watchDirectoryFiles (but without doing another ReadDir)
|
||||||
|
filePath, err = w.internalWatch(filePath, fileInfo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
w.fileExists[filePath] = struct{}{}
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Watcher) internalWatch(name string, fileInfo os.FileInfo) (string, error) {
|
||||||
|
if fileInfo.IsDir() {
|
||||||
|
// mimic Linux providing delete events for subdirectories
|
||||||
|
// but preserve the flags used if currently watching subdirectory
|
||||||
|
w.mu.Lock()
|
||||||
|
flags := w.dirFlags[name]
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
flags |= unix.NOTE_DELETE | unix.NOTE_RENAME
|
||||||
|
return w.addWatch(name, flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
// watch file to mimic Linux inotify
|
||||||
|
return w.addWatch(name, noteAllEvents)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register events with the queue.
|
||||||
|
func (w *Watcher) register(fds []int, flags int, fflags uint32) error {
|
||||||
|
changes := make([]unix.Kevent_t, len(fds))
|
||||||
|
for i, fd := range fds {
|
||||||
|
// SetKevent converts int to the platform-specific types.
|
||||||
|
unix.SetKevent(&changes[i], fd, unix.EVFILT_VNODE, flags)
|
||||||
|
changes[i].Fflags = fflags
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the events.
|
||||||
|
success, err := unix.Kevent(w.kq, changes, nil, nil)
|
||||||
|
if success == -1 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// read retrieves pending events, or waits until an event occurs.
|
||||||
|
func (w *Watcher) read(events []unix.Kevent_t) ([]unix.Kevent_t, error) {
|
||||||
|
n, err := unix.Kevent(w.kq, nil, events, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return events[0:n], nil
|
||||||
|
}
|
66
vendor/github.com/fsnotify/fsnotify/backend_other.go
generated
vendored
Normal file
66
vendor/github.com/fsnotify/fsnotify/backend_other.go
generated
vendored
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
//go:build !darwin && !dragonfly && !freebsd && !openbsd && !linux && !netbsd && !solaris && !windows
|
||||||
|
// +build !darwin,!dragonfly,!freebsd,!openbsd,!linux,!netbsd,!solaris,!windows
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Watcher watches a set of files, delivering events to a channel.
|
||||||
|
type Watcher struct{}
|
||||||
|
|
||||||
|
// NewWatcher creates a new Watcher.
|
||||||
|
func NewWatcher() (*Watcher, error) {
|
||||||
|
return nil, fmt.Errorf("fsnotify not supported on %s", runtime.GOOS)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close removes all watches and closes the events channel.
|
||||||
|
func (w *Watcher) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add starts monitoring the path for changes.
|
||||||
|
//
|
||||||
|
// A path can only be watched once; attempting to watch it more than once will
|
||||||
|
// return an error. Paths that do not yet exist on the filesystem cannot be
|
||||||
|
// added. A watch will be automatically removed if the path is deleted.
|
||||||
|
//
|
||||||
|
// A path will remain watched if it gets renamed to somewhere else on the same
|
||||||
|
// filesystem, but the monitor will get removed if the path gets deleted and
|
||||||
|
// re-created, or if it's moved to a different filesystem.
|
||||||
|
//
|
||||||
|
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
|
||||||
|
// filesystems (/proc, /sys, etc.) generally don't work.
|
||||||
|
//
|
||||||
|
// # Watching directories
|
||||||
|
//
|
||||||
|
// All files in a directory are monitored, including new files that are created
|
||||||
|
// after the watcher is started. Subdirectories are not watched (i.e. it's
|
||||||
|
// non-recursive).
|
||||||
|
//
|
||||||
|
// # Watching files
|
||||||
|
//
|
||||||
|
// Watching individual files (rather than directories) is generally not
|
||||||
|
// recommended as many tools update files atomically. Instead of "just" writing
|
||||||
|
// to the file a temporary file will be written to first, and if successful the
|
||||||
|
// temporary file is moved to to destination removing the original, or some
|
||||||
|
// variant thereof. The watcher on the original file is now lost, as it no
|
||||||
|
// longer exists.
|
||||||
|
//
|
||||||
|
// Instead, watch the parent directory and use Event.Name to filter out files
|
||||||
|
// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
|
||||||
|
func (w *Watcher) Add(name string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove stops monitoring the path for changes.
|
||||||
|
//
|
||||||
|
// Directories are always removed non-recursively. For example, if you added
|
||||||
|
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
|
||||||
|
//
|
||||||
|
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
|
||||||
|
func (w *Watcher) Remove(name string) error {
|
||||||
|
return nil
|
||||||
|
}
|
746
vendor/github.com/fsnotify/fsnotify/backend_windows.go
generated
vendored
Normal file
746
vendor/github.com/fsnotify/fsnotify/backend_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,746 @@
|
||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Watcher watches a set of paths, delivering events on a channel.
|
||||||
|
//
|
||||||
|
// A watcher should not be copied (e.g. pass it by pointer, rather than by
|
||||||
|
// value).
|
||||||
|
//
|
||||||
|
// # Linux notes
|
||||||
|
//
|
||||||
|
// When a file is removed a Remove event won't be emitted until all file
|
||||||
|
// descriptors are closed, and deletes will always emit a Chmod. For example:
|
||||||
|
//
|
||||||
|
// fp := os.Open("file")
|
||||||
|
// os.Remove("file") // Triggers Chmod
|
||||||
|
// fp.Close() // Triggers Remove
|
||||||
|
//
|
||||||
|
// This is the event that inotify sends, so not much can be changed about this.
|
||||||
|
//
|
||||||
|
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
|
||||||
|
// for the number of watches per user, and fs.inotify.max_user_instances
|
||||||
|
// specifies the maximum number of inotify instances per user. Every Watcher you
|
||||||
|
// create is an "instance", and every path you add is a "watch".
|
||||||
|
//
|
||||||
|
// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
|
||||||
|
// /proc/sys/fs/inotify/max_user_instances
|
||||||
|
//
|
||||||
|
// To increase them you can use sysctl or write the value to the /proc file:
|
||||||
|
//
|
||||||
|
// # Default values on Linux 5.18
|
||||||
|
// sysctl fs.inotify.max_user_watches=124983
|
||||||
|
// sysctl fs.inotify.max_user_instances=128
|
||||||
|
//
|
||||||
|
// To make the changes persist on reboot edit /etc/sysctl.conf or
|
||||||
|
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
|
||||||
|
// your distro's documentation):
|
||||||
|
//
|
||||||
|
// fs.inotify.max_user_watches=124983
|
||||||
|
// fs.inotify.max_user_instances=128
|
||||||
|
//
|
||||||
|
// Reaching the limit will result in a "no space left on device" or "too many open
|
||||||
|
// files" error.
|
||||||
|
//
|
||||||
|
// # kqueue notes (macOS, BSD)
|
||||||
|
//
|
||||||
|
// kqueue requires opening a file descriptor for every file that's being watched;
|
||||||
|
// so if you're watching a directory with five files then that's six file
|
||||||
|
// descriptors. You will run in to your system's "max open files" limit faster on
|
||||||
|
// these platforms.
|
||||||
|
//
|
||||||
|
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
|
||||||
|
// control the maximum number of open files, as well as /etc/login.conf on BSD
|
||||||
|
// systems.
|
||||||
|
//
|
||||||
|
// # macOS notes
|
||||||
|
//
|
||||||
|
// Spotlight indexing on macOS can result in multiple events (see [#15]). A
|
||||||
|
// temporary workaround is to add your folder(s) to the "Spotlight Privacy
|
||||||
|
// Settings" until we have a native FSEvents implementation (see [#11]).
|
||||||
|
//
|
||||||
|
// [#11]: https://github.com/fsnotify/fsnotify/issues/11
|
||||||
|
// [#15]: https://github.com/fsnotify/fsnotify/issues/15
|
||||||
|
type Watcher struct {
|
||||||
|
// Events sends the filesystem change events.
|
||||||
|
//
|
||||||
|
// fsnotify can send the following events; a "path" here can refer to a
|
||||||
|
// file, directory, symbolic link, or special file like a FIFO.
|
||||||
|
//
|
||||||
|
// fsnotify.Create A new path was created; this may be followed by one
|
||||||
|
// or more Write events if data also gets written to a
|
||||||
|
// file.
|
||||||
|
//
|
||||||
|
// fsnotify.Remove A path was removed.
|
||||||
|
//
|
||||||
|
// fsnotify.Rename A path was renamed. A rename is always sent with the
|
||||||
|
// old path as Event.Name, and a Create event will be
|
||||||
|
// sent with the new name. Renames are only sent for
|
||||||
|
// paths that are currently watched; e.g. moving an
|
||||||
|
// unmonitored file into a monitored directory will
|
||||||
|
// show up as just a Create. Similarly, renaming a file
|
||||||
|
// to outside a monitored directory will show up as
|
||||||
|
// only a Rename.
|
||||||
|
//
|
||||||
|
// fsnotify.Write A file or named pipe was written to. A Truncate will
|
||||||
|
// also trigger a Write. A single "write action"
|
||||||
|
// initiated by the user may show up as one or multiple
|
||||||
|
// writes, depending on when the system syncs things to
|
||||||
|
// disk. For example when compiling a large Go program
|
||||||
|
// you may get hundreds of Write events, so you
|
||||||
|
// probably want to wait until you've stopped receiving
|
||||||
|
// them (see the dedup example in cmd/fsnotify).
|
||||||
|
//
|
||||||
|
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
|
||||||
|
// when a file is removed (or more accurately, when a
|
||||||
|
// link to an inode is removed). On kqueue it's sent
|
||||||
|
// and on kqueue when a file is truncated. On Windows
|
||||||
|
// it's never sent.
|
||||||
|
Events chan Event
|
||||||
|
|
||||||
|
// Errors sends any errors.
|
||||||
|
Errors chan error
|
||||||
|
|
||||||
|
port windows.Handle // Handle to completion port
|
||||||
|
input chan *input // Inputs to the reader are sent on this channel
|
||||||
|
quit chan chan<- error
|
||||||
|
|
||||||
|
mu sync.Mutex // Protects access to watches, isClosed
|
||||||
|
watches watchMap // Map of watches (key: i-number)
|
||||||
|
isClosed bool // Set to true when Close() is first called
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWatcher creates a new Watcher.
|
||||||
|
func NewWatcher() (*Watcher, error) {
|
||||||
|
port, err := windows.CreateIoCompletionPort(windows.InvalidHandle, 0, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, os.NewSyscallError("CreateIoCompletionPort", err)
|
||||||
|
}
|
||||||
|
w := &Watcher{
|
||||||
|
port: port,
|
||||||
|
watches: make(watchMap),
|
||||||
|
input: make(chan *input, 1),
|
||||||
|
Events: make(chan Event, 50),
|
||||||
|
Errors: make(chan error),
|
||||||
|
quit: make(chan chan<- error, 1),
|
||||||
|
}
|
||||||
|
go w.readEvents()
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Watcher) sendEvent(name string, mask uint64) bool {
|
||||||
|
if mask == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
event := w.newEvent(name, uint32(mask))
|
||||||
|
select {
|
||||||
|
case ch := <-w.quit:
|
||||||
|
w.quit <- ch
|
||||||
|
case w.Events <- event:
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the error was sent, or false if watcher is closed.
|
||||||
|
func (w *Watcher) sendError(err error) bool {
|
||||||
|
select {
|
||||||
|
case w.Errors <- err:
|
||||||
|
return true
|
||||||
|
case <-w.quit:
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close removes all watches and closes the events channel.
|
||||||
|
func (w *Watcher) Close() error {
|
||||||
|
w.mu.Lock()
|
||||||
|
if w.isClosed {
|
||||||
|
w.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
w.isClosed = true
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
// Send "quit" message to the reader goroutine
|
||||||
|
ch := make(chan error)
|
||||||
|
w.quit <- ch
|
||||||
|
if err := w.wakeupReader(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return <-ch
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add starts monitoring the path for changes.
|
||||||
|
//
|
||||||
|
// A path can only be watched once; attempting to watch it more than once will
|
||||||
|
// return an error. Paths that do not yet exist on the filesystem cannot be
|
||||||
|
// added. A watch will be automatically removed if the path is deleted.
|
||||||
|
//
|
||||||
|
// A path will remain watched if it gets renamed to somewhere else on the same
|
||||||
|
// filesystem, but the monitor will get removed if the path gets deleted and
|
||||||
|
// re-created, or if it's moved to a different filesystem.
|
||||||
|
//
|
||||||
|
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
|
||||||
|
// filesystems (/proc, /sys, etc.) generally don't work.
|
||||||
|
//
|
||||||
|
// # Watching directories
|
||||||
|
//
|
||||||
|
// All files in a directory are monitored, including new files that are created
|
||||||
|
// after the watcher is started. Subdirectories are not watched (i.e. it's
|
||||||
|
// non-recursive).
|
||||||
|
//
|
||||||
|
// # Watching files
|
||||||
|
//
|
||||||
|
// Watching individual files (rather than directories) is generally not
|
||||||
|
// recommended as many tools update files atomically. Instead of "just" writing
|
||||||
|
// to the file a temporary file will be written to first, and if successful the
|
||||||
|
// temporary file is moved to to destination removing the original, or some
|
||||||
|
// variant thereof. The watcher on the original file is now lost, as it no
|
||||||
|
// longer exists.
|
||||||
|
//
|
||||||
|
// Instead, watch the parent directory and use Event.Name to filter out files
|
||||||
|
// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
|
||||||
|
func (w *Watcher) Add(name string) error {
|
||||||
|
w.mu.Lock()
|
||||||
|
if w.isClosed {
|
||||||
|
w.mu.Unlock()
|
||||||
|
return errors.New("watcher already closed")
|
||||||
|
}
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
in := &input{
|
||||||
|
op: opAddWatch,
|
||||||
|
path: filepath.Clean(name),
|
||||||
|
flags: sysFSALLEVENTS,
|
||||||
|
reply: make(chan error),
|
||||||
|
}
|
||||||
|
w.input <- in
|
||||||
|
if err := w.wakeupReader(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return <-in.reply
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove stops monitoring the path for changes.
|
||||||
|
//
|
||||||
|
// Directories are always removed non-recursively. For example, if you added
|
||||||
|
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
|
||||||
|
//
|
||||||
|
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
|
||||||
|
func (w *Watcher) Remove(name string) error {
|
||||||
|
in := &input{
|
||||||
|
op: opRemoveWatch,
|
||||||
|
path: filepath.Clean(name),
|
||||||
|
reply: make(chan error),
|
||||||
|
}
|
||||||
|
w.input <- in
|
||||||
|
if err := w.wakeupReader(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return <-in.reply
|
||||||
|
}
|
||||||
|
|
||||||
|
// WatchList returns all paths added with [Add] (and are not yet removed).
|
||||||
|
func (w *Watcher) WatchList() []string {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
entries := make([]string, 0, len(w.watches))
|
||||||
|
for _, entry := range w.watches {
|
||||||
|
for _, watchEntry := range entry {
|
||||||
|
entries = append(entries, watchEntry.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
// These options are from the old golang.org/x/exp/winfsnotify, where you could
|
||||||
|
// add various options to the watch. This has long since been removed.
|
||||||
|
//
|
||||||
|
// The "sys" in the name is misleading as they're not part of any "system".
|
||||||
|
//
|
||||||
|
// This should all be removed at some point, and just use windows.FILE_NOTIFY_*
|
||||||
|
const (
|
||||||
|
sysFSALLEVENTS = 0xfff
|
||||||
|
sysFSATTRIB = 0x4
|
||||||
|
sysFSCREATE = 0x100
|
||||||
|
sysFSDELETE = 0x200
|
||||||
|
sysFSDELETESELF = 0x400
|
||||||
|
sysFSMODIFY = 0x2
|
||||||
|
sysFSMOVE = 0xc0
|
||||||
|
sysFSMOVEDFROM = 0x40
|
||||||
|
sysFSMOVEDTO = 0x80
|
||||||
|
sysFSMOVESELF = 0x800
|
||||||
|
sysFSIGNORED = 0x8000
|
||||||
|
)
|
||||||
|
|
||||||
|
func (w *Watcher) newEvent(name string, mask uint32) Event {
|
||||||
|
e := Event{Name: name}
|
||||||
|
if mask&sysFSCREATE == sysFSCREATE || mask&sysFSMOVEDTO == sysFSMOVEDTO {
|
||||||
|
e.Op |= Create
|
||||||
|
}
|
||||||
|
if mask&sysFSDELETE == sysFSDELETE || mask&sysFSDELETESELF == sysFSDELETESELF {
|
||||||
|
e.Op |= Remove
|
||||||
|
}
|
||||||
|
if mask&sysFSMODIFY == sysFSMODIFY {
|
||||||
|
e.Op |= Write
|
||||||
|
}
|
||||||
|
if mask&sysFSMOVE == sysFSMOVE || mask&sysFSMOVESELF == sysFSMOVESELF || mask&sysFSMOVEDFROM == sysFSMOVEDFROM {
|
||||||
|
e.Op |= Rename
|
||||||
|
}
|
||||||
|
if mask&sysFSATTRIB == sysFSATTRIB {
|
||||||
|
e.Op |= Chmod
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
opAddWatch = iota
|
||||||
|
opRemoveWatch
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
provisional uint64 = 1 << (32 + iota)
|
||||||
|
)
|
||||||
|
|
||||||
|
type input struct {
|
||||||
|
op int
|
||||||
|
path string
|
||||||
|
flags uint32
|
||||||
|
reply chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
type inode struct {
|
||||||
|
handle windows.Handle
|
||||||
|
volume uint32
|
||||||
|
index uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type watch struct {
|
||||||
|
ov windows.Overlapped
|
||||||
|
ino *inode // i-number
|
||||||
|
path string // Directory path
|
||||||
|
mask uint64 // Directory itself is being watched with these notify flags
|
||||||
|
names map[string]uint64 // Map of names being watched and their notify flags
|
||||||
|
rename string // Remembers the old name while renaming a file
|
||||||
|
buf [65536]byte // 64K buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
indexMap map[uint64]*watch
|
||||||
|
watchMap map[uint32]indexMap
|
||||||
|
)
|
||||||
|
|
||||||
|
func (w *Watcher) wakeupReader() error {
|
||||||
|
err := windows.PostQueuedCompletionStatus(w.port, 0, 0, nil)
|
||||||
|
if err != nil {
|
||||||
|
return os.NewSyscallError("PostQueuedCompletionStatus", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Watcher) getDir(pathname string) (dir string, err error) {
|
||||||
|
attr, err := windows.GetFileAttributes(windows.StringToUTF16Ptr(pathname))
|
||||||
|
if err != nil {
|
||||||
|
return "", os.NewSyscallError("GetFileAttributes", err)
|
||||||
|
}
|
||||||
|
if attr&windows.FILE_ATTRIBUTE_DIRECTORY != 0 {
|
||||||
|
dir = pathname
|
||||||
|
} else {
|
||||||
|
dir, _ = filepath.Split(pathname)
|
||||||
|
dir = filepath.Clean(dir)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Watcher) getIno(path string) (ino *inode, err error) {
|
||||||
|
h, err := windows.CreateFile(windows.StringToUTF16Ptr(path),
|
||||||
|
windows.FILE_LIST_DIRECTORY,
|
||||||
|
windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE,
|
||||||
|
nil, windows.OPEN_EXISTING,
|
||||||
|
windows.FILE_FLAG_BACKUP_SEMANTICS|windows.FILE_FLAG_OVERLAPPED, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, os.NewSyscallError("CreateFile", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var fi windows.ByHandleFileInformation
|
||||||
|
err = windows.GetFileInformationByHandle(h, &fi)
|
||||||
|
if err != nil {
|
||||||
|
windows.CloseHandle(h)
|
||||||
|
return nil, os.NewSyscallError("GetFileInformationByHandle", err)
|
||||||
|
}
|
||||||
|
ino = &inode{
|
||||||
|
handle: h,
|
||||||
|
volume: fi.VolumeSerialNumber,
|
||||||
|
index: uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow),
|
||||||
|
}
|
||||||
|
return ino, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (m watchMap) get(ino *inode) *watch {
|
||||||
|
if i := m[ino.volume]; i != nil {
|
||||||
|
return i[ino.index]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (m watchMap) set(ino *inode, watch *watch) {
|
||||||
|
i := m[ino.volume]
|
||||||
|
if i == nil {
|
||||||
|
i = make(indexMap)
|
||||||
|
m[ino.volume] = i
|
||||||
|
}
|
||||||
|
i[ino.index] = watch
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (w *Watcher) addWatch(pathname string, flags uint64) error {
|
||||||
|
dir, err := w.getDir(pathname)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ino, err := w.getIno(dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.mu.Lock()
|
||||||
|
watchEntry := w.watches.get(ino)
|
||||||
|
w.mu.Unlock()
|
||||||
|
if watchEntry == nil {
|
||||||
|
_, err := windows.CreateIoCompletionPort(ino.handle, w.port, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
windows.CloseHandle(ino.handle)
|
||||||
|
return os.NewSyscallError("CreateIoCompletionPort", err)
|
||||||
|
}
|
||||||
|
watchEntry = &watch{
|
||||||
|
ino: ino,
|
||||||
|
path: dir,
|
||||||
|
names: make(map[string]uint64),
|
||||||
|
}
|
||||||
|
w.mu.Lock()
|
||||||
|
w.watches.set(ino, watchEntry)
|
||||||
|
w.mu.Unlock()
|
||||||
|
flags |= provisional
|
||||||
|
} else {
|
||||||
|
windows.CloseHandle(ino.handle)
|
||||||
|
}
|
||||||
|
if pathname == dir {
|
||||||
|
watchEntry.mask |= flags
|
||||||
|
} else {
|
||||||
|
watchEntry.names[filepath.Base(pathname)] |= flags
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.startRead(watchEntry)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if pathname == dir {
|
||||||
|
watchEntry.mask &= ^provisional
|
||||||
|
} else {
|
||||||
|
watchEntry.names[filepath.Base(pathname)] &= ^provisional
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (w *Watcher) remWatch(pathname string) error {
|
||||||
|
dir, err := w.getDir(pathname)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ino, err := w.getIno(dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
watch := w.watches.get(ino)
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
err = windows.CloseHandle(ino.handle)
|
||||||
|
if err != nil {
|
||||||
|
w.sendError(os.NewSyscallError("CloseHandle", err))
|
||||||
|
}
|
||||||
|
if watch == nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrNonExistentWatch, pathname)
|
||||||
|
}
|
||||||
|
if pathname == dir {
|
||||||
|
w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
|
||||||
|
watch.mask = 0
|
||||||
|
} else {
|
||||||
|
name := filepath.Base(pathname)
|
||||||
|
w.sendEvent(filepath.Join(watch.path, name), watch.names[name]&sysFSIGNORED)
|
||||||
|
delete(watch.names, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return w.startRead(watch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (w *Watcher) deleteWatch(watch *watch) {
|
||||||
|
for name, mask := range watch.names {
|
||||||
|
if mask&provisional == 0 {
|
||||||
|
w.sendEvent(filepath.Join(watch.path, name), mask&sysFSIGNORED)
|
||||||
|
}
|
||||||
|
delete(watch.names, name)
|
||||||
|
}
|
||||||
|
if watch.mask != 0 {
|
||||||
|
if watch.mask&provisional == 0 {
|
||||||
|
w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
|
||||||
|
}
|
||||||
|
watch.mask = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (w *Watcher) startRead(watch *watch) error {
|
||||||
|
err := windows.CancelIo(watch.ino.handle)
|
||||||
|
if err != nil {
|
||||||
|
w.sendError(os.NewSyscallError("CancelIo", err))
|
||||||
|
w.deleteWatch(watch)
|
||||||
|
}
|
||||||
|
mask := w.toWindowsFlags(watch.mask)
|
||||||
|
for _, m := range watch.names {
|
||||||
|
mask |= w.toWindowsFlags(m)
|
||||||
|
}
|
||||||
|
if mask == 0 {
|
||||||
|
err := windows.CloseHandle(watch.ino.handle)
|
||||||
|
if err != nil {
|
||||||
|
w.sendError(os.NewSyscallError("CloseHandle", err))
|
||||||
|
}
|
||||||
|
w.mu.Lock()
|
||||||
|
delete(w.watches[watch.ino.volume], watch.ino.index)
|
||||||
|
w.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rdErr := windows.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0],
|
||||||
|
uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0)
|
||||||
|
if rdErr != nil {
|
||||||
|
err := os.NewSyscallError("ReadDirectoryChanges", rdErr)
|
||||||
|
if rdErr == windows.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 {
|
||||||
|
// Watched directory was probably removed
|
||||||
|
w.sendEvent(watch.path, watch.mask&sysFSDELETESELF)
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
w.deleteWatch(watch)
|
||||||
|
w.startRead(watch)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readEvents reads from the I/O completion port, converts the
|
||||||
|
// received events into Event objects and sends them via the Events channel.
|
||||||
|
// Entry point to the I/O thread.
|
||||||
|
func (w *Watcher) readEvents() {
|
||||||
|
var (
|
||||||
|
n uint32
|
||||||
|
key uintptr
|
||||||
|
ov *windows.Overlapped
|
||||||
|
)
|
||||||
|
runtime.LockOSThread()
|
||||||
|
|
||||||
|
for {
|
||||||
|
qErr := windows.GetQueuedCompletionStatus(w.port, &n, &key, &ov, windows.INFINITE)
|
||||||
|
// This error is handled after the watch == nil check below. NOTE: this
|
||||||
|
// seems odd, note sure if it's correct.
|
||||||
|
|
||||||
|
watch := (*watch)(unsafe.Pointer(ov))
|
||||||
|
if watch == nil {
|
||||||
|
select {
|
||||||
|
case ch := <-w.quit:
|
||||||
|
w.mu.Lock()
|
||||||
|
var indexes []indexMap
|
||||||
|
for _, index := range w.watches {
|
||||||
|
indexes = append(indexes, index)
|
||||||
|
}
|
||||||
|
w.mu.Unlock()
|
||||||
|
for _, index := range indexes {
|
||||||
|
for _, watch := range index {
|
||||||
|
w.deleteWatch(watch)
|
||||||
|
w.startRead(watch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := windows.CloseHandle(w.port)
|
||||||
|
if err != nil {
|
||||||
|
err = os.NewSyscallError("CloseHandle", err)
|
||||||
|
}
|
||||||
|
close(w.Events)
|
||||||
|
close(w.Errors)
|
||||||
|
ch <- err
|
||||||
|
return
|
||||||
|
case in := <-w.input:
|
||||||
|
switch in.op {
|
||||||
|
case opAddWatch:
|
||||||
|
in.reply <- w.addWatch(in.path, uint64(in.flags))
|
||||||
|
case opRemoveWatch:
|
||||||
|
in.reply <- w.remWatch(in.path)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch qErr {
|
||||||
|
case windows.ERROR_MORE_DATA:
|
||||||
|
if watch == nil {
|
||||||
|
w.sendError(errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer"))
|
||||||
|
} else {
|
||||||
|
// The i/o succeeded but the buffer is full.
|
||||||
|
// In theory we should be building up a full packet.
|
||||||
|
// In practice we can get away with just carrying on.
|
||||||
|
n = uint32(unsafe.Sizeof(watch.buf))
|
||||||
|
}
|
||||||
|
case windows.ERROR_ACCESS_DENIED:
|
||||||
|
// Watched directory was probably removed
|
||||||
|
w.sendEvent(watch.path, watch.mask&sysFSDELETESELF)
|
||||||
|
w.deleteWatch(watch)
|
||||||
|
w.startRead(watch)
|
||||||
|
continue
|
||||||
|
case windows.ERROR_OPERATION_ABORTED:
|
||||||
|
// CancelIo was called on this handle
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
w.sendError(os.NewSyscallError("GetQueuedCompletionPort", qErr))
|
||||||
|
continue
|
||||||
|
case nil:
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset uint32
|
||||||
|
for {
|
||||||
|
if n == 0 {
|
||||||
|
w.sendError(errors.New("short read in readEvents()"))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Point "raw" to the event in the buffer
|
||||||
|
raw := (*windows.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset]))
|
||||||
|
|
||||||
|
// Create a buf that is the size of the path name
|
||||||
|
size := int(raw.FileNameLength / 2)
|
||||||
|
var buf []uint16
|
||||||
|
// TODO: Use unsafe.Slice in Go 1.17; https://stackoverflow.com/questions/51187973
|
||||||
|
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
|
||||||
|
sh.Data = uintptr(unsafe.Pointer(&raw.FileName))
|
||||||
|
sh.Len = size
|
||||||
|
sh.Cap = size
|
||||||
|
name := windows.UTF16ToString(buf)
|
||||||
|
fullname := filepath.Join(watch.path, name)
|
||||||
|
|
||||||
|
var mask uint64
|
||||||
|
switch raw.Action {
|
||||||
|
case windows.FILE_ACTION_REMOVED:
|
||||||
|
mask = sysFSDELETESELF
|
||||||
|
case windows.FILE_ACTION_MODIFIED:
|
||||||
|
mask = sysFSMODIFY
|
||||||
|
case windows.FILE_ACTION_RENAMED_OLD_NAME:
|
||||||
|
watch.rename = name
|
||||||
|
case windows.FILE_ACTION_RENAMED_NEW_NAME:
|
||||||
|
// Update saved path of all sub-watches.
|
||||||
|
old := filepath.Join(watch.path, watch.rename)
|
||||||
|
w.mu.Lock()
|
||||||
|
for _, watchMap := range w.watches {
|
||||||
|
for _, ww := range watchMap {
|
||||||
|
if strings.HasPrefix(ww.path, old) {
|
||||||
|
ww.path = filepath.Join(fullname, strings.TrimPrefix(ww.path, old))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
if watch.names[watch.rename] != 0 {
|
||||||
|
watch.names[name] |= watch.names[watch.rename]
|
||||||
|
delete(watch.names, watch.rename)
|
||||||
|
mask = sysFSMOVESELF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sendNameEvent := func() {
|
||||||
|
w.sendEvent(fullname, watch.names[name]&mask)
|
||||||
|
}
|
||||||
|
if raw.Action != windows.FILE_ACTION_RENAMED_NEW_NAME {
|
||||||
|
sendNameEvent()
|
||||||
|
}
|
||||||
|
if raw.Action == windows.FILE_ACTION_REMOVED {
|
||||||
|
w.sendEvent(fullname, watch.names[name]&sysFSIGNORED)
|
||||||
|
delete(watch.names, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.sendEvent(fullname, watch.mask&w.toFSnotifyFlags(raw.Action))
|
||||||
|
if raw.Action == windows.FILE_ACTION_RENAMED_NEW_NAME {
|
||||||
|
fullname = filepath.Join(watch.path, watch.rename)
|
||||||
|
sendNameEvent()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to the next event in the buffer
|
||||||
|
if raw.NextEntryOffset == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
offset += raw.NextEntryOffset
|
||||||
|
|
||||||
|
// Error!
|
||||||
|
if offset >= n {
|
||||||
|
w.sendError(errors.New(
|
||||||
|
"Windows system assumed buffer larger than it is, events have likely been missed."))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := w.startRead(watch); err != nil {
|
||||||
|
w.sendError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Watcher) toWindowsFlags(mask uint64) uint32 {
|
||||||
|
var m uint32
|
||||||
|
if mask&sysFSMODIFY != 0 {
|
||||||
|
m |= windows.FILE_NOTIFY_CHANGE_LAST_WRITE
|
||||||
|
}
|
||||||
|
if mask&sysFSATTRIB != 0 {
|
||||||
|
m |= windows.FILE_NOTIFY_CHANGE_ATTRIBUTES
|
||||||
|
}
|
||||||
|
if mask&(sysFSMOVE|sysFSCREATE|sysFSDELETE) != 0 {
|
||||||
|
m |= windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Watcher) toFSnotifyFlags(action uint32) uint64 {
|
||||||
|
switch action {
|
||||||
|
case windows.FILE_ACTION_ADDED:
|
||||||
|
return sysFSCREATE
|
||||||
|
case windows.FILE_ACTION_REMOVED:
|
||||||
|
return sysFSDELETE
|
||||||
|
case windows.FILE_ACTION_MODIFIED:
|
||||||
|
return sysFSMODIFY
|
||||||
|
case windows.FILE_ACTION_RENAMED_OLD_NAME:
|
||||||
|
return sysFSMOVEDFROM
|
||||||
|
case windows.FILE_ACTION_RENAMED_NEW_NAME:
|
||||||
|
return sysFSMOVEDTO
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
81
vendor/github.com/fsnotify/fsnotify/fsnotify.go
generated
vendored
Normal file
81
vendor/github.com/fsnotify/fsnotify/fsnotify.go
generated
vendored
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
//go:build !plan9
|
||||||
|
// +build !plan9
|
||||||
|
|
||||||
|
// Package fsnotify provides a cross-platform interface for file system
|
||||||
|
// notifications.
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Event represents a file system notification.
|
||||||
|
type Event struct {
|
||||||
|
// Path to the file or directory.
|
||||||
|
//
|
||||||
|
// Paths are relative to the input; for example with Add("dir") the Name
|
||||||
|
// will be set to "dir/file" if you create that file, but if you use
|
||||||
|
// Add("/path/to/dir") it will be "/path/to/dir/file".
|
||||||
|
Name string
|
||||||
|
|
||||||
|
// File operation that triggered the event.
|
||||||
|
//
|
||||||
|
// This is a bitmask and some systems may send multiple operations at once.
|
||||||
|
// Use the Event.Has() method instead of comparing with ==.
|
||||||
|
Op Op
|
||||||
|
}
|
||||||
|
|
||||||
|
// Op describes a set of file operations.
|
||||||
|
type Op uint32
|
||||||
|
|
||||||
|
// The operations fsnotify can trigger; see the documentation on [Watcher] for a
|
||||||
|
// full description, and check them with [Event.Has].
|
||||||
|
const (
|
||||||
|
Create Op = 1 << iota
|
||||||
|
Write
|
||||||
|
Remove
|
||||||
|
Rename
|
||||||
|
Chmod
|
||||||
|
)
|
||||||
|
|
||||||
|
// Common errors that can be reported by a watcher
|
||||||
|
var (
|
||||||
|
ErrNonExistentWatch = errors.New("can't remove non-existent watcher")
|
||||||
|
ErrEventOverflow = errors.New("fsnotify queue overflow")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (op Op) String() string {
|
||||||
|
var b strings.Builder
|
||||||
|
if op.Has(Create) {
|
||||||
|
b.WriteString("|CREATE")
|
||||||
|
}
|
||||||
|
if op.Has(Remove) {
|
||||||
|
b.WriteString("|REMOVE")
|
||||||
|
}
|
||||||
|
if op.Has(Write) {
|
||||||
|
b.WriteString("|WRITE")
|
||||||
|
}
|
||||||
|
if op.Has(Rename) {
|
||||||
|
b.WriteString("|RENAME")
|
||||||
|
}
|
||||||
|
if op.Has(Chmod) {
|
||||||
|
b.WriteString("|CHMOD")
|
||||||
|
}
|
||||||
|
if b.Len() == 0 {
|
||||||
|
return "[no events]"
|
||||||
|
}
|
||||||
|
return b.String()[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has reports if this operation has the given operation.
|
||||||
|
func (o Op) Has(h Op) bool { return o&h == h }
|
||||||
|
|
||||||
|
// Has reports if this event has the given operation.
|
||||||
|
func (e Event) Has(op Op) bool { return e.Op.Has(op) }
|
||||||
|
|
||||||
|
// String returns a string representation of the event with their path.
|
||||||
|
func (e Event) String() string {
|
||||||
|
return fmt.Sprintf("%-13s %q", e.Op.String(), e.Name)
|
||||||
|
}
|
208
vendor/github.com/fsnotify/fsnotify/mkdoc.zsh
generated
vendored
Normal file
208
vendor/github.com/fsnotify/fsnotify/mkdoc.zsh
generated
vendored
Normal file
|
@ -0,0 +1,208 @@
|
||||||
|
#!/usr/bin/env zsh
|
||||||
|
[ "${ZSH_VERSION:-}" = "" ] && echo >&2 "Only works with zsh" && exit 1
|
||||||
|
setopt err_exit no_unset pipefail extended_glob
|
||||||
|
|
||||||
|
# Simple script to update the godoc comments on all watchers. Probably took me
|
||||||
|
# more time to write this than doing it manually, but ah well 🙃
|
||||||
|
|
||||||
|
watcher=$(<<EOF
|
||||||
|
// Watcher watches a set of paths, delivering events on a channel.
|
||||||
|
//
|
||||||
|
// A watcher should not be copied (e.g. pass it by pointer, rather than by
|
||||||
|
// value).
|
||||||
|
//
|
||||||
|
// # Linux notes
|
||||||
|
//
|
||||||
|
// When a file is removed a Remove event won't be emitted until all file
|
||||||
|
// descriptors are closed, and deletes will always emit a Chmod. For example:
|
||||||
|
//
|
||||||
|
// fp := os.Open("file")
|
||||||
|
// os.Remove("file") // Triggers Chmod
|
||||||
|
// fp.Close() // Triggers Remove
|
||||||
|
//
|
||||||
|
// This is the event that inotify sends, so not much can be changed about this.
|
||||||
|
//
|
||||||
|
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
|
||||||
|
// for the number of watches per user, and fs.inotify.max_user_instances
|
||||||
|
// specifies the maximum number of inotify instances per user. Every Watcher you
|
||||||
|
// create is an "instance", and every path you add is a "watch".
|
||||||
|
//
|
||||||
|
// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
|
||||||
|
// /proc/sys/fs/inotify/max_user_instances
|
||||||
|
//
|
||||||
|
// To increase them you can use sysctl or write the value to the /proc file:
|
||||||
|
//
|
||||||
|
// # Default values on Linux 5.18
|
||||||
|
// sysctl fs.inotify.max_user_watches=124983
|
||||||
|
// sysctl fs.inotify.max_user_instances=128
|
||||||
|
//
|
||||||
|
// To make the changes persist on reboot edit /etc/sysctl.conf or
|
||||||
|
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
|
||||||
|
// your distro's documentation):
|
||||||
|
//
|
||||||
|
// fs.inotify.max_user_watches=124983
|
||||||
|
// fs.inotify.max_user_instances=128
|
||||||
|
//
|
||||||
|
// Reaching the limit will result in a "no space left on device" or "too many open
|
||||||
|
// files" error.
|
||||||
|
//
|
||||||
|
// # kqueue notes (macOS, BSD)
|
||||||
|
//
|
||||||
|
// kqueue requires opening a file descriptor for every file that's being watched;
|
||||||
|
// so if you're watching a directory with five files then that's six file
|
||||||
|
// descriptors. You will run in to your system's "max open files" limit faster on
|
||||||
|
// these platforms.
|
||||||
|
//
|
||||||
|
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
|
||||||
|
// control the maximum number of open files, as well as /etc/login.conf on BSD
|
||||||
|
// systems.
|
||||||
|
//
|
||||||
|
// # macOS notes
|
||||||
|
//
|
||||||
|
// Spotlight indexing on macOS can result in multiple events (see [#15]). A
|
||||||
|
// temporary workaround is to add your folder(s) to the "Spotlight Privacy
|
||||||
|
// Settings" until we have a native FSEvents implementation (see [#11]).
|
||||||
|
//
|
||||||
|
// [#11]: https://github.com/fsnotify/fsnotify/issues/11
|
||||||
|
// [#15]: https://github.com/fsnotify/fsnotify/issues/15
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
new=$(<<EOF
|
||||||
|
// NewWatcher creates a new Watcher.
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
add=$(<<EOF
|
||||||
|
// Add starts monitoring the path for changes.
|
||||||
|
//
|
||||||
|
// A path can only be watched once; attempting to watch it more than once will
|
||||||
|
// return an error. Paths that do not yet exist on the filesystem cannot be
|
||||||
|
// added. A watch will be automatically removed if the path is deleted.
|
||||||
|
//
|
||||||
|
// A path will remain watched if it gets renamed to somewhere else on the same
|
||||||
|
// filesystem, but the monitor will get removed if the path gets deleted and
|
||||||
|
// re-created, or if it's moved to a different filesystem.
|
||||||
|
//
|
||||||
|
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
|
||||||
|
// filesystems (/proc, /sys, etc.) generally don't work.
|
||||||
|
//
|
||||||
|
// # Watching directories
|
||||||
|
//
|
||||||
|
// All files in a directory are monitored, including new files that are created
|
||||||
|
// after the watcher is started. Subdirectories are not watched (i.e. it's
|
||||||
|
// non-recursive).
|
||||||
|
//
|
||||||
|
// # Watching files
|
||||||
|
//
|
||||||
|
// Watching individual files (rather than directories) is generally not
|
||||||
|
// recommended as many tools update files atomically. Instead of "just" writing
|
||||||
|
// to the file a temporary file will be written to first, and if successful the
|
||||||
|
// temporary file is moved to to destination removing the original, or some
|
||||||
|
// variant thereof. The watcher on the original file is now lost, as it no
|
||||||
|
// longer exists.
|
||||||
|
//
|
||||||
|
// Instead, watch the parent directory and use Event.Name to filter out files
|
||||||
|
// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
remove=$(<<EOF
|
||||||
|
// Remove stops monitoring the path for changes.
|
||||||
|
//
|
||||||
|
// Directories are always removed non-recursively. For example, if you added
|
||||||
|
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
|
||||||
|
//
|
||||||
|
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
close=$(<<EOF
|
||||||
|
// Close removes all watches and closes the events channel.
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
watchlist=$(<<EOF
|
||||||
|
// WatchList returns all paths added with [Add] (and are not yet removed).
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
events=$(<<EOF
|
||||||
|
// Events sends the filesystem change events.
|
||||||
|
//
|
||||||
|
// fsnotify can send the following events; a "path" here can refer to a
|
||||||
|
// file, directory, symbolic link, or special file like a FIFO.
|
||||||
|
//
|
||||||
|
// fsnotify.Create A new path was created; this may be followed by one
|
||||||
|
// or more Write events if data also gets written to a
|
||||||
|
// file.
|
||||||
|
//
|
||||||
|
// fsnotify.Remove A path was removed.
|
||||||
|
//
|
||||||
|
// fsnotify.Rename A path was renamed. A rename is always sent with the
|
||||||
|
// old path as Event.Name, and a Create event will be
|
||||||
|
// sent with the new name. Renames are only sent for
|
||||||
|
// paths that are currently watched; e.g. moving an
|
||||||
|
// unmonitored file into a monitored directory will
|
||||||
|
// show up as just a Create. Similarly, renaming a file
|
||||||
|
// to outside a monitored directory will show up as
|
||||||
|
// only a Rename.
|
||||||
|
//
|
||||||
|
// fsnotify.Write A file or named pipe was written to. A Truncate will
|
||||||
|
// also trigger a Write. A single "write action"
|
||||||
|
// initiated by the user may show up as one or multiple
|
||||||
|
// writes, depending on when the system syncs things to
|
||||||
|
// disk. For example when compiling a large Go program
|
||||||
|
// you may get hundreds of Write events, so you
|
||||||
|
// probably want to wait until you've stopped receiving
|
||||||
|
// them (see the dedup example in cmd/fsnotify).
|
||||||
|
//
|
||||||
|
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
|
||||||
|
// when a file is removed (or more accurately, when a
|
||||||
|
// link to an inode is removed). On kqueue it's sent
|
||||||
|
// and on kqueue when a file is truncated. On Windows
|
||||||
|
// it's never sent.
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
errors=$(<<EOF
|
||||||
|
// Errors sends any errors.
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
set-cmt() {
|
||||||
|
local pat=$1
|
||||||
|
local cmt=$2
|
||||||
|
|
||||||
|
IFS=$'\n' local files=($(grep -n $pat backend_*~*_test.go))
|
||||||
|
for f in $files; do
|
||||||
|
IFS=':' local fields=($=f)
|
||||||
|
local file=$fields[1]
|
||||||
|
local end=$(( $fields[2] - 1 ))
|
||||||
|
|
||||||
|
# Find start of comment.
|
||||||
|
local start=0
|
||||||
|
IFS=$'\n' local lines=($(head -n$end $file))
|
||||||
|
for (( i = 1; i <= $#lines; i++ )); do
|
||||||
|
local line=$lines[-$i]
|
||||||
|
if ! grep -q '^[[:space:]]*//' <<<$line; then
|
||||||
|
start=$(( end - (i - 2) ))
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
head -n $(( start - 1 )) $file >/tmp/x
|
||||||
|
print -r -- $cmt >>/tmp/x
|
||||||
|
tail -n+$(( end + 1 )) $file >>/tmp/x
|
||||||
|
mv /tmp/x $file
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
set-cmt '^type Watcher struct ' $watcher
|
||||||
|
set-cmt '^func NewWatcher(' $new
|
||||||
|
set-cmt '^func (w \*Watcher) Add(' $add
|
||||||
|
set-cmt '^func (w \*Watcher) Remove(' $remove
|
||||||
|
set-cmt '^func (w \*Watcher) Close(' $close
|
||||||
|
set-cmt '^func (w \*Watcher) WatchList(' $watchlist
|
||||||
|
set-cmt '^[[:space:]]*Events *chan Event$' $events
|
||||||
|
set-cmt '^[[:space:]]*Errors *chan error$' $errors
|
8
vendor/github.com/fsnotify/fsnotify/system_bsd.go
generated
vendored
Normal file
8
vendor/github.com/fsnotify/fsnotify/system_bsd.go
generated
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
//go:build freebsd || openbsd || netbsd || dragonfly
|
||||||
|
// +build freebsd openbsd netbsd dragonfly
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
const openMode = unix.O_NONBLOCK | unix.O_RDONLY | unix.O_CLOEXEC
|
9
vendor/github.com/fsnotify/fsnotify/system_darwin.go
generated
vendored
Normal file
9
vendor/github.com/fsnotify/fsnotify/system_darwin.go
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
//go:build darwin
|
||||||
|
// +build darwin
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
// note: this constant is not defined on BSD
|
||||||
|
const openMode = unix.O_EVTONLY | unix.O_CLOEXEC
|
3
vendor/github.com/golang/protobuf/AUTHORS
generated
vendored
Normal file
3
vendor/github.com/golang/protobuf/AUTHORS
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# This source code refers to The Go Authors for copyright purposes.
|
||||||
|
# The master list of authors is in the main Go distribution,
|
||||||
|
# visible at http://tip.golang.org/AUTHORS.
|
3
vendor/github.com/golang/protobuf/CONTRIBUTORS
generated
vendored
Normal file
3
vendor/github.com/golang/protobuf/CONTRIBUTORS
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# This source code was written by the Go contributors.
|
||||||
|
# The master list of contributors is in the main Go distribution,
|
||||||
|
# visible at http://tip.golang.org/CONTRIBUTORS.
|
28
vendor/github.com/golang/protobuf/LICENSE
generated
vendored
Normal file
28
vendor/github.com/golang/protobuf/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
324
vendor/github.com/golang/protobuf/proto/buffer.go
generated
vendored
Normal file
324
vendor/github.com/golang/protobuf/proto/buffer.go
generated
vendored
Normal file
|
@ -0,0 +1,324 @@
|
||||||
|
// Copyright 2019 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"google.golang.org/protobuf/encoding/prototext"
|
||||||
|
"google.golang.org/protobuf/encoding/protowire"
|
||||||
|
"google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
WireVarint = 0
|
||||||
|
WireFixed32 = 5
|
||||||
|
WireFixed64 = 1
|
||||||
|
WireBytes = 2
|
||||||
|
WireStartGroup = 3
|
||||||
|
WireEndGroup = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
// EncodeVarint returns the varint encoded bytes of v.
|
||||||
|
func EncodeVarint(v uint64) []byte {
|
||||||
|
return protowire.AppendVarint(nil, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SizeVarint returns the length of the varint encoded bytes of v.
|
||||||
|
// This is equal to len(EncodeVarint(v)).
|
||||||
|
func SizeVarint(v uint64) int {
|
||||||
|
return protowire.SizeVarint(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeVarint parses a varint encoded integer from b,
|
||||||
|
// returning the integer value and the length of the varint.
|
||||||
|
// It returns (0, 0) if there is a parse error.
|
||||||
|
func DecodeVarint(b []byte) (uint64, int) {
|
||||||
|
v, n := protowire.ConsumeVarint(b)
|
||||||
|
if n < 0 {
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
|
return v, n
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buffer is a buffer for encoding and decoding the protobuf wire format.
|
||||||
|
// It may be reused between invocations to reduce memory usage.
|
||||||
|
type Buffer struct {
|
||||||
|
buf []byte
|
||||||
|
idx int
|
||||||
|
deterministic bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBuffer allocates a new Buffer initialized with buf,
|
||||||
|
// where the contents of buf are considered the unread portion of the buffer.
|
||||||
|
func NewBuffer(buf []byte) *Buffer {
|
||||||
|
return &Buffer{buf: buf}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDeterministic specifies whether to use deterministic serialization.
|
||||||
|
//
|
||||||
|
// Deterministic serialization guarantees that for a given binary, equal
|
||||||
|
// messages will always be serialized to the same bytes. This implies:
|
||||||
|
//
|
||||||
|
// - Repeated serialization of a message will return the same bytes.
|
||||||
|
// - Different processes of the same binary (which may be executing on
|
||||||
|
// different machines) will serialize equal messages to the same bytes.
|
||||||
|
//
|
||||||
|
// Note that the deterministic serialization is NOT canonical across
|
||||||
|
// languages. It is not guaranteed to remain stable over time. It is unstable
|
||||||
|
// across different builds with schema changes due to unknown fields.
|
||||||
|
// Users who need canonical serialization (e.g., persistent storage in a
|
||||||
|
// canonical form, fingerprinting, etc.) should define their own
|
||||||
|
// canonicalization specification and implement their own serializer rather
|
||||||
|
// than relying on this API.
|
||||||
|
//
|
||||||
|
// If deterministic serialization is requested, map entries will be sorted
|
||||||
|
// by keys in lexographical order. This is an implementation detail and
|
||||||
|
// subject to change.
|
||||||
|
func (b *Buffer) SetDeterministic(deterministic bool) {
|
||||||
|
b.deterministic = deterministic
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBuf sets buf as the internal buffer,
|
||||||
|
// where the contents of buf are considered the unread portion of the buffer.
|
||||||
|
func (b *Buffer) SetBuf(buf []byte) {
|
||||||
|
b.buf = buf
|
||||||
|
b.idx = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset clears the internal buffer of all written and unread data.
|
||||||
|
func (b *Buffer) Reset() {
|
||||||
|
b.buf = b.buf[:0]
|
||||||
|
b.idx = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes returns the internal buffer.
|
||||||
|
func (b *Buffer) Bytes() []byte {
|
||||||
|
return b.buf
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unread returns the unread portion of the buffer.
|
||||||
|
func (b *Buffer) Unread() []byte {
|
||||||
|
return b.buf[b.idx:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal appends the wire-format encoding of m to the buffer.
|
||||||
|
func (b *Buffer) Marshal(m Message) error {
|
||||||
|
var err error
|
||||||
|
b.buf, err = marshalAppend(b.buf, m, b.deterministic)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal parses the wire-format message in the buffer and
|
||||||
|
// places the decoded results in m.
|
||||||
|
// It does not reset m before unmarshaling.
|
||||||
|
func (b *Buffer) Unmarshal(m Message) error {
|
||||||
|
err := UnmarshalMerge(b.Unread(), m)
|
||||||
|
b.idx = len(b.buf)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type unknownFields struct{ XXX_unrecognized protoimpl.UnknownFields }
|
||||||
|
|
||||||
|
func (m *unknownFields) String() string { panic("not implemented") }
|
||||||
|
func (m *unknownFields) Reset() { panic("not implemented") }
|
||||||
|
func (m *unknownFields) ProtoMessage() { panic("not implemented") }
|
||||||
|
|
||||||
|
// DebugPrint dumps the encoded bytes of b with a header and footer including s
|
||||||
|
// to stdout. This is only intended for debugging.
|
||||||
|
func (*Buffer) DebugPrint(s string, b []byte) {
|
||||||
|
m := MessageReflect(new(unknownFields))
|
||||||
|
m.SetUnknown(b)
|
||||||
|
b, _ = prototext.MarshalOptions{AllowPartial: true, Indent: "\t"}.Marshal(m.Interface())
|
||||||
|
fmt.Printf("==== %s ====\n%s==== %s ====\n", s, b, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeVarint appends an unsigned varint encoding to the buffer.
|
||||||
|
func (b *Buffer) EncodeVarint(v uint64) error {
|
||||||
|
b.buf = protowire.AppendVarint(b.buf, v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeZigzag32 appends a 32-bit zig-zag varint encoding to the buffer.
|
||||||
|
func (b *Buffer) EncodeZigzag32(v uint64) error {
|
||||||
|
return b.EncodeVarint(uint64((uint32(v) << 1) ^ uint32((int32(v) >> 31))))
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeZigzag64 appends a 64-bit zig-zag varint encoding to the buffer.
|
||||||
|
func (b *Buffer) EncodeZigzag64(v uint64) error {
|
||||||
|
return b.EncodeVarint(uint64((uint64(v) << 1) ^ uint64((int64(v) >> 63))))
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeFixed32 appends a 32-bit little-endian integer to the buffer.
|
||||||
|
func (b *Buffer) EncodeFixed32(v uint64) error {
|
||||||
|
b.buf = protowire.AppendFixed32(b.buf, uint32(v))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeFixed64 appends a 64-bit little-endian integer to the buffer.
|
||||||
|
func (b *Buffer) EncodeFixed64(v uint64) error {
|
||||||
|
b.buf = protowire.AppendFixed64(b.buf, uint64(v))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeRawBytes appends a length-prefixed raw bytes to the buffer.
|
||||||
|
func (b *Buffer) EncodeRawBytes(v []byte) error {
|
||||||
|
b.buf = protowire.AppendBytes(b.buf, v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeStringBytes appends a length-prefixed raw bytes to the buffer.
|
||||||
|
// It does not validate whether v contains valid UTF-8.
|
||||||
|
func (b *Buffer) EncodeStringBytes(v string) error {
|
||||||
|
b.buf = protowire.AppendString(b.buf, v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeMessage appends a length-prefixed encoded message to the buffer.
|
||||||
|
func (b *Buffer) EncodeMessage(m Message) error {
|
||||||
|
var err error
|
||||||
|
b.buf = protowire.AppendVarint(b.buf, uint64(Size(m)))
|
||||||
|
b.buf, err = marshalAppend(b.buf, m, b.deterministic)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeVarint consumes an encoded unsigned varint from the buffer.
|
||||||
|
func (b *Buffer) DecodeVarint() (uint64, error) {
|
||||||
|
v, n := protowire.ConsumeVarint(b.buf[b.idx:])
|
||||||
|
if n < 0 {
|
||||||
|
return 0, protowire.ParseError(n)
|
||||||
|
}
|
||||||
|
b.idx += n
|
||||||
|
return uint64(v), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeZigzag32 consumes an encoded 32-bit zig-zag varint from the buffer.
|
||||||
|
func (b *Buffer) DecodeZigzag32() (uint64, error) {
|
||||||
|
v, err := b.DecodeVarint()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return uint64((uint32(v) >> 1) ^ uint32((int32(v&1)<<31)>>31)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeZigzag64 consumes an encoded 64-bit zig-zag varint from the buffer.
|
||||||
|
func (b *Buffer) DecodeZigzag64() (uint64, error) {
|
||||||
|
v, err := b.DecodeVarint()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return uint64((uint64(v) >> 1) ^ uint64((int64(v&1)<<63)>>63)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeFixed32 consumes a 32-bit little-endian integer from the buffer.
|
||||||
|
func (b *Buffer) DecodeFixed32() (uint64, error) {
|
||||||
|
v, n := protowire.ConsumeFixed32(b.buf[b.idx:])
|
||||||
|
if n < 0 {
|
||||||
|
return 0, protowire.ParseError(n)
|
||||||
|
}
|
||||||
|
b.idx += n
|
||||||
|
return uint64(v), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeFixed64 consumes a 64-bit little-endian integer from the buffer.
|
||||||
|
func (b *Buffer) DecodeFixed64() (uint64, error) {
|
||||||
|
v, n := protowire.ConsumeFixed64(b.buf[b.idx:])
|
||||||
|
if n < 0 {
|
||||||
|
return 0, protowire.ParseError(n)
|
||||||
|
}
|
||||||
|
b.idx += n
|
||||||
|
return uint64(v), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeRawBytes consumes a length-prefixed raw bytes from the buffer.
|
||||||
|
// If alloc is specified, it returns a copy the raw bytes
|
||||||
|
// rather than a sub-slice of the buffer.
|
||||||
|
func (b *Buffer) DecodeRawBytes(alloc bool) ([]byte, error) {
|
||||||
|
v, n := protowire.ConsumeBytes(b.buf[b.idx:])
|
||||||
|
if n < 0 {
|
||||||
|
return nil, protowire.ParseError(n)
|
||||||
|
}
|
||||||
|
b.idx += n
|
||||||
|
if alloc {
|
||||||
|
v = append([]byte(nil), v...)
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeStringBytes consumes a length-prefixed raw bytes from the buffer.
|
||||||
|
// It does not validate whether the raw bytes contain valid UTF-8.
|
||||||
|
func (b *Buffer) DecodeStringBytes() (string, error) {
|
||||||
|
v, n := protowire.ConsumeString(b.buf[b.idx:])
|
||||||
|
if n < 0 {
|
||||||
|
return "", protowire.ParseError(n)
|
||||||
|
}
|
||||||
|
b.idx += n
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeMessage consumes a length-prefixed message from the buffer.
|
||||||
|
// It does not reset m before unmarshaling.
|
||||||
|
func (b *Buffer) DecodeMessage(m Message) error {
|
||||||
|
v, err := b.DecodeRawBytes(false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return UnmarshalMerge(v, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeGroup consumes a message group from the buffer.
|
||||||
|
// It assumes that the start group marker has already been consumed and
|
||||||
|
// consumes all bytes until (and including the end group marker).
|
||||||
|
// It does not reset m before unmarshaling.
|
||||||
|
func (b *Buffer) DecodeGroup(m Message) error {
|
||||||
|
v, n, err := consumeGroup(b.buf[b.idx:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b.idx += n
|
||||||
|
return UnmarshalMerge(v, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// consumeGroup parses b until it finds an end group marker, returning
|
||||||
|
// the raw bytes of the message (excluding the end group marker) and the
|
||||||
|
// the total length of the message (including the end group marker).
|
||||||
|
func consumeGroup(b []byte) ([]byte, int, error) {
|
||||||
|
b0 := b
|
||||||
|
depth := 1 // assume this follows a start group marker
|
||||||
|
for {
|
||||||
|
_, wtyp, tagLen := protowire.ConsumeTag(b)
|
||||||
|
if tagLen < 0 {
|
||||||
|
return nil, 0, protowire.ParseError(tagLen)
|
||||||
|
}
|
||||||
|
b = b[tagLen:]
|
||||||
|
|
||||||
|
var valLen int
|
||||||
|
switch wtyp {
|
||||||
|
case protowire.VarintType:
|
||||||
|
_, valLen = protowire.ConsumeVarint(b)
|
||||||
|
case protowire.Fixed32Type:
|
||||||
|
_, valLen = protowire.ConsumeFixed32(b)
|
||||||
|
case protowire.Fixed64Type:
|
||||||
|
_, valLen = protowire.ConsumeFixed64(b)
|
||||||
|
case protowire.BytesType:
|
||||||
|
_, valLen = protowire.ConsumeBytes(b)
|
||||||
|
case protowire.StartGroupType:
|
||||||
|
depth++
|
||||||
|
case protowire.EndGroupType:
|
||||||
|
depth--
|
||||||
|
default:
|
||||||
|
return nil, 0, errors.New("proto: cannot parse reserved wire type")
|
||||||
|
}
|
||||||
|
if valLen < 0 {
|
||||||
|
return nil, 0, protowire.ParseError(valLen)
|
||||||
|
}
|
||||||
|
b = b[valLen:]
|
||||||
|
|
||||||
|
if depth == 0 {
|
||||||
|
return b0[:len(b0)-len(b)-tagLen], len(b0) - len(b), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
63
vendor/github.com/golang/protobuf/proto/defaults.go
generated
vendored
Normal file
63
vendor/github.com/golang/protobuf/proto/defaults.go
generated
vendored
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
// Copyright 2019 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetDefaults sets unpopulated scalar fields to their default values.
|
||||||
|
// Fields within a oneof are not set even if they have a default value.
|
||||||
|
// SetDefaults is recursively called upon any populated message fields.
|
||||||
|
func SetDefaults(m Message) {
|
||||||
|
if m != nil {
|
||||||
|
setDefaults(MessageReflect(m))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setDefaults(m protoreflect.Message) {
|
||||||
|
fds := m.Descriptor().Fields()
|
||||||
|
for i := 0; i < fds.Len(); i++ {
|
||||||
|
fd := fds.Get(i)
|
||||||
|
if !m.Has(fd) {
|
||||||
|
if fd.HasDefault() && fd.ContainingOneof() == nil {
|
||||||
|
v := fd.Default()
|
||||||
|
if fd.Kind() == protoreflect.BytesKind {
|
||||||
|
v = protoreflect.ValueOf(append([]byte(nil), v.Bytes()...)) // copy the default bytes
|
||||||
|
}
|
||||||
|
m.Set(fd, v)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
|
||||||
|
switch {
|
||||||
|
// Handle singular message.
|
||||||
|
case fd.Cardinality() != protoreflect.Repeated:
|
||||||
|
if fd.Message() != nil {
|
||||||
|
setDefaults(m.Get(fd).Message())
|
||||||
|
}
|
||||||
|
// Handle list of messages.
|
||||||
|
case fd.IsList():
|
||||||
|
if fd.Message() != nil {
|
||||||
|
ls := m.Get(fd).List()
|
||||||
|
for i := 0; i < ls.Len(); i++ {
|
||||||
|
setDefaults(ls.Get(i).Message())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Handle map of messages.
|
||||||
|
case fd.IsMap():
|
||||||
|
if fd.MapValue().Message() != nil {
|
||||||
|
ms := m.Get(fd).Map()
|
||||||
|
ms.Range(func(_ protoreflect.MapKey, v protoreflect.Value) bool {
|
||||||
|
setDefaults(v.Message())
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
113
vendor/github.com/golang/protobuf/proto/deprecated.go
generated
vendored
Normal file
113
vendor/github.com/golang/protobuf/proto/deprecated.go
generated
vendored
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
// Copyright 2018 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
protoV2 "google.golang.org/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Deprecated: No longer returned.
|
||||||
|
ErrNil = errors.New("proto: Marshal called with nil")
|
||||||
|
|
||||||
|
// Deprecated: No longer returned.
|
||||||
|
ErrTooLarge = errors.New("proto: message encodes to over 2 GB")
|
||||||
|
|
||||||
|
// Deprecated: No longer returned.
|
||||||
|
ErrInternalBadWireType = errors.New("proto: internal error: bad wiretype for oneof")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Deprecated: Do not use.
|
||||||
|
type Stats struct{ Emalloc, Dmalloc, Encode, Decode, Chit, Cmiss, Size uint64 }
|
||||||
|
|
||||||
|
// Deprecated: Do not use.
|
||||||
|
func GetStats() Stats { return Stats{} }
|
||||||
|
|
||||||
|
// Deprecated: Do not use.
|
||||||
|
func MarshalMessageSet(interface{}) ([]byte, error) {
|
||||||
|
return nil, errors.New("proto: not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Do not use.
|
||||||
|
func UnmarshalMessageSet([]byte, interface{}) error {
|
||||||
|
return errors.New("proto: not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Do not use.
|
||||||
|
func MarshalMessageSetJSON(interface{}) ([]byte, error) {
|
||||||
|
return nil, errors.New("proto: not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Do not use.
|
||||||
|
func UnmarshalMessageSetJSON([]byte, interface{}) error {
|
||||||
|
return errors.New("proto: not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Do not use.
|
||||||
|
func RegisterMessageSetType(Message, int32, string) {}
|
||||||
|
|
||||||
|
// Deprecated: Do not use.
|
||||||
|
func EnumName(m map[int32]string, v int32) string {
|
||||||
|
s, ok := m[v]
|
||||||
|
if ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return strconv.Itoa(int(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Do not use.
|
||||||
|
func UnmarshalJSONEnum(m map[string]int32, data []byte, enumName string) (int32, error) {
|
||||||
|
if data[0] == '"' {
|
||||||
|
// New style: enums are strings.
|
||||||
|
var repr string
|
||||||
|
if err := json.Unmarshal(data, &repr); err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
val, ok := m[repr]
|
||||||
|
if !ok {
|
||||||
|
return 0, fmt.Errorf("unrecognized enum %s value %q", enumName, repr)
|
||||||
|
}
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
// Old style: enums are ints.
|
||||||
|
var val int32
|
||||||
|
if err := json.Unmarshal(data, &val); err != nil {
|
||||||
|
return 0, fmt.Errorf("cannot unmarshal %#q into enum %s", data, enumName)
|
||||||
|
}
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Do not use; this type existed for intenal-use only.
|
||||||
|
type InternalMessageInfo struct{}
|
||||||
|
|
||||||
|
// Deprecated: Do not use; this method existed for intenal-use only.
|
||||||
|
func (*InternalMessageInfo) DiscardUnknown(m Message) {
|
||||||
|
DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Do not use; this method existed for intenal-use only.
|
||||||
|
func (*InternalMessageInfo) Marshal(b []byte, m Message, deterministic bool) ([]byte, error) {
|
||||||
|
return protoV2.MarshalOptions{Deterministic: deterministic}.MarshalAppend(b, MessageV2(m))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Do not use; this method existed for intenal-use only.
|
||||||
|
func (*InternalMessageInfo) Merge(dst, src Message) {
|
||||||
|
protoV2.Merge(MessageV2(dst), MessageV2(src))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Do not use; this method existed for intenal-use only.
|
||||||
|
func (*InternalMessageInfo) Size(m Message) int {
|
||||||
|
return protoV2.Size(MessageV2(m))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Do not use; this method existed for intenal-use only.
|
||||||
|
func (*InternalMessageInfo) Unmarshal(m Message, b []byte) error {
|
||||||
|
return protoV2.UnmarshalOptions{Merge: true}.Unmarshal(b, MessageV2(m))
|
||||||
|
}
|
58
vendor/github.com/golang/protobuf/proto/discard.go
generated
vendored
Normal file
58
vendor/github.com/golang/protobuf/proto/discard.go
generated
vendored
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
// Copyright 2019 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DiscardUnknown recursively discards all unknown fields from this message
|
||||||
|
// and all embedded messages.
|
||||||
|
//
|
||||||
|
// When unmarshaling a message with unrecognized fields, the tags and values
|
||||||
|
// of such fields are preserved in the Message. This allows a later call to
|
||||||
|
// marshal to be able to produce a message that continues to have those
|
||||||
|
// unrecognized fields. To avoid this, DiscardUnknown is used to
|
||||||
|
// explicitly clear the unknown fields after unmarshaling.
|
||||||
|
func DiscardUnknown(m Message) {
|
||||||
|
if m != nil {
|
||||||
|
discardUnknown(MessageReflect(m))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func discardUnknown(m protoreflect.Message) {
|
||||||
|
m.Range(func(fd protoreflect.FieldDescriptor, val protoreflect.Value) bool {
|
||||||
|
switch {
|
||||||
|
// Handle singular message.
|
||||||
|
case fd.Cardinality() != protoreflect.Repeated:
|
||||||
|
if fd.Message() != nil {
|
||||||
|
discardUnknown(m.Get(fd).Message())
|
||||||
|
}
|
||||||
|
// Handle list of messages.
|
||||||
|
case fd.IsList():
|
||||||
|
if fd.Message() != nil {
|
||||||
|
ls := m.Get(fd).List()
|
||||||
|
for i := 0; i < ls.Len(); i++ {
|
||||||
|
discardUnknown(ls.Get(i).Message())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Handle map of messages.
|
||||||
|
case fd.IsMap():
|
||||||
|
if fd.MapValue().Message() != nil {
|
||||||
|
ms := m.Get(fd).Map()
|
||||||
|
ms.Range(func(_ protoreflect.MapKey, v protoreflect.Value) bool {
|
||||||
|
discardUnknown(v.Message())
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
// Discard unknown fields.
|
||||||
|
if len(m.GetUnknown()) > 0 {
|
||||||
|
m.SetUnknown(nil)
|
||||||
|
}
|
||||||
|
}
|
356
vendor/github.com/golang/protobuf/proto/extensions.go
generated
vendored
Normal file
356
vendor/github.com/golang/protobuf/proto/extensions.go
generated
vendored
Normal file
|
@ -0,0 +1,356 @@
|
||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"google.golang.org/protobuf/encoding/protowire"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
"google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
"google.golang.org/protobuf/reflect/protoregistry"
|
||||||
|
"google.golang.org/protobuf/runtime/protoiface"
|
||||||
|
"google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// ExtensionDesc represents an extension descriptor and
|
||||||
|
// is used to interact with an extension field in a message.
|
||||||
|
//
|
||||||
|
// Variables of this type are generated in code by protoc-gen-go.
|
||||||
|
ExtensionDesc = protoimpl.ExtensionInfo
|
||||||
|
|
||||||
|
// ExtensionRange represents a range of message extensions.
|
||||||
|
// Used in code generated by protoc-gen-go.
|
||||||
|
ExtensionRange = protoiface.ExtensionRangeV1
|
||||||
|
|
||||||
|
// Deprecated: Do not use; this is an internal type.
|
||||||
|
Extension = protoimpl.ExtensionFieldV1
|
||||||
|
|
||||||
|
// Deprecated: Do not use; this is an internal type.
|
||||||
|
XXX_InternalExtensions = protoimpl.ExtensionFields
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrMissingExtension reports whether the extension was not present.
|
||||||
|
var ErrMissingExtension = errors.New("proto: missing extension")
|
||||||
|
|
||||||
|
var errNotExtendable = errors.New("proto: not an extendable proto.Message")
|
||||||
|
|
||||||
|
// HasExtension reports whether the extension field is present in m
|
||||||
|
// either as an explicitly populated field or as an unknown field.
|
||||||
|
func HasExtension(m Message, xt *ExtensionDesc) (has bool) {
|
||||||
|
mr := MessageReflect(m)
|
||||||
|
if mr == nil || !mr.IsValid() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether any populated known field matches the field number.
|
||||||
|
xtd := xt.TypeDescriptor()
|
||||||
|
if isValidExtension(mr.Descriptor(), xtd) {
|
||||||
|
has = mr.Has(xtd)
|
||||||
|
} else {
|
||||||
|
mr.Range(func(fd protoreflect.FieldDescriptor, _ protoreflect.Value) bool {
|
||||||
|
has = int32(fd.Number()) == xt.Field
|
||||||
|
return !has
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether any unknown field matches the field number.
|
||||||
|
for b := mr.GetUnknown(); !has && len(b) > 0; {
|
||||||
|
num, _, n := protowire.ConsumeField(b)
|
||||||
|
has = int32(num) == xt.Field
|
||||||
|
b = b[n:]
|
||||||
|
}
|
||||||
|
return has
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearExtension removes the extension field from m
|
||||||
|
// either as an explicitly populated field or as an unknown field.
|
||||||
|
func ClearExtension(m Message, xt *ExtensionDesc) {
|
||||||
|
mr := MessageReflect(m)
|
||||||
|
if mr == nil || !mr.IsValid() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
xtd := xt.TypeDescriptor()
|
||||||
|
if isValidExtension(mr.Descriptor(), xtd) {
|
||||||
|
mr.Clear(xtd)
|
||||||
|
} else {
|
||||||
|
mr.Range(func(fd protoreflect.FieldDescriptor, _ protoreflect.Value) bool {
|
||||||
|
if int32(fd.Number()) == xt.Field {
|
||||||
|
mr.Clear(fd)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
clearUnknown(mr, fieldNum(xt.Field))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearAllExtensions clears all extensions from m.
|
||||||
|
// This includes populated fields and unknown fields in the extension range.
|
||||||
|
func ClearAllExtensions(m Message) {
|
||||||
|
mr := MessageReflect(m)
|
||||||
|
if mr == nil || !mr.IsValid() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mr.Range(func(fd protoreflect.FieldDescriptor, _ protoreflect.Value) bool {
|
||||||
|
if fd.IsExtension() {
|
||||||
|
mr.Clear(fd)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
clearUnknown(mr, mr.Descriptor().ExtensionRanges())
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExtension retrieves a proto2 extended field from m.
|
||||||
|
//
|
||||||
|
// If the descriptor is type complete (i.e., ExtensionDesc.ExtensionType is non-nil),
|
||||||
|
// then GetExtension parses the encoded field and returns a Go value of the specified type.
|
||||||
|
// If the field is not present, then the default value is returned (if one is specified),
|
||||||
|
// otherwise ErrMissingExtension is reported.
|
||||||
|
//
|
||||||
|
// If the descriptor is type incomplete (i.e., ExtensionDesc.ExtensionType is nil),
|
||||||
|
// then GetExtension returns the raw encoded bytes for the extension field.
|
||||||
|
func GetExtension(m Message, xt *ExtensionDesc) (interface{}, error) {
|
||||||
|
mr := MessageReflect(m)
|
||||||
|
if mr == nil || !mr.IsValid() || mr.Descriptor().ExtensionRanges().Len() == 0 {
|
||||||
|
return nil, errNotExtendable
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the unknown fields for this extension field.
|
||||||
|
var bo protoreflect.RawFields
|
||||||
|
for bi := mr.GetUnknown(); len(bi) > 0; {
|
||||||
|
num, _, n := protowire.ConsumeField(bi)
|
||||||
|
if int32(num) == xt.Field {
|
||||||
|
bo = append(bo, bi[:n]...)
|
||||||
|
}
|
||||||
|
bi = bi[n:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// For type incomplete descriptors, only retrieve the unknown fields.
|
||||||
|
if xt.ExtensionType == nil {
|
||||||
|
return []byte(bo), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the extension field only exists as unknown fields, unmarshal it.
|
||||||
|
// This is rarely done since proto.Unmarshal eagerly unmarshals extensions.
|
||||||
|
xtd := xt.TypeDescriptor()
|
||||||
|
if !isValidExtension(mr.Descriptor(), xtd) {
|
||||||
|
return nil, fmt.Errorf("proto: bad extended type; %T does not extend %T", xt.ExtendedType, m)
|
||||||
|
}
|
||||||
|
if !mr.Has(xtd) && len(bo) > 0 {
|
||||||
|
m2 := mr.New()
|
||||||
|
if err := (proto.UnmarshalOptions{
|
||||||
|
Resolver: extensionResolver{xt},
|
||||||
|
}.Unmarshal(bo, m2.Interface())); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if m2.Has(xtd) {
|
||||||
|
mr.Set(xtd, m2.Get(xtd))
|
||||||
|
clearUnknown(mr, fieldNum(xt.Field))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether the message has the extension field set or a default.
|
||||||
|
var pv protoreflect.Value
|
||||||
|
switch {
|
||||||
|
case mr.Has(xtd):
|
||||||
|
pv = mr.Get(xtd)
|
||||||
|
case xtd.HasDefault():
|
||||||
|
pv = xtd.Default()
|
||||||
|
default:
|
||||||
|
return nil, ErrMissingExtension
|
||||||
|
}
|
||||||
|
|
||||||
|
v := xt.InterfaceOf(pv)
|
||||||
|
rv := reflect.ValueOf(v)
|
||||||
|
if isScalarKind(rv.Kind()) {
|
||||||
|
rv2 := reflect.New(rv.Type())
|
||||||
|
rv2.Elem().Set(rv)
|
||||||
|
v = rv2.Interface()
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// extensionResolver is a custom extension resolver that stores a single
|
||||||
|
// extension type that takes precedence over the global registry.
|
||||||
|
type extensionResolver struct{ xt protoreflect.ExtensionType }
|
||||||
|
|
||||||
|
func (r extensionResolver) FindExtensionByName(field protoreflect.FullName) (protoreflect.ExtensionType, error) {
|
||||||
|
if xtd := r.xt.TypeDescriptor(); xtd.FullName() == field {
|
||||||
|
return r.xt, nil
|
||||||
|
}
|
||||||
|
return protoregistry.GlobalTypes.FindExtensionByName(field)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r extensionResolver) FindExtensionByNumber(message protoreflect.FullName, field protoreflect.FieldNumber) (protoreflect.ExtensionType, error) {
|
||||||
|
if xtd := r.xt.TypeDescriptor(); xtd.ContainingMessage().FullName() == message && xtd.Number() == field {
|
||||||
|
return r.xt, nil
|
||||||
|
}
|
||||||
|
return protoregistry.GlobalTypes.FindExtensionByNumber(message, field)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExtensions returns a list of the extensions values present in m,
|
||||||
|
// corresponding with the provided list of extension descriptors, xts.
|
||||||
|
// If an extension is missing in m, the corresponding value is nil.
|
||||||
|
func GetExtensions(m Message, xts []*ExtensionDesc) ([]interface{}, error) {
|
||||||
|
mr := MessageReflect(m)
|
||||||
|
if mr == nil || !mr.IsValid() {
|
||||||
|
return nil, errNotExtendable
|
||||||
|
}
|
||||||
|
|
||||||
|
vs := make([]interface{}, len(xts))
|
||||||
|
for i, xt := range xts {
|
||||||
|
v, err := GetExtension(m, xt)
|
||||||
|
if err != nil {
|
||||||
|
if err == ErrMissingExtension {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return vs, err
|
||||||
|
}
|
||||||
|
vs[i] = v
|
||||||
|
}
|
||||||
|
return vs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetExtension sets an extension field in m to the provided value.
|
||||||
|
func SetExtension(m Message, xt *ExtensionDesc, v interface{}) error {
|
||||||
|
mr := MessageReflect(m)
|
||||||
|
if mr == nil || !mr.IsValid() || mr.Descriptor().ExtensionRanges().Len() == 0 {
|
||||||
|
return errNotExtendable
|
||||||
|
}
|
||||||
|
|
||||||
|
rv := reflect.ValueOf(v)
|
||||||
|
if reflect.TypeOf(v) != reflect.TypeOf(xt.ExtensionType) {
|
||||||
|
return fmt.Errorf("proto: bad extension value type. got: %T, want: %T", v, xt.ExtensionType)
|
||||||
|
}
|
||||||
|
if rv.Kind() == reflect.Ptr {
|
||||||
|
if rv.IsNil() {
|
||||||
|
return fmt.Errorf("proto: SetExtension called with nil value of type %T", v)
|
||||||
|
}
|
||||||
|
if isScalarKind(rv.Elem().Kind()) {
|
||||||
|
v = rv.Elem().Interface()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xtd := xt.TypeDescriptor()
|
||||||
|
if !isValidExtension(mr.Descriptor(), xtd) {
|
||||||
|
return fmt.Errorf("proto: bad extended type; %T does not extend %T", xt.ExtendedType, m)
|
||||||
|
}
|
||||||
|
mr.Set(xtd, xt.ValueOf(v))
|
||||||
|
clearUnknown(mr, fieldNum(xt.Field))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRawExtension inserts b into the unknown fields of m.
|
||||||
|
//
|
||||||
|
// Deprecated: Use Message.ProtoReflect.SetUnknown instead.
|
||||||
|
func SetRawExtension(m Message, fnum int32, b []byte) {
|
||||||
|
mr := MessageReflect(m)
|
||||||
|
if mr == nil || !mr.IsValid() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that the raw field is valid.
|
||||||
|
for b0 := b; len(b0) > 0; {
|
||||||
|
num, _, n := protowire.ConsumeField(b0)
|
||||||
|
if int32(num) != fnum {
|
||||||
|
panic(fmt.Sprintf("mismatching field number: got %d, want %d", num, fnum))
|
||||||
|
}
|
||||||
|
b0 = b0[n:]
|
||||||
|
}
|
||||||
|
|
||||||
|
ClearExtension(m, &ExtensionDesc{Field: fnum})
|
||||||
|
mr.SetUnknown(append(mr.GetUnknown(), b...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtensionDescs returns a list of extension descriptors found in m,
|
||||||
|
// containing descriptors for both populated extension fields in m and
|
||||||
|
// also unknown fields of m that are in the extension range.
|
||||||
|
// For the later case, an type incomplete descriptor is provided where only
|
||||||
|
// the ExtensionDesc.Field field is populated.
|
||||||
|
// The order of the extension descriptors is undefined.
|
||||||
|
func ExtensionDescs(m Message) ([]*ExtensionDesc, error) {
|
||||||
|
mr := MessageReflect(m)
|
||||||
|
if mr == nil || !mr.IsValid() || mr.Descriptor().ExtensionRanges().Len() == 0 {
|
||||||
|
return nil, errNotExtendable
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect a set of known extension descriptors.
|
||||||
|
extDescs := make(map[protoreflect.FieldNumber]*ExtensionDesc)
|
||||||
|
mr.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
|
||||||
|
if fd.IsExtension() {
|
||||||
|
xt := fd.(protoreflect.ExtensionTypeDescriptor)
|
||||||
|
if xd, ok := xt.Type().(*ExtensionDesc); ok {
|
||||||
|
extDescs[fd.Number()] = xd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
// Collect a set of unknown extension descriptors.
|
||||||
|
extRanges := mr.Descriptor().ExtensionRanges()
|
||||||
|
for b := mr.GetUnknown(); len(b) > 0; {
|
||||||
|
num, _, n := protowire.ConsumeField(b)
|
||||||
|
if extRanges.Has(num) && extDescs[num] == nil {
|
||||||
|
extDescs[num] = nil
|
||||||
|
}
|
||||||
|
b = b[n:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transpose the set of descriptors into a list.
|
||||||
|
var xts []*ExtensionDesc
|
||||||
|
for num, xt := range extDescs {
|
||||||
|
if xt == nil {
|
||||||
|
xt = &ExtensionDesc{Field: int32(num)}
|
||||||
|
}
|
||||||
|
xts = append(xts, xt)
|
||||||
|
}
|
||||||
|
return xts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// isValidExtension reports whether xtd is a valid extension descriptor for md.
|
||||||
|
func isValidExtension(md protoreflect.MessageDescriptor, xtd protoreflect.ExtensionTypeDescriptor) bool {
|
||||||
|
return xtd.ContainingMessage() == md && md.ExtensionRanges().Has(xtd.Number())
|
||||||
|
}
|
||||||
|
|
||||||
|
// isScalarKind reports whether k is a protobuf scalar kind (except bytes).
|
||||||
|
// This function exists for historical reasons since the representation of
|
||||||
|
// scalars differs between v1 and v2, where v1 uses *T and v2 uses T.
|
||||||
|
func isScalarKind(k reflect.Kind) bool {
|
||||||
|
switch k {
|
||||||
|
case reflect.Bool, reflect.Int32, reflect.Int64, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64, reflect.String:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// clearUnknown removes unknown fields from m where remover.Has reports true.
|
||||||
|
func clearUnknown(m protoreflect.Message, remover interface {
|
||||||
|
Has(protoreflect.FieldNumber) bool
|
||||||
|
}) {
|
||||||
|
var bo protoreflect.RawFields
|
||||||
|
for bi := m.GetUnknown(); len(bi) > 0; {
|
||||||
|
num, _, n := protowire.ConsumeField(bi)
|
||||||
|
if !remover.Has(num) {
|
||||||
|
bo = append(bo, bi[:n]...)
|
||||||
|
}
|
||||||
|
bi = bi[n:]
|
||||||
|
}
|
||||||
|
if bi := m.GetUnknown(); len(bi) != len(bo) {
|
||||||
|
m.SetUnknown(bo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type fieldNum protoreflect.FieldNumber
|
||||||
|
|
||||||
|
func (n1 fieldNum) Has(n2 protoreflect.FieldNumber) bool {
|
||||||
|
return protoreflect.FieldNumber(n1) == n2
|
||||||
|
}
|
306
vendor/github.com/golang/protobuf/proto/properties.go
generated
vendored
Normal file
306
vendor/github.com/golang/protobuf/proto/properties.go
generated
vendored
Normal file
|
@ -0,0 +1,306 @@
|
||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
"google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StructProperties represents protocol buffer type information for a
|
||||||
|
// generated protobuf message in the open-struct API.
|
||||||
|
//
|
||||||
|
// Deprecated: Do not use.
|
||||||
|
type StructProperties struct {
|
||||||
|
// Prop are the properties for each field.
|
||||||
|
//
|
||||||
|
// Fields belonging to a oneof are stored in OneofTypes instead, with a
|
||||||
|
// single Properties representing the parent oneof held here.
|
||||||
|
//
|
||||||
|
// The order of Prop matches the order of fields in the Go struct.
|
||||||
|
// Struct fields that are not related to protobufs have a "XXX_" prefix
|
||||||
|
// in the Properties.Name and must be ignored by the user.
|
||||||
|
Prop []*Properties
|
||||||
|
|
||||||
|
// OneofTypes contains information about the oneof fields in this message.
|
||||||
|
// It is keyed by the protobuf field name.
|
||||||
|
OneofTypes map[string]*OneofProperties
|
||||||
|
}
|
||||||
|
|
||||||
|
// Properties represents the type information for a protobuf message field.
|
||||||
|
//
|
||||||
|
// Deprecated: Do not use.
|
||||||
|
type Properties struct {
|
||||||
|
// Name is a placeholder name with little meaningful semantic value.
|
||||||
|
// If the name has an "XXX_" prefix, the entire Properties must be ignored.
|
||||||
|
Name string
|
||||||
|
// OrigName is the protobuf field name or oneof name.
|
||||||
|
OrigName string
|
||||||
|
// JSONName is the JSON name for the protobuf field.
|
||||||
|
JSONName string
|
||||||
|
// Enum is a placeholder name for enums.
|
||||||
|
// For historical reasons, this is neither the Go name for the enum,
|
||||||
|
// nor the protobuf name for the enum.
|
||||||
|
Enum string // Deprecated: Do not use.
|
||||||
|
// Weak contains the full name of the weakly referenced message.
|
||||||
|
Weak string
|
||||||
|
// Wire is a string representation of the wire type.
|
||||||
|
Wire string
|
||||||
|
// WireType is the protobuf wire type for the field.
|
||||||
|
WireType int
|
||||||
|
// Tag is the protobuf field number.
|
||||||
|
Tag int
|
||||||
|
// Required reports whether this is a required field.
|
||||||
|
Required bool
|
||||||
|
// Optional reports whether this is a optional field.
|
||||||
|
Optional bool
|
||||||
|
// Repeated reports whether this is a repeated field.
|
||||||
|
Repeated bool
|
||||||
|
// Packed reports whether this is a packed repeated field of scalars.
|
||||||
|
Packed bool
|
||||||
|
// Proto3 reports whether this field operates under the proto3 syntax.
|
||||||
|
Proto3 bool
|
||||||
|
// Oneof reports whether this field belongs within a oneof.
|
||||||
|
Oneof bool
|
||||||
|
|
||||||
|
// Default is the default value in string form.
|
||||||
|
Default string
|
||||||
|
// HasDefault reports whether the field has a default value.
|
||||||
|
HasDefault bool
|
||||||
|
|
||||||
|
// MapKeyProp is the properties for the key field for a map field.
|
||||||
|
MapKeyProp *Properties
|
||||||
|
// MapValProp is the properties for the value field for a map field.
|
||||||
|
MapValProp *Properties
|
||||||
|
}
|
||||||
|
|
||||||
|
// OneofProperties represents the type information for a protobuf oneof.
|
||||||
|
//
|
||||||
|
// Deprecated: Do not use.
|
||||||
|
type OneofProperties struct {
|
||||||
|
// Type is a pointer to the generated wrapper type for the field value.
|
||||||
|
// This is nil for messages that are not in the open-struct API.
|
||||||
|
Type reflect.Type
|
||||||
|
// Field is the index into StructProperties.Prop for the containing oneof.
|
||||||
|
Field int
|
||||||
|
// Prop is the properties for the field.
|
||||||
|
Prop *Properties
|
||||||
|
}
|
||||||
|
|
||||||
|
// String formats the properties in the protobuf struct field tag style.
|
||||||
|
func (p *Properties) String() string {
|
||||||
|
s := p.Wire
|
||||||
|
s += "," + strconv.Itoa(p.Tag)
|
||||||
|
if p.Required {
|
||||||
|
s += ",req"
|
||||||
|
}
|
||||||
|
if p.Optional {
|
||||||
|
s += ",opt"
|
||||||
|
}
|
||||||
|
if p.Repeated {
|
||||||
|
s += ",rep"
|
||||||
|
}
|
||||||
|
if p.Packed {
|
||||||
|
s += ",packed"
|
||||||
|
}
|
||||||
|
s += ",name=" + p.OrigName
|
||||||
|
if p.JSONName != "" {
|
||||||
|
s += ",json=" + p.JSONName
|
||||||
|
}
|
||||||
|
if len(p.Enum) > 0 {
|
||||||
|
s += ",enum=" + p.Enum
|
||||||
|
}
|
||||||
|
if len(p.Weak) > 0 {
|
||||||
|
s += ",weak=" + p.Weak
|
||||||
|
}
|
||||||
|
if p.Proto3 {
|
||||||
|
s += ",proto3"
|
||||||
|
}
|
||||||
|
if p.Oneof {
|
||||||
|
s += ",oneof"
|
||||||
|
}
|
||||||
|
if p.HasDefault {
|
||||||
|
s += ",def=" + p.Default
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse populates p by parsing a string in the protobuf struct field tag style.
|
||||||
|
func (p *Properties) Parse(tag string) {
|
||||||
|
// For example: "bytes,49,opt,name=foo,def=hello!"
|
||||||
|
for len(tag) > 0 {
|
||||||
|
i := strings.IndexByte(tag, ',')
|
||||||
|
if i < 0 {
|
||||||
|
i = len(tag)
|
||||||
|
}
|
||||||
|
switch s := tag[:i]; {
|
||||||
|
case strings.HasPrefix(s, "name="):
|
||||||
|
p.OrigName = s[len("name="):]
|
||||||
|
case strings.HasPrefix(s, "json="):
|
||||||
|
p.JSONName = s[len("json="):]
|
||||||
|
case strings.HasPrefix(s, "enum="):
|
||||||
|
p.Enum = s[len("enum="):]
|
||||||
|
case strings.HasPrefix(s, "weak="):
|
||||||
|
p.Weak = s[len("weak="):]
|
||||||
|
case strings.Trim(s, "0123456789") == "":
|
||||||
|
n, _ := strconv.ParseUint(s, 10, 32)
|
||||||
|
p.Tag = int(n)
|
||||||
|
case s == "opt":
|
||||||
|
p.Optional = true
|
||||||
|
case s == "req":
|
||||||
|
p.Required = true
|
||||||
|
case s == "rep":
|
||||||
|
p.Repeated = true
|
||||||
|
case s == "varint" || s == "zigzag32" || s == "zigzag64":
|
||||||
|
p.Wire = s
|
||||||
|
p.WireType = WireVarint
|
||||||
|
case s == "fixed32":
|
||||||
|
p.Wire = s
|
||||||
|
p.WireType = WireFixed32
|
||||||
|
case s == "fixed64":
|
||||||
|
p.Wire = s
|
||||||
|
p.WireType = WireFixed64
|
||||||
|
case s == "bytes":
|
||||||
|
p.Wire = s
|
||||||
|
p.WireType = WireBytes
|
||||||
|
case s == "group":
|
||||||
|
p.Wire = s
|
||||||
|
p.WireType = WireStartGroup
|
||||||
|
case s == "packed":
|
||||||
|
p.Packed = true
|
||||||
|
case s == "proto3":
|
||||||
|
p.Proto3 = true
|
||||||
|
case s == "oneof":
|
||||||
|
p.Oneof = true
|
||||||
|
case strings.HasPrefix(s, "def="):
|
||||||
|
// The default tag is special in that everything afterwards is the
|
||||||
|
// default regardless of the presence of commas.
|
||||||
|
p.HasDefault = true
|
||||||
|
p.Default, i = tag[len("def="):], len(tag)
|
||||||
|
}
|
||||||
|
tag = strings.TrimPrefix(tag[i:], ",")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init populates the properties from a protocol buffer struct tag.
|
||||||
|
//
|
||||||
|
// Deprecated: Do not use.
|
||||||
|
func (p *Properties) Init(typ reflect.Type, name, tag string, f *reflect.StructField) {
|
||||||
|
p.Name = name
|
||||||
|
p.OrigName = name
|
||||||
|
if tag == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.Parse(tag)
|
||||||
|
|
||||||
|
if typ != nil && typ.Kind() == reflect.Map {
|
||||||
|
p.MapKeyProp = new(Properties)
|
||||||
|
p.MapKeyProp.Init(nil, "Key", f.Tag.Get("protobuf_key"), nil)
|
||||||
|
p.MapValProp = new(Properties)
|
||||||
|
p.MapValProp.Init(nil, "Value", f.Tag.Get("protobuf_val"), nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var propertiesCache sync.Map // map[reflect.Type]*StructProperties
|
||||||
|
|
||||||
|
// GetProperties returns the list of properties for the type represented by t,
|
||||||
|
// which must be a generated protocol buffer message in the open-struct API,
|
||||||
|
// where protobuf message fields are represented by exported Go struct fields.
|
||||||
|
//
|
||||||
|
// Deprecated: Use protobuf reflection instead.
|
||||||
|
func GetProperties(t reflect.Type) *StructProperties {
|
||||||
|
if p, ok := propertiesCache.Load(t); ok {
|
||||||
|
return p.(*StructProperties)
|
||||||
|
}
|
||||||
|
p, _ := propertiesCache.LoadOrStore(t, newProperties(t))
|
||||||
|
return p.(*StructProperties)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newProperties(t reflect.Type) *StructProperties {
|
||||||
|
if t.Kind() != reflect.Struct {
|
||||||
|
panic(fmt.Sprintf("%v is not a generated message in the open-struct API", t))
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasOneof bool
|
||||||
|
prop := new(StructProperties)
|
||||||
|
|
||||||
|
// Construct a list of properties for each field in the struct.
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
p := new(Properties)
|
||||||
|
f := t.Field(i)
|
||||||
|
tagField := f.Tag.Get("protobuf")
|
||||||
|
p.Init(f.Type, f.Name, tagField, &f)
|
||||||
|
|
||||||
|
tagOneof := f.Tag.Get("protobuf_oneof")
|
||||||
|
if tagOneof != "" {
|
||||||
|
hasOneof = true
|
||||||
|
p.OrigName = tagOneof
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rename unrelated struct fields with the "XXX_" prefix since so much
|
||||||
|
// user code simply checks for this to exclude special fields.
|
||||||
|
if tagField == "" && tagOneof == "" && !strings.HasPrefix(p.Name, "XXX_") {
|
||||||
|
p.Name = "XXX_" + p.Name
|
||||||
|
p.OrigName = "XXX_" + p.OrigName
|
||||||
|
} else if p.Weak != "" {
|
||||||
|
p.Name = p.OrigName // avoid possible "XXX_" prefix on weak field
|
||||||
|
}
|
||||||
|
|
||||||
|
prop.Prop = append(prop.Prop, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct a mapping of oneof field names to properties.
|
||||||
|
if hasOneof {
|
||||||
|
var oneofWrappers []interface{}
|
||||||
|
if fn, ok := reflect.PtrTo(t).MethodByName("XXX_OneofFuncs"); ok {
|
||||||
|
oneofWrappers = fn.Func.Call([]reflect.Value{reflect.Zero(fn.Type.In(0))})[3].Interface().([]interface{})
|
||||||
|
}
|
||||||
|
if fn, ok := reflect.PtrTo(t).MethodByName("XXX_OneofWrappers"); ok {
|
||||||
|
oneofWrappers = fn.Func.Call([]reflect.Value{reflect.Zero(fn.Type.In(0))})[0].Interface().([]interface{})
|
||||||
|
}
|
||||||
|
if m, ok := reflect.Zero(reflect.PtrTo(t)).Interface().(protoreflect.ProtoMessage); ok {
|
||||||
|
if m, ok := m.ProtoReflect().(interface{ ProtoMessageInfo() *protoimpl.MessageInfo }); ok {
|
||||||
|
oneofWrappers = m.ProtoMessageInfo().OneofWrappers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prop.OneofTypes = make(map[string]*OneofProperties)
|
||||||
|
for _, wrapper := range oneofWrappers {
|
||||||
|
p := &OneofProperties{
|
||||||
|
Type: reflect.ValueOf(wrapper).Type(), // *T
|
||||||
|
Prop: new(Properties),
|
||||||
|
}
|
||||||
|
f := p.Type.Elem().Field(0)
|
||||||
|
p.Prop.Name = f.Name
|
||||||
|
p.Prop.Parse(f.Tag.Get("protobuf"))
|
||||||
|
|
||||||
|
// Determine the struct field that contains this oneof.
|
||||||
|
// Each wrapper is assignable to exactly one parent field.
|
||||||
|
var foundOneof bool
|
||||||
|
for i := 0; i < t.NumField() && !foundOneof; i++ {
|
||||||
|
if p.Type.AssignableTo(t.Field(i).Type) {
|
||||||
|
p.Field = i
|
||||||
|
foundOneof = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !foundOneof {
|
||||||
|
panic(fmt.Sprintf("%v is not a generated message in the open-struct API", t))
|
||||||
|
}
|
||||||
|
prop.OneofTypes[p.Prop.OrigName] = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return prop
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *StructProperties) Len() int { return len(sp.Prop) }
|
||||||
|
func (sp *StructProperties) Less(i, j int) bool { return false }
|
||||||
|
func (sp *StructProperties) Swap(i, j int) { return }
|
167
vendor/github.com/golang/protobuf/proto/proto.go
generated
vendored
Normal file
167
vendor/github.com/golang/protobuf/proto/proto.go
generated
vendored
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
// Copyright 2019 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package proto provides functionality for handling protocol buffer messages.
|
||||||
|
// In particular, it provides marshaling and unmarshaling between a protobuf
|
||||||
|
// message and the binary wire format.
|
||||||
|
//
|
||||||
|
// See https://developers.google.com/protocol-buffers/docs/gotutorial for
|
||||||
|
// more information.
|
||||||
|
//
|
||||||
|
// Deprecated: Use the "google.golang.org/protobuf/proto" package instead.
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
protoV2 "google.golang.org/protobuf/proto"
|
||||||
|
"google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
"google.golang.org/protobuf/runtime/protoiface"
|
||||||
|
"google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ProtoPackageIsVersion1 = true
|
||||||
|
ProtoPackageIsVersion2 = true
|
||||||
|
ProtoPackageIsVersion3 = true
|
||||||
|
ProtoPackageIsVersion4 = true
|
||||||
|
)
|
||||||
|
|
||||||
|
// GeneratedEnum is any enum type generated by protoc-gen-go
|
||||||
|
// which is a named int32 kind.
|
||||||
|
// This type exists for documentation purposes.
|
||||||
|
type GeneratedEnum interface{}
|
||||||
|
|
||||||
|
// GeneratedMessage is any message type generated by protoc-gen-go
|
||||||
|
// which is a pointer to a named struct kind.
|
||||||
|
// This type exists for documentation purposes.
|
||||||
|
type GeneratedMessage interface{}
|
||||||
|
|
||||||
|
// Message is a protocol buffer message.
|
||||||
|
//
|
||||||
|
// This is the v1 version of the message interface and is marginally better
|
||||||
|
// than an empty interface as it lacks any method to programatically interact
|
||||||
|
// with the contents of the message.
|
||||||
|
//
|
||||||
|
// A v2 message is declared in "google.golang.org/protobuf/proto".Message and
|
||||||
|
// exposes protobuf reflection as a first-class feature of the interface.
|
||||||
|
//
|
||||||
|
// To convert a v1 message to a v2 message, use the MessageV2 function.
|
||||||
|
// To convert a v2 message to a v1 message, use the MessageV1 function.
|
||||||
|
type Message = protoiface.MessageV1
|
||||||
|
|
||||||
|
// MessageV1 converts either a v1 or v2 message to a v1 message.
|
||||||
|
// It returns nil if m is nil.
|
||||||
|
func MessageV1(m GeneratedMessage) protoiface.MessageV1 {
|
||||||
|
return protoimpl.X.ProtoMessageV1Of(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageV2 converts either a v1 or v2 message to a v2 message.
|
||||||
|
// It returns nil if m is nil.
|
||||||
|
func MessageV2(m GeneratedMessage) protoV2.Message {
|
||||||
|
return protoimpl.X.ProtoMessageV2Of(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageReflect returns a reflective view for a message.
|
||||||
|
// It returns nil if m is nil.
|
||||||
|
func MessageReflect(m Message) protoreflect.Message {
|
||||||
|
return protoimpl.X.MessageOf(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshaler is implemented by messages that can marshal themselves.
|
||||||
|
// This interface is used by the following functions: Size, Marshal,
|
||||||
|
// Buffer.Marshal, and Buffer.EncodeMessage.
|
||||||
|
//
|
||||||
|
// Deprecated: Do not implement.
|
||||||
|
type Marshaler interface {
|
||||||
|
// Marshal formats the encoded bytes of the message.
|
||||||
|
// It should be deterministic and emit valid protobuf wire data.
|
||||||
|
// The caller takes ownership of the returned buffer.
|
||||||
|
Marshal() ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshaler is implemented by messages that can unmarshal themselves.
|
||||||
|
// This interface is used by the following functions: Unmarshal, UnmarshalMerge,
|
||||||
|
// Buffer.Unmarshal, Buffer.DecodeMessage, and Buffer.DecodeGroup.
|
||||||
|
//
|
||||||
|
// Deprecated: Do not implement.
|
||||||
|
type Unmarshaler interface {
|
||||||
|
// Unmarshal parses the encoded bytes of the protobuf wire input.
|
||||||
|
// The provided buffer is only valid for during method call.
|
||||||
|
// It should not reset the receiver message.
|
||||||
|
Unmarshal([]byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merger is implemented by messages that can merge themselves.
|
||||||
|
// This interface is used by the following functions: Clone and Merge.
|
||||||
|
//
|
||||||
|
// Deprecated: Do not implement.
|
||||||
|
type Merger interface {
|
||||||
|
// Merge merges the contents of src into the receiver message.
|
||||||
|
// It clones all data structures in src such that it aliases no mutable
|
||||||
|
// memory referenced by src.
|
||||||
|
Merge(src Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequiredNotSetError is an error type returned when
|
||||||
|
// marshaling or unmarshaling a message with missing required fields.
|
||||||
|
type RequiredNotSetError struct {
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *RequiredNotSetError) Error() string {
|
||||||
|
if e.err != nil {
|
||||||
|
return e.err.Error()
|
||||||
|
}
|
||||||
|
return "proto: required field not set"
|
||||||
|
}
|
||||||
|
func (e *RequiredNotSetError) RequiredNotSet() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkRequiredNotSet(m protoV2.Message) error {
|
||||||
|
if err := protoV2.CheckInitialized(m); err != nil {
|
||||||
|
return &RequiredNotSetError{err: err}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone returns a deep copy of src.
|
||||||
|
func Clone(src Message) Message {
|
||||||
|
return MessageV1(protoV2.Clone(MessageV2(src)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge merges src into dst, which must be messages of the same type.
|
||||||
|
//
|
||||||
|
// Populated scalar fields in src are copied to dst, while populated
|
||||||
|
// singular messages in src are merged into dst by recursively calling Merge.
|
||||||
|
// The elements of every list field in src is appended to the corresponded
|
||||||
|
// list fields in dst. The entries of every map field in src is copied into
|
||||||
|
// the corresponding map field in dst, possibly replacing existing entries.
|
||||||
|
// The unknown fields of src are appended to the unknown fields of dst.
|
||||||
|
func Merge(dst, src Message) {
|
||||||
|
protoV2.Merge(MessageV2(dst), MessageV2(src))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal reports whether two messages are equal.
|
||||||
|
// If two messages marshal to the same bytes under deterministic serialization,
|
||||||
|
// then Equal is guaranteed to report true.
|
||||||
|
//
|
||||||
|
// Two messages are equal if they are the same protobuf message type,
|
||||||
|
// have the same set of populated known and extension field values,
|
||||||
|
// and the same set of unknown fields values.
|
||||||
|
//
|
||||||
|
// Scalar values are compared with the equivalent of the == operator in Go,
|
||||||
|
// except bytes values which are compared using bytes.Equal and
|
||||||
|
// floating point values which specially treat NaNs as equal.
|
||||||
|
// Message values are compared by recursively calling Equal.
|
||||||
|
// Lists are equal if each element value is also equal.
|
||||||
|
// Maps are equal if they have the same set of keys, where the pair of values
|
||||||
|
// for each key is also equal.
|
||||||
|
func Equal(x, y Message) bool {
|
||||||
|
return protoV2.Equal(MessageV2(x), MessageV2(y))
|
||||||
|
}
|
||||||
|
|
||||||
|
func isMessageSet(md protoreflect.MessageDescriptor) bool {
|
||||||
|
ms, ok := md.(interface{ IsMessageSet() bool })
|
||||||
|
return ok && ms.IsMessageSet()
|
||||||
|
}
|
317
vendor/github.com/golang/protobuf/proto/registry.go
generated
vendored
Normal file
317
vendor/github.com/golang/protobuf/proto/registry.go
generated
vendored
Normal file
|
@ -0,0 +1,317 @@
|
||||||
|
// Copyright 2019 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"google.golang.org/protobuf/reflect/protodesc"
|
||||||
|
"google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
"google.golang.org/protobuf/reflect/protoregistry"
|
||||||
|
"google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
)
|
||||||
|
|
||||||
|
// filePath is the path to the proto source file.
|
||||||
|
type filePath = string // e.g., "google/protobuf/descriptor.proto"
|
||||||
|
|
||||||
|
// fileDescGZIP is the compressed contents of the encoded FileDescriptorProto.
|
||||||
|
type fileDescGZIP = []byte
|
||||||
|
|
||||||
|
var fileCache sync.Map // map[filePath]fileDescGZIP
|
||||||
|
|
||||||
|
// RegisterFile is called from generated code to register the compressed
|
||||||
|
// FileDescriptorProto with the file path for a proto source file.
|
||||||
|
//
|
||||||
|
// Deprecated: Use protoregistry.GlobalFiles.RegisterFile instead.
|
||||||
|
func RegisterFile(s filePath, d fileDescGZIP) {
|
||||||
|
// Decompress the descriptor.
|
||||||
|
zr, err := gzip.NewReader(bytes.NewReader(d))
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("proto: invalid compressed file descriptor: %v", err))
|
||||||
|
}
|
||||||
|
b, err := ioutil.ReadAll(zr)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("proto: invalid compressed file descriptor: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct a protoreflect.FileDescriptor from the raw descriptor.
|
||||||
|
// Note that DescBuilder.Build automatically registers the constructed
|
||||||
|
// file descriptor with the v2 registry.
|
||||||
|
protoimpl.DescBuilder{RawDescriptor: b}.Build()
|
||||||
|
|
||||||
|
// Locally cache the raw descriptor form for the file.
|
||||||
|
fileCache.Store(s, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileDescriptor returns the compressed FileDescriptorProto given the file path
|
||||||
|
// for a proto source file. It returns nil if not found.
|
||||||
|
//
|
||||||
|
// Deprecated: Use protoregistry.GlobalFiles.FindFileByPath instead.
|
||||||
|
func FileDescriptor(s filePath) fileDescGZIP {
|
||||||
|
if v, ok := fileCache.Load(s); ok {
|
||||||
|
return v.(fileDescGZIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the descriptor in the v2 registry.
|
||||||
|
var b []byte
|
||||||
|
if fd, _ := protoregistry.GlobalFiles.FindFileByPath(s); fd != nil {
|
||||||
|
b, _ = Marshal(protodesc.ToFileDescriptorProto(fd))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Locally cache the raw descriptor form for the file.
|
||||||
|
if len(b) > 0 {
|
||||||
|
v, _ := fileCache.LoadOrStore(s, protoimpl.X.CompressGZIP(b))
|
||||||
|
return v.(fileDescGZIP)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// enumName is the name of an enum. For historical reasons, the enum name is
|
||||||
|
// neither the full Go name nor the full protobuf name of the enum.
|
||||||
|
// The name is the dot-separated combination of just the proto package that the
|
||||||
|
// enum is declared within followed by the Go type name of the generated enum.
|
||||||
|
type enumName = string // e.g., "my.proto.package.GoMessage_GoEnum"
|
||||||
|
|
||||||
|
// enumsByName maps enum values by name to their numeric counterpart.
|
||||||
|
type enumsByName = map[string]int32
|
||||||
|
|
||||||
|
// enumsByNumber maps enum values by number to their name counterpart.
|
||||||
|
type enumsByNumber = map[int32]string
|
||||||
|
|
||||||
|
var enumCache sync.Map // map[enumName]enumsByName
|
||||||
|
var numFilesCache sync.Map // map[protoreflect.FullName]int
|
||||||
|
|
||||||
|
// RegisterEnum is called from the generated code to register the mapping of
|
||||||
|
// enum value names to enum numbers for the enum identified by s.
|
||||||
|
//
|
||||||
|
// Deprecated: Use protoregistry.GlobalTypes.RegisterEnum instead.
|
||||||
|
func RegisterEnum(s enumName, _ enumsByNumber, m enumsByName) {
|
||||||
|
if _, ok := enumCache.Load(s); ok {
|
||||||
|
panic("proto: duplicate enum registered: " + s)
|
||||||
|
}
|
||||||
|
enumCache.Store(s, m)
|
||||||
|
|
||||||
|
// This does not forward registration to the v2 registry since this API
|
||||||
|
// lacks sufficient information to construct a complete v2 enum descriptor.
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnumValueMap returns the mapping from enum value names to enum numbers for
|
||||||
|
// the enum of the given name. It returns nil if not found.
|
||||||
|
//
|
||||||
|
// Deprecated: Use protoregistry.GlobalTypes.FindEnumByName instead.
|
||||||
|
func EnumValueMap(s enumName) enumsByName {
|
||||||
|
if v, ok := enumCache.Load(s); ok {
|
||||||
|
return v.(enumsByName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether the cache is stale. If the number of files in the current
|
||||||
|
// package differs, then it means that some enums may have been recently
|
||||||
|
// registered upstream that we do not know about.
|
||||||
|
var protoPkg protoreflect.FullName
|
||||||
|
if i := strings.LastIndexByte(s, '.'); i >= 0 {
|
||||||
|
protoPkg = protoreflect.FullName(s[:i])
|
||||||
|
}
|
||||||
|
v, _ := numFilesCache.Load(protoPkg)
|
||||||
|
numFiles, _ := v.(int)
|
||||||
|
if protoregistry.GlobalFiles.NumFilesByPackage(protoPkg) == numFiles {
|
||||||
|
return nil // cache is up-to-date; was not found earlier
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the enum cache for all enums declared in the given proto package.
|
||||||
|
numFiles = 0
|
||||||
|
protoregistry.GlobalFiles.RangeFilesByPackage(protoPkg, func(fd protoreflect.FileDescriptor) bool {
|
||||||
|
walkEnums(fd, func(ed protoreflect.EnumDescriptor) {
|
||||||
|
name := protoimpl.X.LegacyEnumName(ed)
|
||||||
|
if _, ok := enumCache.Load(name); !ok {
|
||||||
|
m := make(enumsByName)
|
||||||
|
evs := ed.Values()
|
||||||
|
for i := evs.Len() - 1; i >= 0; i-- {
|
||||||
|
ev := evs.Get(i)
|
||||||
|
m[string(ev.Name())] = int32(ev.Number())
|
||||||
|
}
|
||||||
|
enumCache.LoadOrStore(name, m)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
numFiles++
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
numFilesCache.Store(protoPkg, numFiles)
|
||||||
|
|
||||||
|
// Check cache again for enum map.
|
||||||
|
if v, ok := enumCache.Load(s); ok {
|
||||||
|
return v.(enumsByName)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// walkEnums recursively walks all enums declared in d.
|
||||||
|
func walkEnums(d interface {
|
||||||
|
Enums() protoreflect.EnumDescriptors
|
||||||
|
Messages() protoreflect.MessageDescriptors
|
||||||
|
}, f func(protoreflect.EnumDescriptor)) {
|
||||||
|
eds := d.Enums()
|
||||||
|
for i := eds.Len() - 1; i >= 0; i-- {
|
||||||
|
f(eds.Get(i))
|
||||||
|
}
|
||||||
|
mds := d.Messages()
|
||||||
|
for i := mds.Len() - 1; i >= 0; i-- {
|
||||||
|
walkEnums(mds.Get(i), f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// messageName is the full name of protobuf message.
|
||||||
|
type messageName = string
|
||||||
|
|
||||||
|
var messageTypeCache sync.Map // map[messageName]reflect.Type
|
||||||
|
|
||||||
|
// RegisterType is called from generated code to register the message Go type
|
||||||
|
// for a message of the given name.
|
||||||
|
//
|
||||||
|
// Deprecated: Use protoregistry.GlobalTypes.RegisterMessage instead.
|
||||||
|
func RegisterType(m Message, s messageName) {
|
||||||
|
mt := protoimpl.X.LegacyMessageTypeOf(m, protoreflect.FullName(s))
|
||||||
|
if err := protoregistry.GlobalTypes.RegisterMessage(mt); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
messageTypeCache.Store(s, reflect.TypeOf(m))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterMapType is called from generated code to register the Go map type
|
||||||
|
// for a protobuf message representing a map entry.
|
||||||
|
//
|
||||||
|
// Deprecated: Do not use.
|
||||||
|
func RegisterMapType(m interface{}, s messageName) {
|
||||||
|
t := reflect.TypeOf(m)
|
||||||
|
if t.Kind() != reflect.Map {
|
||||||
|
panic(fmt.Sprintf("invalid map kind: %v", t))
|
||||||
|
}
|
||||||
|
if _, ok := messageTypeCache.Load(s); ok {
|
||||||
|
panic(fmt.Errorf("proto: duplicate proto message registered: %s", s))
|
||||||
|
}
|
||||||
|
messageTypeCache.Store(s, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageType returns the message type for a named message.
|
||||||
|
// It returns nil if not found.
|
||||||
|
//
|
||||||
|
// Deprecated: Use protoregistry.GlobalTypes.FindMessageByName instead.
|
||||||
|
func MessageType(s messageName) reflect.Type {
|
||||||
|
if v, ok := messageTypeCache.Load(s); ok {
|
||||||
|
return v.(reflect.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Derive the message type from the v2 registry.
|
||||||
|
var t reflect.Type
|
||||||
|
if mt, _ := protoregistry.GlobalTypes.FindMessageByName(protoreflect.FullName(s)); mt != nil {
|
||||||
|
t = messageGoType(mt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we could not get a concrete type, it is possible that it is a
|
||||||
|
// pseudo-message for a map entry.
|
||||||
|
if t == nil {
|
||||||
|
d, _ := protoregistry.GlobalFiles.FindDescriptorByName(protoreflect.FullName(s))
|
||||||
|
if md, _ := d.(protoreflect.MessageDescriptor); md != nil && md.IsMapEntry() {
|
||||||
|
kt := goTypeForField(md.Fields().ByNumber(1))
|
||||||
|
vt := goTypeForField(md.Fields().ByNumber(2))
|
||||||
|
t = reflect.MapOf(kt, vt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Locally cache the message type for the given name.
|
||||||
|
if t != nil {
|
||||||
|
v, _ := messageTypeCache.LoadOrStore(s, t)
|
||||||
|
return v.(reflect.Type)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func goTypeForField(fd protoreflect.FieldDescriptor) reflect.Type {
|
||||||
|
switch k := fd.Kind(); k {
|
||||||
|
case protoreflect.EnumKind:
|
||||||
|
if et, _ := protoregistry.GlobalTypes.FindEnumByName(fd.Enum().FullName()); et != nil {
|
||||||
|
return enumGoType(et)
|
||||||
|
}
|
||||||
|
return reflect.TypeOf(protoreflect.EnumNumber(0))
|
||||||
|
case protoreflect.MessageKind, protoreflect.GroupKind:
|
||||||
|
if mt, _ := protoregistry.GlobalTypes.FindMessageByName(fd.Message().FullName()); mt != nil {
|
||||||
|
return messageGoType(mt)
|
||||||
|
}
|
||||||
|
return reflect.TypeOf((*protoreflect.Message)(nil)).Elem()
|
||||||
|
default:
|
||||||
|
return reflect.TypeOf(fd.Default().Interface())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func enumGoType(et protoreflect.EnumType) reflect.Type {
|
||||||
|
return reflect.TypeOf(et.New(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
func messageGoType(mt protoreflect.MessageType) reflect.Type {
|
||||||
|
return reflect.TypeOf(MessageV1(mt.Zero().Interface()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageName returns the full protobuf name for the given message type.
|
||||||
|
//
|
||||||
|
// Deprecated: Use protoreflect.MessageDescriptor.FullName instead.
|
||||||
|
func MessageName(m Message) messageName {
|
||||||
|
if m == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if m, ok := m.(interface{ XXX_MessageName() messageName }); ok {
|
||||||
|
return m.XXX_MessageName()
|
||||||
|
}
|
||||||
|
return messageName(protoimpl.X.MessageDescriptorOf(m).FullName())
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterExtension is called from the generated code to register
|
||||||
|
// the extension descriptor.
|
||||||
|
//
|
||||||
|
// Deprecated: Use protoregistry.GlobalTypes.RegisterExtension instead.
|
||||||
|
func RegisterExtension(d *ExtensionDesc) {
|
||||||
|
if err := protoregistry.GlobalTypes.RegisterExtension(d); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type extensionsByNumber = map[int32]*ExtensionDesc
|
||||||
|
|
||||||
|
var extensionCache sync.Map // map[messageName]extensionsByNumber
|
||||||
|
|
||||||
|
// RegisteredExtensions returns a map of the registered extensions for the
|
||||||
|
// provided protobuf message, indexed by the extension field number.
|
||||||
|
//
|
||||||
|
// Deprecated: Use protoregistry.GlobalTypes.RangeExtensionsByMessage instead.
|
||||||
|
func RegisteredExtensions(m Message) extensionsByNumber {
|
||||||
|
// Check whether the cache is stale. If the number of extensions for
|
||||||
|
// the given message differs, then it means that some extensions were
|
||||||
|
// recently registered upstream that we do not know about.
|
||||||
|
s := MessageName(m)
|
||||||
|
v, _ := extensionCache.Load(s)
|
||||||
|
xs, _ := v.(extensionsByNumber)
|
||||||
|
if protoregistry.GlobalTypes.NumExtensionsByMessage(protoreflect.FullName(s)) == len(xs) {
|
||||||
|
return xs // cache is up-to-date
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache is stale, re-compute the extensions map.
|
||||||
|
xs = make(extensionsByNumber)
|
||||||
|
protoregistry.GlobalTypes.RangeExtensionsByMessage(protoreflect.FullName(s), func(xt protoreflect.ExtensionType) bool {
|
||||||
|
if xd, ok := xt.(*ExtensionDesc); ok {
|
||||||
|
xs[int32(xt.TypeDescriptor().Number())] = xd
|
||||||
|
} else {
|
||||||
|
// TODO: This implies that the protoreflect.ExtensionType is a
|
||||||
|
// custom type not generated by protoc-gen-go. We could try and
|
||||||
|
// convert the type to an ExtensionDesc.
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
extensionCache.Store(s, xs)
|
||||||
|
return xs
|
||||||
|
}
|
801
vendor/github.com/golang/protobuf/proto/text_decode.go
generated
vendored
Normal file
801
vendor/github.com/golang/protobuf/proto/text_decode.go
generated
vendored
Normal file
|
@ -0,0 +1,801 @@
|
||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"google.golang.org/protobuf/encoding/prototext"
|
||||||
|
protoV2 "google.golang.org/protobuf/proto"
|
||||||
|
"google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
"google.golang.org/protobuf/reflect/protoregistry"
|
||||||
|
)
|
||||||
|
|
||||||
|
const wrapTextUnmarshalV2 = false
|
||||||
|
|
||||||
|
// ParseError is returned by UnmarshalText.
|
||||||
|
type ParseError struct {
|
||||||
|
Message string
|
||||||
|
|
||||||
|
// Deprecated: Do not use.
|
||||||
|
Line, Offset int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ParseError) Error() string {
|
||||||
|
if wrapTextUnmarshalV2 {
|
||||||
|
return e.Message
|
||||||
|
}
|
||||||
|
if e.Line == 1 {
|
||||||
|
return fmt.Sprintf("line 1.%d: %v", e.Offset, e.Message)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("line %d: %v", e.Line, e.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalText parses a proto text formatted string into m.
|
||||||
|
func UnmarshalText(s string, m Message) error {
|
||||||
|
if u, ok := m.(encoding.TextUnmarshaler); ok {
|
||||||
|
return u.UnmarshalText([]byte(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Reset()
|
||||||
|
mi := MessageV2(m)
|
||||||
|
|
||||||
|
if wrapTextUnmarshalV2 {
|
||||||
|
err := prototext.UnmarshalOptions{
|
||||||
|
AllowPartial: true,
|
||||||
|
}.Unmarshal([]byte(s), mi)
|
||||||
|
if err != nil {
|
||||||
|
return &ParseError{Message: err.Error()}
|
||||||
|
}
|
||||||
|
return checkRequiredNotSet(mi)
|
||||||
|
} else {
|
||||||
|
if err := newTextParser(s).unmarshalMessage(mi.ProtoReflect(), ""); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return checkRequiredNotSet(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type textParser struct {
|
||||||
|
s string // remaining input
|
||||||
|
done bool // whether the parsing is finished (success or error)
|
||||||
|
backed bool // whether back() was called
|
||||||
|
offset, line int
|
||||||
|
cur token
|
||||||
|
}
|
||||||
|
|
||||||
|
type token struct {
|
||||||
|
value string
|
||||||
|
err *ParseError
|
||||||
|
line int // line number
|
||||||
|
offset int // byte number from start of input, not start of line
|
||||||
|
unquoted string // the unquoted version of value, if it was a quoted string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTextParser(s string) *textParser {
|
||||||
|
p := new(textParser)
|
||||||
|
p.s = s
|
||||||
|
p.line = 1
|
||||||
|
p.cur.line = 1
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *textParser) unmarshalMessage(m protoreflect.Message, terminator string) (err error) {
|
||||||
|
md := m.Descriptor()
|
||||||
|
fds := md.Fields()
|
||||||
|
|
||||||
|
// A struct is a sequence of "name: value", terminated by one of
|
||||||
|
// '>' or '}', or the end of the input. A name may also be
|
||||||
|
// "[extension]" or "[type/url]".
|
||||||
|
//
|
||||||
|
// The whole struct can also be an expanded Any message, like:
|
||||||
|
// [type/url] < ... struct contents ... >
|
||||||
|
seen := make(map[protoreflect.FieldNumber]bool)
|
||||||
|
for {
|
||||||
|
tok := p.next()
|
||||||
|
if tok.err != nil {
|
||||||
|
return tok.err
|
||||||
|
}
|
||||||
|
if tok.value == terminator {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if tok.value == "[" {
|
||||||
|
if err := p.unmarshalExtensionOrAny(m, seen); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a normal, non-extension field.
|
||||||
|
name := protoreflect.Name(tok.value)
|
||||||
|
fd := fds.ByName(name)
|
||||||
|
switch {
|
||||||
|
case fd == nil:
|
||||||
|
gd := fds.ByName(protoreflect.Name(strings.ToLower(string(name))))
|
||||||
|
if gd != nil && gd.Kind() == protoreflect.GroupKind && gd.Message().Name() == name {
|
||||||
|
fd = gd
|
||||||
|
}
|
||||||
|
case fd.Kind() == protoreflect.GroupKind && fd.Message().Name() != name:
|
||||||
|
fd = nil
|
||||||
|
case fd.IsWeak() && fd.Message().IsPlaceholder():
|
||||||
|
fd = nil
|
||||||
|
}
|
||||||
|
if fd == nil {
|
||||||
|
typeName := string(md.FullName())
|
||||||
|
if m, ok := m.Interface().(Message); ok {
|
||||||
|
t := reflect.TypeOf(m)
|
||||||
|
if t.Kind() == reflect.Ptr {
|
||||||
|
typeName = t.Elem().String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p.errorf("unknown field name %q in %v", name, typeName)
|
||||||
|
}
|
||||||
|
if od := fd.ContainingOneof(); od != nil && m.WhichOneof(od) != nil {
|
||||||
|
return p.errorf("field '%s' would overwrite already parsed oneof '%s'", name, od.Name())
|
||||||
|
}
|
||||||
|
if fd.Cardinality() != protoreflect.Repeated && seen[fd.Number()] {
|
||||||
|
return p.errorf("non-repeated field %q was repeated", fd.Name())
|
||||||
|
}
|
||||||
|
seen[fd.Number()] = true
|
||||||
|
|
||||||
|
// Consume any colon.
|
||||||
|
if err := p.checkForColon(fd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse into the field.
|
||||||
|
v := m.Get(fd)
|
||||||
|
if !m.Has(fd) && (fd.IsList() || fd.IsMap() || fd.Message() != nil) {
|
||||||
|
v = m.Mutable(fd)
|
||||||
|
}
|
||||||
|
if v, err = p.unmarshalValue(v, fd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.Set(fd, v)
|
||||||
|
|
||||||
|
if err := p.consumeOptionalSeparator(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *textParser) unmarshalExtensionOrAny(m protoreflect.Message, seen map[protoreflect.FieldNumber]bool) error {
|
||||||
|
name, err := p.consumeExtensionOrAnyName()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it contains a slash, it's an Any type URL.
|
||||||
|
if slashIdx := strings.LastIndex(name, "/"); slashIdx >= 0 {
|
||||||
|
tok := p.next()
|
||||||
|
if tok.err != nil {
|
||||||
|
return tok.err
|
||||||
|
}
|
||||||
|
// consume an optional colon
|
||||||
|
if tok.value == ":" {
|
||||||
|
tok = p.next()
|
||||||
|
if tok.err != nil {
|
||||||
|
return tok.err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var terminator string
|
||||||
|
switch tok.value {
|
||||||
|
case "<":
|
||||||
|
terminator = ">"
|
||||||
|
case "{":
|
||||||
|
terminator = "}"
|
||||||
|
default:
|
||||||
|
return p.errorf("expected '{' or '<', found %q", tok.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
mt, err := protoregistry.GlobalTypes.FindMessageByURL(name)
|
||||||
|
if err != nil {
|
||||||
|
return p.errorf("unrecognized message %q in google.protobuf.Any", name[slashIdx+len("/"):])
|
||||||
|
}
|
||||||
|
m2 := mt.New()
|
||||||
|
if err := p.unmarshalMessage(m2, terminator); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b, err := protoV2.Marshal(m2.Interface())
|
||||||
|
if err != nil {
|
||||||
|
return p.errorf("failed to marshal message of type %q: %v", name[slashIdx+len("/"):], err)
|
||||||
|
}
|
||||||
|
|
||||||
|
urlFD := m.Descriptor().Fields().ByName("type_url")
|
||||||
|
valFD := m.Descriptor().Fields().ByName("value")
|
||||||
|
if seen[urlFD.Number()] {
|
||||||
|
return p.errorf("Any message unpacked multiple times, or %q already set", urlFD.Name())
|
||||||
|
}
|
||||||
|
if seen[valFD.Number()] {
|
||||||
|
return p.errorf("Any message unpacked multiple times, or %q already set", valFD.Name())
|
||||||
|
}
|
||||||
|
m.Set(urlFD, protoreflect.ValueOfString(name))
|
||||||
|
m.Set(valFD, protoreflect.ValueOfBytes(b))
|
||||||
|
seen[urlFD.Number()] = true
|
||||||
|
seen[valFD.Number()] = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
xname := protoreflect.FullName(name)
|
||||||
|
xt, _ := protoregistry.GlobalTypes.FindExtensionByName(xname)
|
||||||
|
if xt == nil && isMessageSet(m.Descriptor()) {
|
||||||
|
xt, _ = protoregistry.GlobalTypes.FindExtensionByName(xname.Append("message_set_extension"))
|
||||||
|
}
|
||||||
|
if xt == nil {
|
||||||
|
return p.errorf("unrecognized extension %q", name)
|
||||||
|
}
|
||||||
|
fd := xt.TypeDescriptor()
|
||||||
|
if fd.ContainingMessage().FullName() != m.Descriptor().FullName() {
|
||||||
|
return p.errorf("extension field %q does not extend message %q", name, m.Descriptor().FullName())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.checkForColon(fd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
v := m.Get(fd)
|
||||||
|
if !m.Has(fd) && (fd.IsList() || fd.IsMap() || fd.Message() != nil) {
|
||||||
|
v = m.Mutable(fd)
|
||||||
|
}
|
||||||
|
v, err = p.unmarshalValue(v, fd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.Set(fd, v)
|
||||||
|
return p.consumeOptionalSeparator()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *textParser) unmarshalValue(v protoreflect.Value, fd protoreflect.FieldDescriptor) (protoreflect.Value, error) {
|
||||||
|
tok := p.next()
|
||||||
|
if tok.err != nil {
|
||||||
|
return v, tok.err
|
||||||
|
}
|
||||||
|
if tok.value == "" {
|
||||||
|
return v, p.errorf("unexpected EOF")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case fd.IsList():
|
||||||
|
lv := v.List()
|
||||||
|
var err error
|
||||||
|
if tok.value == "[" {
|
||||||
|
// Repeated field with list notation, like [1,2,3].
|
||||||
|
for {
|
||||||
|
vv := lv.NewElement()
|
||||||
|
vv, err = p.unmarshalSingularValue(vv, fd)
|
||||||
|
if err != nil {
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
lv.Append(vv)
|
||||||
|
|
||||||
|
tok := p.next()
|
||||||
|
if tok.err != nil {
|
||||||
|
return v, tok.err
|
||||||
|
}
|
||||||
|
if tok.value == "]" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if tok.value != "," {
|
||||||
|
return v, p.errorf("Expected ']' or ',' found %q", tok.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// One value of the repeated field.
|
||||||
|
p.back()
|
||||||
|
vv := lv.NewElement()
|
||||||
|
vv, err = p.unmarshalSingularValue(vv, fd)
|
||||||
|
if err != nil {
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
lv.Append(vv)
|
||||||
|
return v, nil
|
||||||
|
case fd.IsMap():
|
||||||
|
// The map entry should be this sequence of tokens:
|
||||||
|
// < key : KEY value : VALUE >
|
||||||
|
// However, implementations may omit key or value, and technically
|
||||||
|
// we should support them in any order.
|
||||||
|
var terminator string
|
||||||
|
switch tok.value {
|
||||||
|
case "<":
|
||||||
|
terminator = ">"
|
||||||
|
case "{":
|
||||||
|
terminator = "}"
|
||||||
|
default:
|
||||||
|
return v, p.errorf("expected '{' or '<', found %q", tok.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
keyFD := fd.MapKey()
|
||||||
|
valFD := fd.MapValue()
|
||||||
|
|
||||||
|
mv := v.Map()
|
||||||
|
kv := keyFD.Default()
|
||||||
|
vv := mv.NewValue()
|
||||||
|
for {
|
||||||
|
tok := p.next()
|
||||||
|
if tok.err != nil {
|
||||||
|
return v, tok.err
|
||||||
|
}
|
||||||
|
if tok.value == terminator {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
switch tok.value {
|
||||||
|
case "key":
|
||||||
|
if err := p.consumeToken(":"); err != nil {
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
if kv, err = p.unmarshalSingularValue(kv, keyFD); err != nil {
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
if err := p.consumeOptionalSeparator(); err != nil {
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
case "value":
|
||||||
|
if err := p.checkForColon(valFD); err != nil {
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
if vv, err = p.unmarshalSingularValue(vv, valFD); err != nil {
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
if err := p.consumeOptionalSeparator(); err != nil {
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
p.back()
|
||||||
|
return v, p.errorf(`expected "key", "value", or %q, found %q`, terminator, tok.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mv.Set(kv.MapKey(), vv)
|
||||||
|
return v, nil
|
||||||
|
default:
|
||||||
|
p.back()
|
||||||
|
return p.unmarshalSingularValue(v, fd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *textParser) unmarshalSingularValue(v protoreflect.Value, fd protoreflect.FieldDescriptor) (protoreflect.Value, error) {
|
||||||
|
tok := p.next()
|
||||||
|
if tok.err != nil {
|
||||||
|
return v, tok.err
|
||||||
|
}
|
||||||
|
if tok.value == "" {
|
||||||
|
return v, p.errorf("unexpected EOF")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch fd.Kind() {
|
||||||
|
case protoreflect.BoolKind:
|
||||||
|
switch tok.value {
|
||||||
|
case "true", "1", "t", "True":
|
||||||
|
return protoreflect.ValueOfBool(true), nil
|
||||||
|
case "false", "0", "f", "False":
|
||||||
|
return protoreflect.ValueOfBool(false), nil
|
||||||
|
}
|
||||||
|
case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind:
|
||||||
|
if x, err := strconv.ParseInt(tok.value, 0, 32); err == nil {
|
||||||
|
return protoreflect.ValueOfInt32(int32(x)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The C++ parser accepts large positive hex numbers that uses
|
||||||
|
// two's complement arithmetic to represent negative numbers.
|
||||||
|
// This feature is here for backwards compatibility with C++.
|
||||||
|
if strings.HasPrefix(tok.value, "0x") {
|
||||||
|
if x, err := strconv.ParseUint(tok.value, 0, 32); err == nil {
|
||||||
|
return protoreflect.ValueOfInt32(int32(-(int64(^x) + 1))), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind:
|
||||||
|
if x, err := strconv.ParseInt(tok.value, 0, 64); err == nil {
|
||||||
|
return protoreflect.ValueOfInt64(int64(x)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The C++ parser accepts large positive hex numbers that uses
|
||||||
|
// two's complement arithmetic to represent negative numbers.
|
||||||
|
// This feature is here for backwards compatibility with C++.
|
||||||
|
if strings.HasPrefix(tok.value, "0x") {
|
||||||
|
if x, err := strconv.ParseUint(tok.value, 0, 64); err == nil {
|
||||||
|
return protoreflect.ValueOfInt64(int64(-(int64(^x) + 1))), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case protoreflect.Uint32Kind, protoreflect.Fixed32Kind:
|
||||||
|
if x, err := strconv.ParseUint(tok.value, 0, 32); err == nil {
|
||||||
|
return protoreflect.ValueOfUint32(uint32(x)), nil
|
||||||
|
}
|
||||||
|
case protoreflect.Uint64Kind, protoreflect.Fixed64Kind:
|
||||||
|
if x, err := strconv.ParseUint(tok.value, 0, 64); err == nil {
|
||||||
|
return protoreflect.ValueOfUint64(uint64(x)), nil
|
||||||
|
}
|
||||||
|
case protoreflect.FloatKind:
|
||||||
|
// Ignore 'f' for compatibility with output generated by C++,
|
||||||
|
// but don't remove 'f' when the value is "-inf" or "inf".
|
||||||
|
v := tok.value
|
||||||
|
if strings.HasSuffix(v, "f") && v != "-inf" && v != "inf" {
|
||||||
|
v = v[:len(v)-len("f")]
|
||||||
|
}
|
||||||
|
if x, err := strconv.ParseFloat(v, 32); err == nil {
|
||||||
|
return protoreflect.ValueOfFloat32(float32(x)), nil
|
||||||
|
}
|
||||||
|
case protoreflect.DoubleKind:
|
||||||
|
// Ignore 'f' for compatibility with output generated by C++,
|
||||||
|
// but don't remove 'f' when the value is "-inf" or "inf".
|
||||||
|
v := tok.value
|
||||||
|
if strings.HasSuffix(v, "f") && v != "-inf" && v != "inf" {
|
||||||
|
v = v[:len(v)-len("f")]
|
||||||
|
}
|
||||||
|
if x, err := strconv.ParseFloat(v, 64); err == nil {
|
||||||
|
return protoreflect.ValueOfFloat64(float64(x)), nil
|
||||||
|
}
|
||||||
|
case protoreflect.StringKind:
|
||||||
|
if isQuote(tok.value[0]) {
|
||||||
|
return protoreflect.ValueOfString(tok.unquoted), nil
|
||||||
|
}
|
||||||
|
case protoreflect.BytesKind:
|
||||||
|
if isQuote(tok.value[0]) {
|
||||||
|
return protoreflect.ValueOfBytes([]byte(tok.unquoted)), nil
|
||||||
|
}
|
||||||
|
case protoreflect.EnumKind:
|
||||||
|
if x, err := strconv.ParseInt(tok.value, 0, 32); err == nil {
|
||||||
|
return protoreflect.ValueOfEnum(protoreflect.EnumNumber(x)), nil
|
||||||
|
}
|
||||||
|
vd := fd.Enum().Values().ByName(protoreflect.Name(tok.value))
|
||||||
|
if vd != nil {
|
||||||
|
return protoreflect.ValueOfEnum(vd.Number()), nil
|
||||||
|
}
|
||||||
|
case protoreflect.MessageKind, protoreflect.GroupKind:
|
||||||
|
var terminator string
|
||||||
|
switch tok.value {
|
||||||
|
case "{":
|
||||||
|
terminator = "}"
|
||||||
|
case "<":
|
||||||
|
terminator = ">"
|
||||||
|
default:
|
||||||
|
return v, p.errorf("expected '{' or '<', found %q", tok.value)
|
||||||
|
}
|
||||||
|
err := p.unmarshalMessage(v.Message(), terminator)
|
||||||
|
return v, err
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("invalid kind %v", fd.Kind()))
|
||||||
|
}
|
||||||
|
return v, p.errorf("invalid %v: %v", fd.Kind(), tok.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume a ':' from the input stream (if the next token is a colon),
|
||||||
|
// returning an error if a colon is needed but not present.
|
||||||
|
func (p *textParser) checkForColon(fd protoreflect.FieldDescriptor) *ParseError {
|
||||||
|
tok := p.next()
|
||||||
|
if tok.err != nil {
|
||||||
|
return tok.err
|
||||||
|
}
|
||||||
|
if tok.value != ":" {
|
||||||
|
if fd.Message() == nil {
|
||||||
|
return p.errorf("expected ':', found %q", tok.value)
|
||||||
|
}
|
||||||
|
p.back()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// consumeExtensionOrAnyName consumes an extension name or an Any type URL and
|
||||||
|
// the following ']'. It returns the name or URL consumed.
|
||||||
|
func (p *textParser) consumeExtensionOrAnyName() (string, error) {
|
||||||
|
tok := p.next()
|
||||||
|
if tok.err != nil {
|
||||||
|
return "", tok.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If extension name or type url is quoted, it's a single token.
|
||||||
|
if len(tok.value) > 2 && isQuote(tok.value[0]) && tok.value[len(tok.value)-1] == tok.value[0] {
|
||||||
|
name, err := unquoteC(tok.value[1:len(tok.value)-1], rune(tok.value[0]))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return name, p.consumeToken("]")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume everything up to "]"
|
||||||
|
var parts []string
|
||||||
|
for tok.value != "]" {
|
||||||
|
parts = append(parts, tok.value)
|
||||||
|
tok = p.next()
|
||||||
|
if tok.err != nil {
|
||||||
|
return "", p.errorf("unrecognized type_url or extension name: %s", tok.err)
|
||||||
|
}
|
||||||
|
if p.done && tok.value != "]" {
|
||||||
|
return "", p.errorf("unclosed type_url or extension name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(parts, ""), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// consumeOptionalSeparator consumes an optional semicolon or comma.
|
||||||
|
// It is used in unmarshalMessage to provide backward compatibility.
|
||||||
|
func (p *textParser) consumeOptionalSeparator() error {
|
||||||
|
tok := p.next()
|
||||||
|
if tok.err != nil {
|
||||||
|
return tok.err
|
||||||
|
}
|
||||||
|
if tok.value != ";" && tok.value != "," {
|
||||||
|
p.back()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *textParser) errorf(format string, a ...interface{}) *ParseError {
|
||||||
|
pe := &ParseError{fmt.Sprintf(format, a...), p.cur.line, p.cur.offset}
|
||||||
|
p.cur.err = pe
|
||||||
|
p.done = true
|
||||||
|
return pe
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *textParser) skipWhitespace() {
|
||||||
|
i := 0
|
||||||
|
for i < len(p.s) && (isWhitespace(p.s[i]) || p.s[i] == '#') {
|
||||||
|
if p.s[i] == '#' {
|
||||||
|
// comment; skip to end of line or input
|
||||||
|
for i < len(p.s) && p.s[i] != '\n' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if i == len(p.s) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.s[i] == '\n' {
|
||||||
|
p.line++
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
p.offset += i
|
||||||
|
p.s = p.s[i:len(p.s)]
|
||||||
|
if len(p.s) == 0 {
|
||||||
|
p.done = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *textParser) advance() {
|
||||||
|
// Skip whitespace
|
||||||
|
p.skipWhitespace()
|
||||||
|
if p.done {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start of non-whitespace
|
||||||
|
p.cur.err = nil
|
||||||
|
p.cur.offset, p.cur.line = p.offset, p.line
|
||||||
|
p.cur.unquoted = ""
|
||||||
|
switch p.s[0] {
|
||||||
|
case '<', '>', '{', '}', ':', '[', ']', ';', ',', '/':
|
||||||
|
// Single symbol
|
||||||
|
p.cur.value, p.s = p.s[0:1], p.s[1:len(p.s)]
|
||||||
|
case '"', '\'':
|
||||||
|
// Quoted string
|
||||||
|
i := 1
|
||||||
|
for i < len(p.s) && p.s[i] != p.s[0] && p.s[i] != '\n' {
|
||||||
|
if p.s[i] == '\\' && i+1 < len(p.s) {
|
||||||
|
// skip escaped char
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if i >= len(p.s) || p.s[i] != p.s[0] {
|
||||||
|
p.errorf("unmatched quote")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
unq, err := unquoteC(p.s[1:i], rune(p.s[0]))
|
||||||
|
if err != nil {
|
||||||
|
p.errorf("invalid quoted string %s: %v", p.s[0:i+1], err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.cur.value, p.s = p.s[0:i+1], p.s[i+1:len(p.s)]
|
||||||
|
p.cur.unquoted = unq
|
||||||
|
default:
|
||||||
|
i := 0
|
||||||
|
for i < len(p.s) && isIdentOrNumberChar(p.s[i]) {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if i == 0 {
|
||||||
|
p.errorf("unexpected byte %#x", p.s[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.cur.value, p.s = p.s[0:i], p.s[i:len(p.s)]
|
||||||
|
}
|
||||||
|
p.offset += len(p.cur.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Back off the parser by one token. Can only be done between calls to next().
|
||||||
|
// It makes the next advance() a no-op.
|
||||||
|
func (p *textParser) back() { p.backed = true }
|
||||||
|
|
||||||
|
// Advances the parser and returns the new current token.
|
||||||
|
func (p *textParser) next() *token {
|
||||||
|
if p.backed || p.done {
|
||||||
|
p.backed = false
|
||||||
|
return &p.cur
|
||||||
|
}
|
||||||
|
p.advance()
|
||||||
|
if p.done {
|
||||||
|
p.cur.value = ""
|
||||||
|
} else if len(p.cur.value) > 0 && isQuote(p.cur.value[0]) {
|
||||||
|
// Look for multiple quoted strings separated by whitespace,
|
||||||
|
// and concatenate them.
|
||||||
|
cat := p.cur
|
||||||
|
for {
|
||||||
|
p.skipWhitespace()
|
||||||
|
if p.done || !isQuote(p.s[0]) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
p.advance()
|
||||||
|
if p.cur.err != nil {
|
||||||
|
return &p.cur
|
||||||
|
}
|
||||||
|
cat.value += " " + p.cur.value
|
||||||
|
cat.unquoted += p.cur.unquoted
|
||||||
|
}
|
||||||
|
p.done = false // parser may have seen EOF, but we want to return cat
|
||||||
|
p.cur = cat
|
||||||
|
}
|
||||||
|
return &p.cur
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *textParser) consumeToken(s string) error {
|
||||||
|
tok := p.next()
|
||||||
|
if tok.err != nil {
|
||||||
|
return tok.err
|
||||||
|
}
|
||||||
|
if tok.value != s {
|
||||||
|
p.back()
|
||||||
|
return p.errorf("expected %q, found %q", s, tok.value)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var errBadUTF8 = errors.New("proto: bad UTF-8")
|
||||||
|
|
||||||
|
func unquoteC(s string, quote rune) (string, error) {
|
||||||
|
// This is based on C++'s tokenizer.cc.
|
||||||
|
// Despite its name, this is *not* parsing C syntax.
|
||||||
|
// For instance, "\0" is an invalid quoted string.
|
||||||
|
|
||||||
|
// Avoid allocation in trivial cases.
|
||||||
|
simple := true
|
||||||
|
for _, r := range s {
|
||||||
|
if r == '\\' || r == quote {
|
||||||
|
simple = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if simple {
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, 0, 3*len(s)/2)
|
||||||
|
for len(s) > 0 {
|
||||||
|
r, n := utf8.DecodeRuneInString(s)
|
||||||
|
if r == utf8.RuneError && n == 1 {
|
||||||
|
return "", errBadUTF8
|
||||||
|
}
|
||||||
|
s = s[n:]
|
||||||
|
if r != '\\' {
|
||||||
|
if r < utf8.RuneSelf {
|
||||||
|
buf = append(buf, byte(r))
|
||||||
|
} else {
|
||||||
|
buf = append(buf, string(r)...)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ch, tail, err := unescape(s)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
buf = append(buf, ch...)
|
||||||
|
s = tail
|
||||||
|
}
|
||||||
|
return string(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unescape(s string) (ch string, tail string, err error) {
|
||||||
|
r, n := utf8.DecodeRuneInString(s)
|
||||||
|
if r == utf8.RuneError && n == 1 {
|
||||||
|
return "", "", errBadUTF8
|
||||||
|
}
|
||||||
|
s = s[n:]
|
||||||
|
switch r {
|
||||||
|
case 'a':
|
||||||
|
return "\a", s, nil
|
||||||
|
case 'b':
|
||||||
|
return "\b", s, nil
|
||||||
|
case 'f':
|
||||||
|
return "\f", s, nil
|
||||||
|
case 'n':
|
||||||
|
return "\n", s, nil
|
||||||
|
case 'r':
|
||||||
|
return "\r", s, nil
|
||||||
|
case 't':
|
||||||
|
return "\t", s, nil
|
||||||
|
case 'v':
|
||||||
|
return "\v", s, nil
|
||||||
|
case '?':
|
||||||
|
return "?", s, nil // trigraph workaround
|
||||||
|
case '\'', '"', '\\':
|
||||||
|
return string(r), s, nil
|
||||||
|
case '0', '1', '2', '3', '4', '5', '6', '7':
|
||||||
|
if len(s) < 2 {
|
||||||
|
return "", "", fmt.Errorf(`\%c requires 2 following digits`, r)
|
||||||
|
}
|
||||||
|
ss := string(r) + s[:2]
|
||||||
|
s = s[2:]
|
||||||
|
i, err := strconv.ParseUint(ss, 8, 8)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf(`\%s contains non-octal digits`, ss)
|
||||||
|
}
|
||||||
|
return string([]byte{byte(i)}), s, nil
|
||||||
|
case 'x', 'X', 'u', 'U':
|
||||||
|
var n int
|
||||||
|
switch r {
|
||||||
|
case 'x', 'X':
|
||||||
|
n = 2
|
||||||
|
case 'u':
|
||||||
|
n = 4
|
||||||
|
case 'U':
|
||||||
|
n = 8
|
||||||
|
}
|
||||||
|
if len(s) < n {
|
||||||
|
return "", "", fmt.Errorf(`\%c requires %d following digits`, r, n)
|
||||||
|
}
|
||||||
|
ss := s[:n]
|
||||||
|
s = s[n:]
|
||||||
|
i, err := strconv.ParseUint(ss, 16, 64)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf(`\%c%s contains non-hexadecimal digits`, r, ss)
|
||||||
|
}
|
||||||
|
if r == 'x' || r == 'X' {
|
||||||
|
return string([]byte{byte(i)}), s, nil
|
||||||
|
}
|
||||||
|
if i > utf8.MaxRune {
|
||||||
|
return "", "", fmt.Errorf(`\%c%s is not a valid Unicode code point`, r, ss)
|
||||||
|
}
|
||||||
|
return string(rune(i)), s, nil
|
||||||
|
}
|
||||||
|
return "", "", fmt.Errorf(`unknown escape \%c`, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isIdentOrNumberChar(c byte) bool {
|
||||||
|
switch {
|
||||||
|
case 'A' <= c && c <= 'Z', 'a' <= c && c <= 'z':
|
||||||
|
return true
|
||||||
|
case '0' <= c && c <= '9':
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
switch c {
|
||||||
|
case '-', '+', '.', '_':
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isWhitespace(c byte) bool {
|
||||||
|
switch c {
|
||||||
|
case ' ', '\t', '\n', '\r':
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isQuote(c byte) bool {
|
||||||
|
switch c {
|
||||||
|
case '"', '\'':
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
560
vendor/github.com/golang/protobuf/proto/text_encode.go
generated
vendored
Normal file
560
vendor/github.com/golang/protobuf/proto/text_encode.go
generated
vendored
Normal file
|
@ -0,0 +1,560 @@
|
||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"google.golang.org/protobuf/encoding/prototext"
|
||||||
|
"google.golang.org/protobuf/encoding/protowire"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
"google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
"google.golang.org/protobuf/reflect/protoregistry"
|
||||||
|
)
|
||||||
|
|
||||||
|
const wrapTextMarshalV2 = false
|
||||||
|
|
||||||
|
// TextMarshaler is a configurable text format marshaler.
|
||||||
|
type TextMarshaler struct {
|
||||||
|
Compact bool // use compact text format (one line)
|
||||||
|
ExpandAny bool // expand google.protobuf.Any messages of known types
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal writes the proto text format of m to w.
|
||||||
|
func (tm *TextMarshaler) Marshal(w io.Writer, m Message) error {
|
||||||
|
b, err := tm.marshal(m)
|
||||||
|
if len(b) > 0 {
|
||||||
|
if _, err := w.Write(b); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text returns a proto text formatted string of m.
|
||||||
|
func (tm *TextMarshaler) Text(m Message) string {
|
||||||
|
b, _ := tm.marshal(m)
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tm *TextMarshaler) marshal(m Message) ([]byte, error) {
|
||||||
|
mr := MessageReflect(m)
|
||||||
|
if mr == nil || !mr.IsValid() {
|
||||||
|
return []byte("<nil>"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if wrapTextMarshalV2 {
|
||||||
|
if m, ok := m.(encoding.TextMarshaler); ok {
|
||||||
|
return m.MarshalText()
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := prototext.MarshalOptions{
|
||||||
|
AllowPartial: true,
|
||||||
|
EmitUnknown: true,
|
||||||
|
}
|
||||||
|
if !tm.Compact {
|
||||||
|
opts.Indent = " "
|
||||||
|
}
|
||||||
|
if !tm.ExpandAny {
|
||||||
|
opts.Resolver = (*protoregistry.Types)(nil)
|
||||||
|
}
|
||||||
|
return opts.Marshal(mr.Interface())
|
||||||
|
} else {
|
||||||
|
w := &textWriter{
|
||||||
|
compact: tm.Compact,
|
||||||
|
expandAny: tm.ExpandAny,
|
||||||
|
complete: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if m, ok := m.(encoding.TextMarshaler); ok {
|
||||||
|
b, err := m.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
w.Write(b)
|
||||||
|
return w.buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := w.writeMessage(mr)
|
||||||
|
return w.buf, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultTextMarshaler = TextMarshaler{}
|
||||||
|
compactTextMarshaler = TextMarshaler{Compact: true}
|
||||||
|
)
|
||||||
|
|
||||||
|
// MarshalText writes the proto text format of m to w.
|
||||||
|
func MarshalText(w io.Writer, m Message) error { return defaultTextMarshaler.Marshal(w, m) }
|
||||||
|
|
||||||
|
// MarshalTextString returns a proto text formatted string of m.
|
||||||
|
func MarshalTextString(m Message) string { return defaultTextMarshaler.Text(m) }
|
||||||
|
|
||||||
|
// CompactText writes the compact proto text format of m to w.
|
||||||
|
func CompactText(w io.Writer, m Message) error { return compactTextMarshaler.Marshal(w, m) }
|
||||||
|
|
||||||
|
// CompactTextString returns a compact proto text formatted string of m.
|
||||||
|
func CompactTextString(m Message) string { return compactTextMarshaler.Text(m) }
|
||||||
|
|
||||||
|
var (
|
||||||
|
newline = []byte("\n")
|
||||||
|
endBraceNewline = []byte("}\n")
|
||||||
|
posInf = []byte("inf")
|
||||||
|
negInf = []byte("-inf")
|
||||||
|
nan = []byte("nan")
|
||||||
|
)
|
||||||
|
|
||||||
|
// textWriter is an io.Writer that tracks its indentation level.
|
||||||
|
type textWriter struct {
|
||||||
|
compact bool // same as TextMarshaler.Compact
|
||||||
|
expandAny bool // same as TextMarshaler.ExpandAny
|
||||||
|
complete bool // whether the current position is a complete line
|
||||||
|
indent int // indentation level; never negative
|
||||||
|
buf []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *textWriter) Write(p []byte) (n int, _ error) {
|
||||||
|
newlines := bytes.Count(p, newline)
|
||||||
|
if newlines == 0 {
|
||||||
|
if !w.compact && w.complete {
|
||||||
|
w.writeIndent()
|
||||||
|
}
|
||||||
|
w.buf = append(w.buf, p...)
|
||||||
|
w.complete = false
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
frags := bytes.SplitN(p, newline, newlines+1)
|
||||||
|
if w.compact {
|
||||||
|
for i, frag := range frags {
|
||||||
|
if i > 0 {
|
||||||
|
w.buf = append(w.buf, ' ')
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
w.buf = append(w.buf, frag...)
|
||||||
|
n += len(frag)
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, frag := range frags {
|
||||||
|
if w.complete {
|
||||||
|
w.writeIndent()
|
||||||
|
}
|
||||||
|
w.buf = append(w.buf, frag...)
|
||||||
|
n += len(frag)
|
||||||
|
if i+1 < len(frags) {
|
||||||
|
w.buf = append(w.buf, '\n')
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.complete = len(frags[len(frags)-1]) == 0
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *textWriter) WriteByte(c byte) error {
|
||||||
|
if w.compact && c == '\n' {
|
||||||
|
c = ' '
|
||||||
|
}
|
||||||
|
if !w.compact && w.complete {
|
||||||
|
w.writeIndent()
|
||||||
|
}
|
||||||
|
w.buf = append(w.buf, c)
|
||||||
|
w.complete = c == '\n'
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *textWriter) writeName(fd protoreflect.FieldDescriptor) {
|
||||||
|
if !w.compact && w.complete {
|
||||||
|
w.writeIndent()
|
||||||
|
}
|
||||||
|
w.complete = false
|
||||||
|
|
||||||
|
if fd.Kind() != protoreflect.GroupKind {
|
||||||
|
w.buf = append(w.buf, fd.Name()...)
|
||||||
|
w.WriteByte(':')
|
||||||
|
} else {
|
||||||
|
// Use message type name for group field name.
|
||||||
|
w.buf = append(w.buf, fd.Message().Name()...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !w.compact {
|
||||||
|
w.WriteByte(' ')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func requiresQuotes(u string) bool {
|
||||||
|
// When type URL contains any characters except [0-9A-Za-z./\-]*, it must be quoted.
|
||||||
|
for _, ch := range u {
|
||||||
|
switch {
|
||||||
|
case ch == '.' || ch == '/' || ch == '_':
|
||||||
|
continue
|
||||||
|
case '0' <= ch && ch <= '9':
|
||||||
|
continue
|
||||||
|
case 'A' <= ch && ch <= 'Z':
|
||||||
|
continue
|
||||||
|
case 'a' <= ch && ch <= 'z':
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeProto3Any writes an expanded google.protobuf.Any message.
|
||||||
|
//
|
||||||
|
// It returns (false, nil) if sv value can't be unmarshaled (e.g. because
|
||||||
|
// required messages are not linked in).
|
||||||
|
//
|
||||||
|
// It returns (true, error) when sv was written in expanded format or an error
|
||||||
|
// was encountered.
|
||||||
|
func (w *textWriter) writeProto3Any(m protoreflect.Message) (bool, error) {
|
||||||
|
md := m.Descriptor()
|
||||||
|
fdURL := md.Fields().ByName("type_url")
|
||||||
|
fdVal := md.Fields().ByName("value")
|
||||||
|
|
||||||
|
url := m.Get(fdURL).String()
|
||||||
|
mt, err := protoregistry.GlobalTypes.FindMessageByURL(url)
|
||||||
|
if err != nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
b := m.Get(fdVal).Bytes()
|
||||||
|
m2 := mt.New()
|
||||||
|
if err := proto.Unmarshal(b, m2.Interface()); err != nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
w.Write([]byte("["))
|
||||||
|
if requiresQuotes(url) {
|
||||||
|
w.writeQuotedString(url)
|
||||||
|
} else {
|
||||||
|
w.Write([]byte(url))
|
||||||
|
}
|
||||||
|
if w.compact {
|
||||||
|
w.Write([]byte("]:<"))
|
||||||
|
} else {
|
||||||
|
w.Write([]byte("]: <\n"))
|
||||||
|
w.indent++
|
||||||
|
}
|
||||||
|
if err := w.writeMessage(m2); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
if w.compact {
|
||||||
|
w.Write([]byte("> "))
|
||||||
|
} else {
|
||||||
|
w.indent--
|
||||||
|
w.Write([]byte(">\n"))
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *textWriter) writeMessage(m protoreflect.Message) error {
|
||||||
|
md := m.Descriptor()
|
||||||
|
if w.expandAny && md.FullName() == "google.protobuf.Any" {
|
||||||
|
if canExpand, err := w.writeProto3Any(m); canExpand {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fds := md.Fields()
|
||||||
|
for i := 0; i < fds.Len(); {
|
||||||
|
fd := fds.Get(i)
|
||||||
|
if od := fd.ContainingOneof(); od != nil {
|
||||||
|
fd = m.WhichOneof(od)
|
||||||
|
i += od.Fields().Len()
|
||||||
|
} else {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if fd == nil || !m.Has(fd) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case fd.IsList():
|
||||||
|
lv := m.Get(fd).List()
|
||||||
|
for j := 0; j < lv.Len(); j++ {
|
||||||
|
w.writeName(fd)
|
||||||
|
v := lv.Get(j)
|
||||||
|
if err := w.writeSingularValue(v, fd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.WriteByte('\n')
|
||||||
|
}
|
||||||
|
case fd.IsMap():
|
||||||
|
kfd := fd.MapKey()
|
||||||
|
vfd := fd.MapValue()
|
||||||
|
mv := m.Get(fd).Map()
|
||||||
|
|
||||||
|
type entry struct{ key, val protoreflect.Value }
|
||||||
|
var entries []entry
|
||||||
|
mv.Range(func(k protoreflect.MapKey, v protoreflect.Value) bool {
|
||||||
|
entries = append(entries, entry{k.Value(), v})
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
sort.Slice(entries, func(i, j int) bool {
|
||||||
|
switch kfd.Kind() {
|
||||||
|
case protoreflect.BoolKind:
|
||||||
|
return !entries[i].key.Bool() && entries[j].key.Bool()
|
||||||
|
case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind, protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind:
|
||||||
|
return entries[i].key.Int() < entries[j].key.Int()
|
||||||
|
case protoreflect.Uint32Kind, protoreflect.Fixed32Kind, protoreflect.Uint64Kind, protoreflect.Fixed64Kind:
|
||||||
|
return entries[i].key.Uint() < entries[j].key.Uint()
|
||||||
|
case protoreflect.StringKind:
|
||||||
|
return entries[i].key.String() < entries[j].key.String()
|
||||||
|
default:
|
||||||
|
panic("invalid kind")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
for _, entry := range entries {
|
||||||
|
w.writeName(fd)
|
||||||
|
w.WriteByte('<')
|
||||||
|
if !w.compact {
|
||||||
|
w.WriteByte('\n')
|
||||||
|
}
|
||||||
|
w.indent++
|
||||||
|
w.writeName(kfd)
|
||||||
|
if err := w.writeSingularValue(entry.key, kfd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.WriteByte('\n')
|
||||||
|
w.writeName(vfd)
|
||||||
|
if err := w.writeSingularValue(entry.val, vfd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.WriteByte('\n')
|
||||||
|
w.indent--
|
||||||
|
w.WriteByte('>')
|
||||||
|
w.WriteByte('\n')
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
w.writeName(fd)
|
||||||
|
if err := w.writeSingularValue(m.Get(fd), fd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.WriteByte('\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if b := m.GetUnknown(); len(b) > 0 {
|
||||||
|
w.writeUnknownFields(b)
|
||||||
|
}
|
||||||
|
return w.writeExtensions(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *textWriter) writeSingularValue(v protoreflect.Value, fd protoreflect.FieldDescriptor) error {
|
||||||
|
switch fd.Kind() {
|
||||||
|
case protoreflect.FloatKind, protoreflect.DoubleKind:
|
||||||
|
switch vf := v.Float(); {
|
||||||
|
case math.IsInf(vf, +1):
|
||||||
|
w.Write(posInf)
|
||||||
|
case math.IsInf(vf, -1):
|
||||||
|
w.Write(negInf)
|
||||||
|
case math.IsNaN(vf):
|
||||||
|
w.Write(nan)
|
||||||
|
default:
|
||||||
|
fmt.Fprint(w, v.Interface())
|
||||||
|
}
|
||||||
|
case protoreflect.StringKind:
|
||||||
|
// NOTE: This does not validate UTF-8 for historical reasons.
|
||||||
|
w.writeQuotedString(string(v.String()))
|
||||||
|
case protoreflect.BytesKind:
|
||||||
|
w.writeQuotedString(string(v.Bytes()))
|
||||||
|
case protoreflect.MessageKind, protoreflect.GroupKind:
|
||||||
|
var bra, ket byte = '<', '>'
|
||||||
|
if fd.Kind() == protoreflect.GroupKind {
|
||||||
|
bra, ket = '{', '}'
|
||||||
|
}
|
||||||
|
w.WriteByte(bra)
|
||||||
|
if !w.compact {
|
||||||
|
w.WriteByte('\n')
|
||||||
|
}
|
||||||
|
w.indent++
|
||||||
|
m := v.Message()
|
||||||
|
if m2, ok := m.Interface().(encoding.TextMarshaler); ok {
|
||||||
|
b, err := m2.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.Write(b)
|
||||||
|
} else {
|
||||||
|
w.writeMessage(m)
|
||||||
|
}
|
||||||
|
w.indent--
|
||||||
|
w.WriteByte(ket)
|
||||||
|
case protoreflect.EnumKind:
|
||||||
|
if ev := fd.Enum().Values().ByNumber(v.Enum()); ev != nil {
|
||||||
|
fmt.Fprint(w, ev.Name())
|
||||||
|
} else {
|
||||||
|
fmt.Fprint(w, v.Enum())
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
fmt.Fprint(w, v.Interface())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeQuotedString writes a quoted string in the protocol buffer text format.
|
||||||
|
func (w *textWriter) writeQuotedString(s string) {
|
||||||
|
w.WriteByte('"')
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
switch c := s[i]; c {
|
||||||
|
case '\n':
|
||||||
|
w.buf = append(w.buf, `\n`...)
|
||||||
|
case '\r':
|
||||||
|
w.buf = append(w.buf, `\r`...)
|
||||||
|
case '\t':
|
||||||
|
w.buf = append(w.buf, `\t`...)
|
||||||
|
case '"':
|
||||||
|
w.buf = append(w.buf, `\"`...)
|
||||||
|
case '\\':
|
||||||
|
w.buf = append(w.buf, `\\`...)
|
||||||
|
default:
|
||||||
|
if isPrint := c >= 0x20 && c < 0x7f; isPrint {
|
||||||
|
w.buf = append(w.buf, c)
|
||||||
|
} else {
|
||||||
|
w.buf = append(w.buf, fmt.Sprintf(`\%03o`, c)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.WriteByte('"')
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *textWriter) writeUnknownFields(b []byte) {
|
||||||
|
if !w.compact {
|
||||||
|
fmt.Fprintf(w, "/* %d unknown bytes */\n", len(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
for len(b) > 0 {
|
||||||
|
num, wtyp, n := protowire.ConsumeTag(b)
|
||||||
|
if n < 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b = b[n:]
|
||||||
|
|
||||||
|
if wtyp == protowire.EndGroupType {
|
||||||
|
w.indent--
|
||||||
|
w.Write(endBraceNewline)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Fprint(w, num)
|
||||||
|
if wtyp != protowire.StartGroupType {
|
||||||
|
w.WriteByte(':')
|
||||||
|
}
|
||||||
|
if !w.compact || wtyp == protowire.StartGroupType {
|
||||||
|
w.WriteByte(' ')
|
||||||
|
}
|
||||||
|
switch wtyp {
|
||||||
|
case protowire.VarintType:
|
||||||
|
v, n := protowire.ConsumeVarint(b)
|
||||||
|
if n < 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b = b[n:]
|
||||||
|
fmt.Fprint(w, v)
|
||||||
|
case protowire.Fixed32Type:
|
||||||
|
v, n := protowire.ConsumeFixed32(b)
|
||||||
|
if n < 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b = b[n:]
|
||||||
|
fmt.Fprint(w, v)
|
||||||
|
case protowire.Fixed64Type:
|
||||||
|
v, n := protowire.ConsumeFixed64(b)
|
||||||
|
if n < 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b = b[n:]
|
||||||
|
fmt.Fprint(w, v)
|
||||||
|
case protowire.BytesType:
|
||||||
|
v, n := protowire.ConsumeBytes(b)
|
||||||
|
if n < 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b = b[n:]
|
||||||
|
fmt.Fprintf(w, "%q", v)
|
||||||
|
case protowire.StartGroupType:
|
||||||
|
w.WriteByte('{')
|
||||||
|
w.indent++
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(w, "/* unknown wire type %d */", wtyp)
|
||||||
|
}
|
||||||
|
w.WriteByte('\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeExtensions writes all the extensions in m.
|
||||||
|
func (w *textWriter) writeExtensions(m protoreflect.Message) error {
|
||||||
|
md := m.Descriptor()
|
||||||
|
if md.ExtensionRanges().Len() == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ext struct {
|
||||||
|
desc protoreflect.FieldDescriptor
|
||||||
|
val protoreflect.Value
|
||||||
|
}
|
||||||
|
var exts []ext
|
||||||
|
m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
|
||||||
|
if fd.IsExtension() {
|
||||||
|
exts = append(exts, ext{fd, v})
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
sort.Slice(exts, func(i, j int) bool {
|
||||||
|
return exts[i].desc.Number() < exts[j].desc.Number()
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, ext := range exts {
|
||||||
|
// For message set, use the name of the message as the extension name.
|
||||||
|
name := string(ext.desc.FullName())
|
||||||
|
if isMessageSet(ext.desc.ContainingMessage()) {
|
||||||
|
name = strings.TrimSuffix(name, ".message_set_extension")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ext.desc.IsList() {
|
||||||
|
if err := w.writeSingularExtension(name, ext.val, ext.desc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lv := ext.val.List()
|
||||||
|
for i := 0; i < lv.Len(); i++ {
|
||||||
|
if err := w.writeSingularExtension(name, lv.Get(i), ext.desc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *textWriter) writeSingularExtension(name string, v protoreflect.Value, fd protoreflect.FieldDescriptor) error {
|
||||||
|
fmt.Fprintf(w, "[%s]:", name)
|
||||||
|
if !w.compact {
|
||||||
|
w.WriteByte(' ')
|
||||||
|
}
|
||||||
|
if err := w.writeSingularValue(v, fd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.WriteByte('\n')
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *textWriter) writeIndent() {
|
||||||
|
if !w.complete {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i := 0; i < w.indent*2; i++ {
|
||||||
|
w.buf = append(w.buf, ' ')
|
||||||
|
}
|
||||||
|
w.complete = false
|
||||||
|
}
|
78
vendor/github.com/golang/protobuf/proto/wire.go
generated
vendored
Normal file
78
vendor/github.com/golang/protobuf/proto/wire.go
generated
vendored
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
// Copyright 2019 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
protoV2 "google.golang.org/protobuf/proto"
|
||||||
|
"google.golang.org/protobuf/runtime/protoiface"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Size returns the size in bytes of the wire-format encoding of m.
|
||||||
|
func Size(m Message) int {
|
||||||
|
if m == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
mi := MessageV2(m)
|
||||||
|
return protoV2.Size(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal returns the wire-format encoding of m.
|
||||||
|
func Marshal(m Message) ([]byte, error) {
|
||||||
|
b, err := marshalAppend(nil, m, false)
|
||||||
|
if b == nil {
|
||||||
|
b = zeroBytes
|
||||||
|
}
|
||||||
|
return b, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var zeroBytes = make([]byte, 0, 0)
|
||||||
|
|
||||||
|
func marshalAppend(buf []byte, m Message, deterministic bool) ([]byte, error) {
|
||||||
|
if m == nil {
|
||||||
|
return nil, ErrNil
|
||||||
|
}
|
||||||
|
mi := MessageV2(m)
|
||||||
|
nbuf, err := protoV2.MarshalOptions{
|
||||||
|
Deterministic: deterministic,
|
||||||
|
AllowPartial: true,
|
||||||
|
}.MarshalAppend(buf, mi)
|
||||||
|
if err != nil {
|
||||||
|
return buf, err
|
||||||
|
}
|
||||||
|
if len(buf) == len(nbuf) {
|
||||||
|
if !mi.ProtoReflect().IsValid() {
|
||||||
|
return buf, ErrNil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nbuf, checkRequiredNotSet(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal parses a wire-format message in b and places the decoded results in m.
|
||||||
|
//
|
||||||
|
// Unmarshal resets m before starting to unmarshal, so any existing data in m is always
|
||||||
|
// removed. Use UnmarshalMerge to preserve and append to existing data.
|
||||||
|
func Unmarshal(b []byte, m Message) error {
|
||||||
|
m.Reset()
|
||||||
|
return UnmarshalMerge(b, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalMerge parses a wire-format message in b and places the decoded results in m.
|
||||||
|
func UnmarshalMerge(b []byte, m Message) error {
|
||||||
|
mi := MessageV2(m)
|
||||||
|
out, err := protoV2.UnmarshalOptions{
|
||||||
|
AllowPartial: true,
|
||||||
|
Merge: true,
|
||||||
|
}.UnmarshalState(protoiface.UnmarshalInput{
|
||||||
|
Buf: b,
|
||||||
|
Message: mi.ProtoReflect(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if out.Flags&protoiface.UnmarshalInitialized > 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return checkRequiredNotSet(mi)
|
||||||
|
}
|
34
vendor/github.com/golang/protobuf/proto/wrappers.go
generated
vendored
Normal file
34
vendor/github.com/golang/protobuf/proto/wrappers.go
generated
vendored
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
// Copyright 2019 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
// Bool stores v in a new bool value and returns a pointer to it.
|
||||||
|
func Bool(v bool) *bool { return &v }
|
||||||
|
|
||||||
|
// Int stores v in a new int32 value and returns a pointer to it.
|
||||||
|
//
|
||||||
|
// Deprecated: Use Int32 instead.
|
||||||
|
func Int(v int) *int32 { return Int32(int32(v)) }
|
||||||
|
|
||||||
|
// Int32 stores v in a new int32 value and returns a pointer to it.
|
||||||
|
func Int32(v int32) *int32 { return &v }
|
||||||
|
|
||||||
|
// Int64 stores v in a new int64 value and returns a pointer to it.
|
||||||
|
func Int64(v int64) *int64 { return &v }
|
||||||
|
|
||||||
|
// Uint32 stores v in a new uint32 value and returns a pointer to it.
|
||||||
|
func Uint32(v uint32) *uint32 { return &v }
|
||||||
|
|
||||||
|
// Uint64 stores v in a new uint64 value and returns a pointer to it.
|
||||||
|
func Uint64(v uint64) *uint64 { return &v }
|
||||||
|
|
||||||
|
// Float32 stores v in a new float32 value and returns a pointer to it.
|
||||||
|
func Float32(v float32) *float32 { return &v }
|
||||||
|
|
||||||
|
// Float64 stores v in a new float64 value and returns a pointer to it.
|
||||||
|
func Float64(v float64) *float64 { return &v }
|
||||||
|
|
||||||
|
// String stores v in a new string value and returns a pointer to it.
|
||||||
|
func String(v string) *string { return &v }
|
8
vendor/github.com/gorilla/mux/AUTHORS
generated
vendored
Normal file
8
vendor/github.com/gorilla/mux/AUTHORS
generated
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# This is the official list of gorilla/mux authors for copyright purposes.
|
||||||
|
#
|
||||||
|
# Please keep the list sorted.
|
||||||
|
|
||||||
|
Google LLC (https://opensource.google.com/)
|
||||||
|
Kamil Kisielk <kamil@kamilkisiel.net>
|
||||||
|
Matt Silverlock <matt@eatsleeprepeat.net>
|
||||||
|
Rodrigo Moraes (https://github.com/moraes)
|
27
vendor/github.com/gorilla/mux/LICENSE
generated
vendored
Normal file
27
vendor/github.com/gorilla/mux/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright (c) 2012-2018 The Gorilla Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
805
vendor/github.com/gorilla/mux/README.md
generated
vendored
Normal file
805
vendor/github.com/gorilla/mux/README.md
generated
vendored
Normal file
|
@ -0,0 +1,805 @@
|
||||||
|
# gorilla/mux
|
||||||
|
|
||||||
|
[![GoDoc](https://godoc.org/github.com/gorilla/mux?status.svg)](https://godoc.org/github.com/gorilla/mux)
|
||||||
|
[![CircleCI](https://circleci.com/gh/gorilla/mux.svg?style=svg)](https://circleci.com/gh/gorilla/mux)
|
||||||
|
[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/mux/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/mux?badge)
|
||||||
|
|
||||||
|
![Gorilla Logo](https://cloud-cdn.questionable.services/gorilla-icon-64.png)
|
||||||
|
|
||||||
|
https://www.gorillatoolkit.org/pkg/mux
|
||||||
|
|
||||||
|
Package `gorilla/mux` implements a request router and dispatcher for matching incoming requests to
|
||||||
|
their respective handler.
|
||||||
|
|
||||||
|
The name mux stands for "HTTP request multiplexer". Like the standard `http.ServeMux`, `mux.Router` matches incoming requests against a list of registered routes and calls a handler for the route that matches the URL or other conditions. The main features are:
|
||||||
|
|
||||||
|
* It implements the `http.Handler` interface so it is compatible with the standard `http.ServeMux`.
|
||||||
|
* Requests can be matched based on URL host, path, path prefix, schemes, header and query values, HTTP methods or using custom matchers.
|
||||||
|
* URL hosts, paths and query values can have variables with an optional regular expression.
|
||||||
|
* Registered URLs can be built, or "reversed", which helps maintaining references to resources.
|
||||||
|
* Routes can be used as subrouters: nested routes are only tested if the parent route matches. This is useful to define groups of routes that share common conditions like a host, a path prefix or other repeated attributes. As a bonus, this optimizes request matching.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
* [Install](#install)
|
||||||
|
* [Examples](#examples)
|
||||||
|
* [Matching Routes](#matching-routes)
|
||||||
|
* [Static Files](#static-files)
|
||||||
|
* [Serving Single Page Applications](#serving-single-page-applications) (e.g. React, Vue, Ember.js, etc.)
|
||||||
|
* [Registered URLs](#registered-urls)
|
||||||
|
* [Walking Routes](#walking-routes)
|
||||||
|
* [Graceful Shutdown](#graceful-shutdown)
|
||||||
|
* [Middleware](#middleware)
|
||||||
|
* [Handling CORS Requests](#handling-cors-requests)
|
||||||
|
* [Testing Handlers](#testing-handlers)
|
||||||
|
* [Full Example](#full-example)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
With a [correctly configured](https://golang.org/doc/install#testing) Go toolchain:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go get -u github.com/gorilla/mux
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
Let's start registering a couple of URL paths and handlers:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/", HomeHandler)
|
||||||
|
r.HandleFunc("/products", ProductsHandler)
|
||||||
|
r.HandleFunc("/articles", ArticlesHandler)
|
||||||
|
http.Handle("/", r)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Here we register three routes mapping URL paths to handlers. This is equivalent to how `http.HandleFunc()` works: if an incoming request URL matches one of the paths, the corresponding handler is called passing (`http.ResponseWriter`, `*http.Request`) as parameters.
|
||||||
|
|
||||||
|
Paths can have variables. They are defined using the format `{name}` or `{name:pattern}`. If a regular expression pattern is not defined, the matched variable will be anything until the next slash. For example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/products/{key}", ProductHandler)
|
||||||
|
r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
|
||||||
|
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
|
||||||
|
```
|
||||||
|
|
||||||
|
The names are used to create a map of route variables which can be retrieved calling `mux.Vars()`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func ArticlesCategoryHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprintf(w, "Category: %v\n", vars["category"])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And this is all you need to know about the basic usage. More advanced options are explained below.
|
||||||
|
|
||||||
|
### Matching Routes
|
||||||
|
|
||||||
|
Routes can also be restricted to a domain or subdomain. Just define a host pattern to be matched. They can also have variables:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r := mux.NewRouter()
|
||||||
|
// Only matches if domain is "www.example.com".
|
||||||
|
r.Host("www.example.com")
|
||||||
|
// Matches a dynamic subdomain.
|
||||||
|
r.Host("{subdomain:[a-z]+}.example.com")
|
||||||
|
```
|
||||||
|
|
||||||
|
There are several other matchers that can be added. To match path prefixes:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r.PathPrefix("/products/")
|
||||||
|
```
|
||||||
|
|
||||||
|
...or HTTP methods:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r.Methods("GET", "POST")
|
||||||
|
```
|
||||||
|
|
||||||
|
...or URL schemes:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r.Schemes("https")
|
||||||
|
```
|
||||||
|
|
||||||
|
...or header values:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r.Headers("X-Requested-With", "XMLHttpRequest")
|
||||||
|
```
|
||||||
|
|
||||||
|
...or query values:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r.Queries("key", "value")
|
||||||
|
```
|
||||||
|
|
||||||
|
...or to use a custom matcher function:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
|
||||||
|
return r.ProtoMajor == 0
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
...and finally, it is possible to combine several matchers in a single route:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r.HandleFunc("/products", ProductsHandler).
|
||||||
|
Host("www.example.com").
|
||||||
|
Methods("GET").
|
||||||
|
Schemes("http")
|
||||||
|
```
|
||||||
|
|
||||||
|
Routes are tested in the order they were added to the router. If two routes match, the first one wins:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/specific", specificHandler)
|
||||||
|
r.PathPrefix("/").Handler(catchAllHandler)
|
||||||
|
```
|
||||||
|
|
||||||
|
Setting the same matching conditions again and again can be boring, so we have a way to group several routes that share the same requirements. We call it "subrouting".
|
||||||
|
|
||||||
|
For example, let's say we have several URLs that should only match when the host is `www.example.com`. Create a route for that host and get a "subrouter" from it:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r := mux.NewRouter()
|
||||||
|
s := r.Host("www.example.com").Subrouter()
|
||||||
|
```
|
||||||
|
|
||||||
|
Then register routes in the subrouter:
|
||||||
|
|
||||||
|
```go
|
||||||
|
s.HandleFunc("/products/", ProductsHandler)
|
||||||
|
s.HandleFunc("/products/{key}", ProductHandler)
|
||||||
|
s.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
|
||||||
|
```
|
||||||
|
|
||||||
|
The three URL paths we registered above will only be tested if the domain is `www.example.com`, because the subrouter is tested first. This is not only convenient, but also optimizes request matching. You can create subrouters combining any attribute matchers accepted by a route.
|
||||||
|
|
||||||
|
Subrouters can be used to create domain or path "namespaces": you define subrouters in a central place and then parts of the app can register its paths relatively to a given subrouter.
|
||||||
|
|
||||||
|
There's one more thing about subroutes. When a subrouter has a path prefix, the inner routes use it as base for their paths:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r := mux.NewRouter()
|
||||||
|
s := r.PathPrefix("/products").Subrouter()
|
||||||
|
// "/products/"
|
||||||
|
s.HandleFunc("/", ProductsHandler)
|
||||||
|
// "/products/{key}/"
|
||||||
|
s.HandleFunc("/{key}/", ProductHandler)
|
||||||
|
// "/products/{key}/details"
|
||||||
|
s.HandleFunc("/{key}/details", ProductDetailsHandler)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Static Files
|
||||||
|
|
||||||
|
Note that the path provided to `PathPrefix()` represents a "wildcard": calling
|
||||||
|
`PathPrefix("/static/").Handler(...)` means that the handler will be passed any
|
||||||
|
request that matches "/static/\*". This makes it easy to serve static files with mux:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
var dir string
|
||||||
|
|
||||||
|
flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir")
|
||||||
|
flag.Parse()
|
||||||
|
r := mux.NewRouter()
|
||||||
|
|
||||||
|
// This will serve files under http://localhost:8000/static/<filename>
|
||||||
|
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir))))
|
||||||
|
|
||||||
|
srv := &http.Server{
|
||||||
|
Handler: r,
|
||||||
|
Addr: "127.0.0.1:8000",
|
||||||
|
// Good practice: enforce timeouts for servers you create!
|
||||||
|
WriteTimeout: 15 * time.Second,
|
||||||
|
ReadTimeout: 15 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Fatal(srv.ListenAndServe())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Serving Single Page Applications
|
||||||
|
|
||||||
|
Most of the time it makes sense to serve your SPA on a separate web server from your API,
|
||||||
|
but sometimes it's desirable to serve them both from one place. It's possible to write a simple
|
||||||
|
handler for serving your SPA (for use with React Router's [BrowserRouter](https://reacttraining.com/react-router/web/api/BrowserRouter) for example), and leverage
|
||||||
|
mux's powerful routing for your API endpoints.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
// spaHandler implements the http.Handler interface, so we can use it
|
||||||
|
// to respond to HTTP requests. The path to the static directory and
|
||||||
|
// path to the index file within that static directory are used to
|
||||||
|
// serve the SPA in the given static directory.
|
||||||
|
type spaHandler struct {
|
||||||
|
staticPath string
|
||||||
|
indexPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHTTP inspects the URL path to locate a file within the static dir
|
||||||
|
// on the SPA handler. If a file is found, it will be served. If not, the
|
||||||
|
// file located at the index path on the SPA handler will be served. This
|
||||||
|
// is suitable behavior for serving an SPA (single page application).
|
||||||
|
func (h spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// get the absolute path to prevent directory traversal
|
||||||
|
path, err := filepath.Abs(r.URL.Path)
|
||||||
|
if err != nil {
|
||||||
|
// if we failed to get the absolute path respond with a 400 bad request
|
||||||
|
// and stop
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepend the path with the path to the static directory
|
||||||
|
path = filepath.Join(h.staticPath, path)
|
||||||
|
|
||||||
|
// check whether a file exists at the given path
|
||||||
|
_, err = os.Stat(path)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
// file does not exist, serve index.html
|
||||||
|
http.ServeFile(w, r, filepath.Join(h.staticPath, h.indexPath))
|
||||||
|
return
|
||||||
|
} else if err != nil {
|
||||||
|
// if we got an error (that wasn't that the file doesn't exist) stating the
|
||||||
|
// file, return a 500 internal server error and stop
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise, use http.FileServer to serve the static dir
|
||||||
|
http.FileServer(http.Dir(h.staticPath)).ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
router := mux.NewRouter()
|
||||||
|
|
||||||
|
router.HandleFunc("/api/health", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// an example API handler
|
||||||
|
json.NewEncoder(w).Encode(map[string]bool{"ok": true})
|
||||||
|
})
|
||||||
|
|
||||||
|
spa := spaHandler{staticPath: "build", indexPath: "index.html"}
|
||||||
|
router.PathPrefix("/").Handler(spa)
|
||||||
|
|
||||||
|
srv := &http.Server{
|
||||||
|
Handler: router,
|
||||||
|
Addr: "127.0.0.1:8000",
|
||||||
|
// Good practice: enforce timeouts for servers you create!
|
||||||
|
WriteTimeout: 15 * time.Second,
|
||||||
|
ReadTimeout: 15 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Fatal(srv.ListenAndServe())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Registered URLs
|
||||||
|
|
||||||
|
Now let's see how to build registered URLs.
|
||||||
|
|
||||||
|
Routes can be named. All routes that define a name can have their URLs built, or "reversed". We define a name calling `Name()` on a route. For example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
|
||||||
|
Name("article")
|
||||||
|
```
|
||||||
|
|
||||||
|
To build a URL, get the route and call the `URL()` method, passing a sequence of key/value pairs for the route variables. For the previous route, we would do:
|
||||||
|
|
||||||
|
```go
|
||||||
|
url, err := r.Get("article").URL("category", "technology", "id", "42")
|
||||||
|
```
|
||||||
|
|
||||||
|
...and the result will be a `url.URL` with the following path:
|
||||||
|
|
||||||
|
```
|
||||||
|
"/articles/technology/42"
|
||||||
|
```
|
||||||
|
|
||||||
|
This also works for host and query value variables:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.Host("{subdomain}.example.com").
|
||||||
|
Path("/articles/{category}/{id:[0-9]+}").
|
||||||
|
Queries("filter", "{filter}").
|
||||||
|
HandlerFunc(ArticleHandler).
|
||||||
|
Name("article")
|
||||||
|
|
||||||
|
// url.String() will be "http://news.example.com/articles/technology/42?filter=gorilla"
|
||||||
|
url, err := r.Get("article").URL("subdomain", "news",
|
||||||
|
"category", "technology",
|
||||||
|
"id", "42",
|
||||||
|
"filter", "gorilla")
|
||||||
|
```
|
||||||
|
|
||||||
|
All variables defined in the route are required, and their values must conform to the corresponding patterns. These requirements guarantee that a generated URL will always match a registered route -- the only exception is for explicitly defined "build-only" routes which never match.
|
||||||
|
|
||||||
|
Regex support also exists for matching Headers within a route. For example, we could do:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r.HeadersRegexp("Content-Type", "application/(text|json)")
|
||||||
|
```
|
||||||
|
|
||||||
|
...and the route will match both requests with a Content-Type of `application/json` as well as `application/text`
|
||||||
|
|
||||||
|
There's also a way to build only the URL host or path for a route: use the methods `URLHost()` or `URLPath()` instead. For the previous route, we would do:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// "http://news.example.com/"
|
||||||
|
host, err := r.Get("article").URLHost("subdomain", "news")
|
||||||
|
|
||||||
|
// "/articles/technology/42"
|
||||||
|
path, err := r.Get("article").URLPath("category", "technology", "id", "42")
|
||||||
|
```
|
||||||
|
|
||||||
|
And if you use subrouters, host and path defined separately can be built as well:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r := mux.NewRouter()
|
||||||
|
s := r.Host("{subdomain}.example.com").Subrouter()
|
||||||
|
s.Path("/articles/{category}/{id:[0-9]+}").
|
||||||
|
HandlerFunc(ArticleHandler).
|
||||||
|
Name("article")
|
||||||
|
|
||||||
|
// "http://news.example.com/articles/technology/42"
|
||||||
|
url, err := r.Get("article").URL("subdomain", "news",
|
||||||
|
"category", "technology",
|
||||||
|
"id", "42")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Walking Routes
|
||||||
|
|
||||||
|
The `Walk` function on `mux.Router` can be used to visit all of the routes that are registered on a router. For example,
|
||||||
|
the following prints all of the registered routes:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/", handler)
|
||||||
|
r.HandleFunc("/products", handler).Methods("POST")
|
||||||
|
r.HandleFunc("/articles", handler).Methods("GET")
|
||||||
|
r.HandleFunc("/articles/{id}", handler).Methods("GET", "PUT")
|
||||||
|
r.HandleFunc("/authors", handler).Queries("surname", "{surname}")
|
||||||
|
err := r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
|
||||||
|
pathTemplate, err := route.GetPathTemplate()
|
||||||
|
if err == nil {
|
||||||
|
fmt.Println("ROUTE:", pathTemplate)
|
||||||
|
}
|
||||||
|
pathRegexp, err := route.GetPathRegexp()
|
||||||
|
if err == nil {
|
||||||
|
fmt.Println("Path regexp:", pathRegexp)
|
||||||
|
}
|
||||||
|
queriesTemplates, err := route.GetQueriesTemplates()
|
||||||
|
if err == nil {
|
||||||
|
fmt.Println("Queries templates:", strings.Join(queriesTemplates, ","))
|
||||||
|
}
|
||||||
|
queriesRegexps, err := route.GetQueriesRegexp()
|
||||||
|
if err == nil {
|
||||||
|
fmt.Println("Queries regexps:", strings.Join(queriesRegexps, ","))
|
||||||
|
}
|
||||||
|
methods, err := route.GetMethods()
|
||||||
|
if err == nil {
|
||||||
|
fmt.Println("Methods:", strings.Join(methods, ","))
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Handle("/", r)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Graceful Shutdown
|
||||||
|
|
||||||
|
Go 1.8 introduced the ability to [gracefully shutdown](https://golang.org/doc/go1.8#http_shutdown) a `*http.Server`. Here's how to do that alongside `mux`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var wait time.Duration
|
||||||
|
flag.DurationVar(&wait, "graceful-timeout", time.Second * 15, "the duration for which the server gracefully wait for existing connections to finish - e.g. 15s or 1m")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
// Add your routes as needed
|
||||||
|
|
||||||
|
srv := &http.Server{
|
||||||
|
Addr: "0.0.0.0:8080",
|
||||||
|
// Good practice to set timeouts to avoid Slowloris attacks.
|
||||||
|
WriteTimeout: time.Second * 15,
|
||||||
|
ReadTimeout: time.Second * 15,
|
||||||
|
IdleTimeout: time.Second * 60,
|
||||||
|
Handler: r, // Pass our instance of gorilla/mux in.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run our server in a goroutine so that it doesn't block.
|
||||||
|
go func() {
|
||||||
|
if err := srv.ListenAndServe(); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
c := make(chan os.Signal, 1)
|
||||||
|
// We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C)
|
||||||
|
// SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught.
|
||||||
|
signal.Notify(c, os.Interrupt)
|
||||||
|
|
||||||
|
// Block until we receive our signal.
|
||||||
|
<-c
|
||||||
|
|
||||||
|
// Create a deadline to wait for.
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), wait)
|
||||||
|
defer cancel()
|
||||||
|
// Doesn't block if no connections, but will otherwise wait
|
||||||
|
// until the timeout deadline.
|
||||||
|
srv.Shutdown(ctx)
|
||||||
|
// Optionally, you could run srv.Shutdown in a goroutine and block on
|
||||||
|
// <-ctx.Done() if your application should wait for other services
|
||||||
|
// to finalize based on context cancellation.
|
||||||
|
log.Println("shutting down")
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Middleware
|
||||||
|
|
||||||
|
Mux supports the addition of middlewares to a [Router](https://godoc.org/github.com/gorilla/mux#Router), which are executed in the order they are added if a match is found, including its subrouters.
|
||||||
|
Middlewares are (typically) small pieces of code which take one request, do something with it, and pass it down to another middleware or the final handler. Some common use cases for middleware are request logging, header manipulation, or `ResponseWriter` hijacking.
|
||||||
|
|
||||||
|
Mux middlewares are defined using the de facto standard type:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type MiddlewareFunc func(http.Handler) http.Handler
|
||||||
|
```
|
||||||
|
|
||||||
|
Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed to it, and then calls the handler passed as parameter to the MiddlewareFunc. This takes advantage of closures being able access variables from the context where they are created, while retaining the signature enforced by the receivers.
|
||||||
|
|
||||||
|
A very basic middleware which logs the URI of the request being handled could be written as:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func loggingMiddleware(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Do stuff here
|
||||||
|
log.Println(r.RequestURI)
|
||||||
|
// Call the next handler, which can be another middleware in the chain, or the final handler.
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Middlewares can be added to a router using `Router.Use()`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/", handler)
|
||||||
|
r.Use(loggingMiddleware)
|
||||||
|
```
|
||||||
|
|
||||||
|
A more complex authentication middleware, which maps session token to users, could be written as:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Define our struct
|
||||||
|
type authenticationMiddleware struct {
|
||||||
|
tokenUsers map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize it somewhere
|
||||||
|
func (amw *authenticationMiddleware) Populate() {
|
||||||
|
amw.tokenUsers["00000000"] = "user0"
|
||||||
|
amw.tokenUsers["aaaaaaaa"] = "userA"
|
||||||
|
amw.tokenUsers["05f717e5"] = "randomUser"
|
||||||
|
amw.tokenUsers["deadbeef"] = "user0"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Middleware function, which will be called for each request
|
||||||
|
func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
token := r.Header.Get("X-Session-Token")
|
||||||
|
|
||||||
|
if user, found := amw.tokenUsers[token]; found {
|
||||||
|
// We found the token in our map
|
||||||
|
log.Printf("Authenticated user %s\n", user)
|
||||||
|
// Pass down the request to the next middleware (or final handler)
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
} else {
|
||||||
|
// Write an error and stop the handler chain
|
||||||
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/", handler)
|
||||||
|
|
||||||
|
amw := authenticationMiddleware{}
|
||||||
|
amw.Populate()
|
||||||
|
|
||||||
|
r.Use(amw.Middleware)
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to. Middlewares _should_ write to `ResponseWriter` if they _are_ going to terminate the request, and they _should not_ write to `ResponseWriter` if they _are not_ going to terminate it.
|
||||||
|
|
||||||
|
### Handling CORS Requests
|
||||||
|
|
||||||
|
[CORSMethodMiddleware](https://godoc.org/github.com/gorilla/mux#CORSMethodMiddleware) intends to make it easier to strictly set the `Access-Control-Allow-Methods` response header.
|
||||||
|
|
||||||
|
* You will still need to use your own CORS handler to set the other CORS headers such as `Access-Control-Allow-Origin`
|
||||||
|
* The middleware will set the `Access-Control-Allow-Methods` header to all the method matchers (e.g. `r.Methods(http.MethodGet, http.MethodPut, http.MethodOptions)` -> `Access-Control-Allow-Methods: GET,PUT,OPTIONS`) on a route
|
||||||
|
* If you do not specify any methods, then:
|
||||||
|
> _Important_: there must be an `OPTIONS` method matcher for the middleware to set the headers.
|
||||||
|
|
||||||
|
Here is an example of using `CORSMethodMiddleware` along with a custom `OPTIONS` handler to set all the required CORS headers:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := mux.NewRouter()
|
||||||
|
|
||||||
|
// IMPORTANT: you must specify an OPTIONS method matcher for the middleware to set CORS headers
|
||||||
|
r.HandleFunc("/foo", fooHandler).Methods(http.MethodGet, http.MethodPut, http.MethodPatch, http.MethodOptions)
|
||||||
|
r.Use(mux.CORSMethodMiddleware(r))
|
||||||
|
|
||||||
|
http.ListenAndServe(":8080", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fooHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
|
if r.Method == http.MethodOptions {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write([]byte("foo"))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And an request to `/foo` using something like:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl localhost:8080/foo -v
|
||||||
|
```
|
||||||
|
|
||||||
|
Would look like:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
* Trying ::1...
|
||||||
|
* TCP_NODELAY set
|
||||||
|
* Connected to localhost (::1) port 8080 (#0)
|
||||||
|
> GET /foo HTTP/1.1
|
||||||
|
> Host: localhost:8080
|
||||||
|
> User-Agent: curl/7.59.0
|
||||||
|
> Accept: */*
|
||||||
|
>
|
||||||
|
< HTTP/1.1 200 OK
|
||||||
|
< Access-Control-Allow-Methods: GET,PUT,PATCH,OPTIONS
|
||||||
|
< Access-Control-Allow-Origin: *
|
||||||
|
< Date: Fri, 28 Jun 2019 20:13:30 GMT
|
||||||
|
< Content-Length: 3
|
||||||
|
< Content-Type: text/plain; charset=utf-8
|
||||||
|
<
|
||||||
|
* Connection #0 to host localhost left intact
|
||||||
|
foo
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing Handlers
|
||||||
|
|
||||||
|
Testing handlers in a Go web application is straightforward, and _mux_ doesn't complicate this any further. Given two files: `endpoints.go` and `endpoints_test.go`, here's how we'd test an application using _mux_.
|
||||||
|
|
||||||
|
First, our simple HTTP handler:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// endpoints.go
|
||||||
|
package main
|
||||||
|
|
||||||
|
func HealthCheckHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// A very simple health check.
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
|
||||||
|
// In the future we could report back on the status of our DB, or our cache
|
||||||
|
// (e.g. Redis) by performing a simple PING, and include them in the response.
|
||||||
|
io.WriteString(w, `{"alive": true}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/health", HealthCheckHandler)
|
||||||
|
|
||||||
|
log.Fatal(http.ListenAndServe("localhost:8080", r))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Our test code:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// endpoints_test.go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHealthCheckHandler(t *testing.T) {
|
||||||
|
// Create a request to pass to our handler. We don't have any query parameters for now, so we'll
|
||||||
|
// pass 'nil' as the third parameter.
|
||||||
|
req, err := http.NewRequest("GET", "/health", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
handler := http.HandlerFunc(HealthCheckHandler)
|
||||||
|
|
||||||
|
// Our handlers satisfy http.Handler, so we can call their ServeHTTP method
|
||||||
|
// directly and pass in our Request and ResponseRecorder.
|
||||||
|
handler.ServeHTTP(rr, req)
|
||||||
|
|
||||||
|
// Check the status code is what we expect.
|
||||||
|
if status := rr.Code; status != http.StatusOK {
|
||||||
|
t.Errorf("handler returned wrong status code: got %v want %v",
|
||||||
|
status, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the response body is what we expect.
|
||||||
|
expected := `{"alive": true}`
|
||||||
|
if rr.Body.String() != expected {
|
||||||
|
t.Errorf("handler returned unexpected body: got %v want %v",
|
||||||
|
rr.Body.String(), expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In the case that our routes have [variables](#examples), we can pass those in the request. We could write
|
||||||
|
[table-driven tests](https://dave.cheney.net/2013/06/09/writing-table-driven-tests-in-go) to test multiple
|
||||||
|
possible route variables as needed.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// endpoints.go
|
||||||
|
func main() {
|
||||||
|
r := mux.NewRouter()
|
||||||
|
// A route with a route variable:
|
||||||
|
r.HandleFunc("/metrics/{type}", MetricsHandler)
|
||||||
|
|
||||||
|
log.Fatal(http.ListenAndServe("localhost:8080", r))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Our test file, with a table-driven test of `routeVariables`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// endpoints_test.go
|
||||||
|
func TestMetricsHandler(t *testing.T) {
|
||||||
|
tt := []struct{
|
||||||
|
routeVariable string
|
||||||
|
shouldPass bool
|
||||||
|
}{
|
||||||
|
{"goroutines", true},
|
||||||
|
{"heap", true},
|
||||||
|
{"counters", true},
|
||||||
|
{"queries", true},
|
||||||
|
{"adhadaeqm3k", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
path := fmt.Sprintf("/metrics/%s", tc.routeVariable)
|
||||||
|
req, err := http.NewRequest("GET", path, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
|
||||||
|
// Need to create a router that we can pass the request through so that the vars will be added to the context
|
||||||
|
router := mux.NewRouter()
|
||||||
|
router.HandleFunc("/metrics/{type}", MetricsHandler)
|
||||||
|
router.ServeHTTP(rr, req)
|
||||||
|
|
||||||
|
// In this case, our MetricsHandler returns a non-200 response
|
||||||
|
// for a route variable it doesn't know about.
|
||||||
|
if rr.Code == http.StatusOK && !tc.shouldPass {
|
||||||
|
t.Errorf("handler should have failed on routeVariable %s: got %v want %v",
|
||||||
|
tc.routeVariable, rr.Code, http.StatusOK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Full Example
|
||||||
|
|
||||||
|
Here's a complete, runnable example of a small `mux` based server:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"log"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
func YourHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte("Gorilla!\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := mux.NewRouter()
|
||||||
|
// Routes consist of a path and a handler function.
|
||||||
|
r.HandleFunc("/", YourHandler)
|
||||||
|
|
||||||
|
// Bind to a port and pass our router in
|
||||||
|
log.Fatal(http.ListenAndServe(":8000", r))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
BSD licensed. See the LICENSE file for details.
|
306
vendor/github.com/gorilla/mux/doc.go
generated
vendored
Normal file
306
vendor/github.com/gorilla/mux/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,306 @@
|
||||||
|
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package mux implements a request router and dispatcher.
|
||||||
|
|
||||||
|
The name mux stands for "HTTP request multiplexer". Like the standard
|
||||||
|
http.ServeMux, mux.Router matches incoming requests against a list of
|
||||||
|
registered routes and calls a handler for the route that matches the URL
|
||||||
|
or other conditions. The main features are:
|
||||||
|
|
||||||
|
* Requests can be matched based on URL host, path, path prefix, schemes,
|
||||||
|
header and query values, HTTP methods or using custom matchers.
|
||||||
|
* URL hosts, paths and query values can have variables with an optional
|
||||||
|
regular expression.
|
||||||
|
* Registered URLs can be built, or "reversed", which helps maintaining
|
||||||
|
references to resources.
|
||||||
|
* Routes can be used as subrouters: nested routes are only tested if the
|
||||||
|
parent route matches. This is useful to define groups of routes that
|
||||||
|
share common conditions like a host, a path prefix or other repeated
|
||||||
|
attributes. As a bonus, this optimizes request matching.
|
||||||
|
* It implements the http.Handler interface so it is compatible with the
|
||||||
|
standard http.ServeMux.
|
||||||
|
|
||||||
|
Let's start registering a couple of URL paths and handlers:
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/", HomeHandler)
|
||||||
|
r.HandleFunc("/products", ProductsHandler)
|
||||||
|
r.HandleFunc("/articles", ArticlesHandler)
|
||||||
|
http.Handle("/", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
Here we register three routes mapping URL paths to handlers. This is
|
||||||
|
equivalent to how http.HandleFunc() works: if an incoming request URL matches
|
||||||
|
one of the paths, the corresponding handler is called passing
|
||||||
|
(http.ResponseWriter, *http.Request) as parameters.
|
||||||
|
|
||||||
|
Paths can have variables. They are defined using the format {name} or
|
||||||
|
{name:pattern}. If a regular expression pattern is not defined, the matched
|
||||||
|
variable will be anything until the next slash. For example:
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/products/{key}", ProductHandler)
|
||||||
|
r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
|
||||||
|
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
|
||||||
|
|
||||||
|
Groups can be used inside patterns, as long as they are non-capturing (?:re). For example:
|
||||||
|
|
||||||
|
r.HandleFunc("/articles/{category}/{sort:(?:asc|desc|new)}", ArticlesCategoryHandler)
|
||||||
|
|
||||||
|
The names are used to create a map of route variables which can be retrieved
|
||||||
|
calling mux.Vars():
|
||||||
|
|
||||||
|
vars := mux.Vars(request)
|
||||||
|
category := vars["category"]
|
||||||
|
|
||||||
|
Note that if any capturing groups are present, mux will panic() during parsing. To prevent
|
||||||
|
this, convert any capturing groups to non-capturing, e.g. change "/{sort:(asc|desc)}" to
|
||||||
|
"/{sort:(?:asc|desc)}". This is a change from prior versions which behaved unpredictably
|
||||||
|
when capturing groups were present.
|
||||||
|
|
||||||
|
And this is all you need to know about the basic usage. More advanced options
|
||||||
|
are explained below.
|
||||||
|
|
||||||
|
Routes can also be restricted to a domain or subdomain. Just define a host
|
||||||
|
pattern to be matched. They can also have variables:
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
// Only matches if domain is "www.example.com".
|
||||||
|
r.Host("www.example.com")
|
||||||
|
// Matches a dynamic subdomain.
|
||||||
|
r.Host("{subdomain:[a-z]+}.domain.com")
|
||||||
|
|
||||||
|
There are several other matchers that can be added. To match path prefixes:
|
||||||
|
|
||||||
|
r.PathPrefix("/products/")
|
||||||
|
|
||||||
|
...or HTTP methods:
|
||||||
|
|
||||||
|
r.Methods("GET", "POST")
|
||||||
|
|
||||||
|
...or URL schemes:
|
||||||
|
|
||||||
|
r.Schemes("https")
|
||||||
|
|
||||||
|
...or header values:
|
||||||
|
|
||||||
|
r.Headers("X-Requested-With", "XMLHttpRequest")
|
||||||
|
|
||||||
|
...or query values:
|
||||||
|
|
||||||
|
r.Queries("key", "value")
|
||||||
|
|
||||||
|
...or to use a custom matcher function:
|
||||||
|
|
||||||
|
r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
|
||||||
|
return r.ProtoMajor == 0
|
||||||
|
})
|
||||||
|
|
||||||
|
...and finally, it is possible to combine several matchers in a single route:
|
||||||
|
|
||||||
|
r.HandleFunc("/products", ProductsHandler).
|
||||||
|
Host("www.example.com").
|
||||||
|
Methods("GET").
|
||||||
|
Schemes("http")
|
||||||
|
|
||||||
|
Setting the same matching conditions again and again can be boring, so we have
|
||||||
|
a way to group several routes that share the same requirements.
|
||||||
|
We call it "subrouting".
|
||||||
|
|
||||||
|
For example, let's say we have several URLs that should only match when the
|
||||||
|
host is "www.example.com". Create a route for that host and get a "subrouter"
|
||||||
|
from it:
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
s := r.Host("www.example.com").Subrouter()
|
||||||
|
|
||||||
|
Then register routes in the subrouter:
|
||||||
|
|
||||||
|
s.HandleFunc("/products/", ProductsHandler)
|
||||||
|
s.HandleFunc("/products/{key}", ProductHandler)
|
||||||
|
s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
|
||||||
|
|
||||||
|
The three URL paths we registered above will only be tested if the domain is
|
||||||
|
"www.example.com", because the subrouter is tested first. This is not
|
||||||
|
only convenient, but also optimizes request matching. You can create
|
||||||
|
subrouters combining any attribute matchers accepted by a route.
|
||||||
|
|
||||||
|
Subrouters can be used to create domain or path "namespaces": you define
|
||||||
|
subrouters in a central place and then parts of the app can register its
|
||||||
|
paths relatively to a given subrouter.
|
||||||
|
|
||||||
|
There's one more thing about subroutes. When a subrouter has a path prefix,
|
||||||
|
the inner routes use it as base for their paths:
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
s := r.PathPrefix("/products").Subrouter()
|
||||||
|
// "/products/"
|
||||||
|
s.HandleFunc("/", ProductsHandler)
|
||||||
|
// "/products/{key}/"
|
||||||
|
s.HandleFunc("/{key}/", ProductHandler)
|
||||||
|
// "/products/{key}/details"
|
||||||
|
s.HandleFunc("/{key}/details", ProductDetailsHandler)
|
||||||
|
|
||||||
|
Note that the path provided to PathPrefix() represents a "wildcard": calling
|
||||||
|
PathPrefix("/static/").Handler(...) means that the handler will be passed any
|
||||||
|
request that matches "/static/*". This makes it easy to serve static files with mux:
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var dir string
|
||||||
|
|
||||||
|
flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir")
|
||||||
|
flag.Parse()
|
||||||
|
r := mux.NewRouter()
|
||||||
|
|
||||||
|
// This will serve files under http://localhost:8000/static/<filename>
|
||||||
|
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir))))
|
||||||
|
|
||||||
|
srv := &http.Server{
|
||||||
|
Handler: r,
|
||||||
|
Addr: "127.0.0.1:8000",
|
||||||
|
// Good practice: enforce timeouts for servers you create!
|
||||||
|
WriteTimeout: 15 * time.Second,
|
||||||
|
ReadTimeout: 15 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Fatal(srv.ListenAndServe())
|
||||||
|
}
|
||||||
|
|
||||||
|
Now let's see how to build registered URLs.
|
||||||
|
|
||||||
|
Routes can be named. All routes that define a name can have their URLs built,
|
||||||
|
or "reversed". We define a name calling Name() on a route. For example:
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
|
||||||
|
Name("article")
|
||||||
|
|
||||||
|
To build a URL, get the route and call the URL() method, passing a sequence of
|
||||||
|
key/value pairs for the route variables. For the previous route, we would do:
|
||||||
|
|
||||||
|
url, err := r.Get("article").URL("category", "technology", "id", "42")
|
||||||
|
|
||||||
|
...and the result will be a url.URL with the following path:
|
||||||
|
|
||||||
|
"/articles/technology/42"
|
||||||
|
|
||||||
|
This also works for host and query value variables:
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.Host("{subdomain}.domain.com").
|
||||||
|
Path("/articles/{category}/{id:[0-9]+}").
|
||||||
|
Queries("filter", "{filter}").
|
||||||
|
HandlerFunc(ArticleHandler).
|
||||||
|
Name("article")
|
||||||
|
|
||||||
|
// url.String() will be "http://news.domain.com/articles/technology/42?filter=gorilla"
|
||||||
|
url, err := r.Get("article").URL("subdomain", "news",
|
||||||
|
"category", "technology",
|
||||||
|
"id", "42",
|
||||||
|
"filter", "gorilla")
|
||||||
|
|
||||||
|
All variables defined in the route are required, and their values must
|
||||||
|
conform to the corresponding patterns. These requirements guarantee that a
|
||||||
|
generated URL will always match a registered route -- the only exception is
|
||||||
|
for explicitly defined "build-only" routes which never match.
|
||||||
|
|
||||||
|
Regex support also exists for matching Headers within a route. For example, we could do:
|
||||||
|
|
||||||
|
r.HeadersRegexp("Content-Type", "application/(text|json)")
|
||||||
|
|
||||||
|
...and the route will match both requests with a Content-Type of `application/json` as well as
|
||||||
|
`application/text`
|
||||||
|
|
||||||
|
There's also a way to build only the URL host or path for a route:
|
||||||
|
use the methods URLHost() or URLPath() instead. For the previous route,
|
||||||
|
we would do:
|
||||||
|
|
||||||
|
// "http://news.domain.com/"
|
||||||
|
host, err := r.Get("article").URLHost("subdomain", "news")
|
||||||
|
|
||||||
|
// "/articles/technology/42"
|
||||||
|
path, err := r.Get("article").URLPath("category", "technology", "id", "42")
|
||||||
|
|
||||||
|
And if you use subrouters, host and path defined separately can be built
|
||||||
|
as well:
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
s := r.Host("{subdomain}.domain.com").Subrouter()
|
||||||
|
s.Path("/articles/{category}/{id:[0-9]+}").
|
||||||
|
HandlerFunc(ArticleHandler).
|
||||||
|
Name("article")
|
||||||
|
|
||||||
|
// "http://news.domain.com/articles/technology/42"
|
||||||
|
url, err := r.Get("article").URL("subdomain", "news",
|
||||||
|
"category", "technology",
|
||||||
|
"id", "42")
|
||||||
|
|
||||||
|
Mux supports the addition of middlewares to a Router, which are executed in the order they are added if a match is found, including its subrouters. Middlewares are (typically) small pieces of code which take one request, do something with it, and pass it down to another middleware or the final handler. Some common use cases for middleware are request logging, header manipulation, or ResponseWriter hijacking.
|
||||||
|
|
||||||
|
type MiddlewareFunc func(http.Handler) http.Handler
|
||||||
|
|
||||||
|
Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed to it, and then calls the handler passed as parameter to the MiddlewareFunc (closures can access variables from the context where they are created).
|
||||||
|
|
||||||
|
A very basic middleware which logs the URI of the request being handled could be written as:
|
||||||
|
|
||||||
|
func simpleMw(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Do stuff here
|
||||||
|
log.Println(r.RequestURI)
|
||||||
|
// Call the next handler, which can be another middleware in the chain, or the final handler.
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Middlewares can be added to a router using `Router.Use()`:
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/", handler)
|
||||||
|
r.Use(simpleMw)
|
||||||
|
|
||||||
|
A more complex authentication middleware, which maps session token to users, could be written as:
|
||||||
|
|
||||||
|
// Define our struct
|
||||||
|
type authenticationMiddleware struct {
|
||||||
|
tokenUsers map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize it somewhere
|
||||||
|
func (amw *authenticationMiddleware) Populate() {
|
||||||
|
amw.tokenUsers["00000000"] = "user0"
|
||||||
|
amw.tokenUsers["aaaaaaaa"] = "userA"
|
||||||
|
amw.tokenUsers["05f717e5"] = "randomUser"
|
||||||
|
amw.tokenUsers["deadbeef"] = "user0"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Middleware function, which will be called for each request
|
||||||
|
func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
token := r.Header.Get("X-Session-Token")
|
||||||
|
|
||||||
|
if user, found := amw.tokenUsers[token]; found {
|
||||||
|
// We found the token in our map
|
||||||
|
log.Printf("Authenticated user %s\n", user)
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
} else {
|
||||||
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/", handler)
|
||||||
|
|
||||||
|
amw := authenticationMiddleware{tokenUsers: make(map[string]string)}
|
||||||
|
amw.Populate()
|
||||||
|
|
||||||
|
r.Use(amw.Middleware)
|
||||||
|
|
||||||
|
Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to.
|
||||||
|
|
||||||
|
*/
|
||||||
|
package mux
|
74
vendor/github.com/gorilla/mux/middleware.go
generated
vendored
Normal file
74
vendor/github.com/gorilla/mux/middleware.go
generated
vendored
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
package mux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MiddlewareFunc is a function which receives an http.Handler and returns another http.Handler.
|
||||||
|
// Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed
|
||||||
|
// to it, and then calls the handler passed as parameter to the MiddlewareFunc.
|
||||||
|
type MiddlewareFunc func(http.Handler) http.Handler
|
||||||
|
|
||||||
|
// middleware interface is anything which implements a MiddlewareFunc named Middleware.
|
||||||
|
type middleware interface {
|
||||||
|
Middleware(handler http.Handler) http.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// Middleware allows MiddlewareFunc to implement the middleware interface.
|
||||||
|
func (mw MiddlewareFunc) Middleware(handler http.Handler) http.Handler {
|
||||||
|
return mw(handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use appends a MiddlewareFunc to the chain. Middleware can be used to intercept or otherwise modify requests and/or responses, and are executed in the order that they are applied to the Router.
|
||||||
|
func (r *Router) Use(mwf ...MiddlewareFunc) {
|
||||||
|
for _, fn := range mwf {
|
||||||
|
r.middlewares = append(r.middlewares, fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// useInterface appends a middleware to the chain. Middleware can be used to intercept or otherwise modify requests and/or responses, and are executed in the order that they are applied to the Router.
|
||||||
|
func (r *Router) useInterface(mw middleware) {
|
||||||
|
r.middlewares = append(r.middlewares, mw)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORSMethodMiddleware automatically sets the Access-Control-Allow-Methods response header
|
||||||
|
// on requests for routes that have an OPTIONS method matcher to all the method matchers on
|
||||||
|
// the route. Routes that do not explicitly handle OPTIONS requests will not be processed
|
||||||
|
// by the middleware. See examples for usage.
|
||||||
|
func CORSMethodMiddleware(r *Router) MiddlewareFunc {
|
||||||
|
return func(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
allMethods, err := getAllMethodsForRoute(r, req)
|
||||||
|
if err == nil {
|
||||||
|
for _, v := range allMethods {
|
||||||
|
if v == http.MethodOptions {
|
||||||
|
w.Header().Set("Access-Control-Allow-Methods", strings.Join(allMethods, ","))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
next.ServeHTTP(w, req)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getAllMethodsForRoute returns all the methods from method matchers matching a given
|
||||||
|
// request.
|
||||||
|
func getAllMethodsForRoute(r *Router, req *http.Request) ([]string, error) {
|
||||||
|
var allMethods []string
|
||||||
|
|
||||||
|
for _, route := range r.routes {
|
||||||
|
var match RouteMatch
|
||||||
|
if route.Match(req, &match) || match.MatchErr == ErrMethodMismatch {
|
||||||
|
methods, err := route.GetMethods()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
allMethods = append(allMethods, methods...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allMethods, nil
|
||||||
|
}
|
606
vendor/github.com/gorilla/mux/mux.go
generated
vendored
Normal file
606
vendor/github.com/gorilla/mux/mux.go
generated
vendored
Normal file
|
@ -0,0 +1,606 @@
|
||||||
|
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package mux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrMethodMismatch is returned when the method in the request does not match
|
||||||
|
// the method defined against the route.
|
||||||
|
ErrMethodMismatch = errors.New("method is not allowed")
|
||||||
|
// ErrNotFound is returned when no route match is found.
|
||||||
|
ErrNotFound = errors.New("no matching route was found")
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewRouter returns a new router instance.
|
||||||
|
func NewRouter() *Router {
|
||||||
|
return &Router{namedRoutes: make(map[string]*Route)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Router registers routes to be matched and dispatches a handler.
|
||||||
|
//
|
||||||
|
// It implements the http.Handler interface, so it can be registered to serve
|
||||||
|
// requests:
|
||||||
|
//
|
||||||
|
// var router = mux.NewRouter()
|
||||||
|
//
|
||||||
|
// func main() {
|
||||||
|
// http.Handle("/", router)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Or, for Google App Engine, register it in a init() function:
|
||||||
|
//
|
||||||
|
// func init() {
|
||||||
|
// http.Handle("/", router)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// This will send all incoming requests to the router.
|
||||||
|
type Router struct {
|
||||||
|
// Configurable Handler to be used when no route matches.
|
||||||
|
NotFoundHandler http.Handler
|
||||||
|
|
||||||
|
// Configurable Handler to be used when the request method does not match the route.
|
||||||
|
MethodNotAllowedHandler http.Handler
|
||||||
|
|
||||||
|
// Routes to be matched, in order.
|
||||||
|
routes []*Route
|
||||||
|
|
||||||
|
// Routes by name for URL building.
|
||||||
|
namedRoutes map[string]*Route
|
||||||
|
|
||||||
|
// If true, do not clear the request context after handling the request.
|
||||||
|
//
|
||||||
|
// Deprecated: No effect, since the context is stored on the request itself.
|
||||||
|
KeepContext bool
|
||||||
|
|
||||||
|
// Slice of middlewares to be called after a match is found
|
||||||
|
middlewares []middleware
|
||||||
|
|
||||||
|
// configuration shared with `Route`
|
||||||
|
routeConf
|
||||||
|
}
|
||||||
|
|
||||||
|
// common route configuration shared between `Router` and `Route`
|
||||||
|
type routeConf struct {
|
||||||
|
// If true, "/path/foo%2Fbar/to" will match the path "/path/{var}/to"
|
||||||
|
useEncodedPath bool
|
||||||
|
|
||||||
|
// If true, when the path pattern is "/path/", accessing "/path" will
|
||||||
|
// redirect to the former and vice versa.
|
||||||
|
strictSlash bool
|
||||||
|
|
||||||
|
// If true, when the path pattern is "/path//to", accessing "/path//to"
|
||||||
|
// will not redirect
|
||||||
|
skipClean bool
|
||||||
|
|
||||||
|
// Manager for the variables from host and path.
|
||||||
|
regexp routeRegexpGroup
|
||||||
|
|
||||||
|
// List of matchers.
|
||||||
|
matchers []matcher
|
||||||
|
|
||||||
|
// The scheme used when building URLs.
|
||||||
|
buildScheme string
|
||||||
|
|
||||||
|
buildVarsFunc BuildVarsFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns an effective deep copy of `routeConf`
|
||||||
|
func copyRouteConf(r routeConf) routeConf {
|
||||||
|
c := r
|
||||||
|
|
||||||
|
if r.regexp.path != nil {
|
||||||
|
c.regexp.path = copyRouteRegexp(r.regexp.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.regexp.host != nil {
|
||||||
|
c.regexp.host = copyRouteRegexp(r.regexp.host)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.regexp.queries = make([]*routeRegexp, 0, len(r.regexp.queries))
|
||||||
|
for _, q := range r.regexp.queries {
|
||||||
|
c.regexp.queries = append(c.regexp.queries, copyRouteRegexp(q))
|
||||||
|
}
|
||||||
|
|
||||||
|
c.matchers = make([]matcher, len(r.matchers))
|
||||||
|
copy(c.matchers, r.matchers)
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyRouteRegexp(r *routeRegexp) *routeRegexp {
|
||||||
|
c := *r
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match attempts to match the given request against the router's registered routes.
|
||||||
|
//
|
||||||
|
// If the request matches a route of this router or one of its subrouters the Route,
|
||||||
|
// Handler, and Vars fields of the the match argument are filled and this function
|
||||||
|
// returns true.
|
||||||
|
//
|
||||||
|
// If the request does not match any of this router's or its subrouters' routes
|
||||||
|
// then this function returns false. If available, a reason for the match failure
|
||||||
|
// will be filled in the match argument's MatchErr field. If the match failure type
|
||||||
|
// (eg: not found) has a registered handler, the handler is assigned to the Handler
|
||||||
|
// field of the match argument.
|
||||||
|
func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
|
||||||
|
for _, route := range r.routes {
|
||||||
|
if route.Match(req, match) {
|
||||||
|
// Build middleware chain if no error was found
|
||||||
|
if match.MatchErr == nil {
|
||||||
|
for i := len(r.middlewares) - 1; i >= 0; i-- {
|
||||||
|
match.Handler = r.middlewares[i].Middleware(match.Handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if match.MatchErr == ErrMethodMismatch {
|
||||||
|
if r.MethodNotAllowedHandler != nil {
|
||||||
|
match.Handler = r.MethodNotAllowedHandler
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Closest match for a router (includes sub-routers)
|
||||||
|
if r.NotFoundHandler != nil {
|
||||||
|
match.Handler = r.NotFoundHandler
|
||||||
|
match.MatchErr = ErrNotFound
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
match.MatchErr = ErrNotFound
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHTTP dispatches the handler registered in the matched route.
|
||||||
|
//
|
||||||
|
// When there is a match, the route variables can be retrieved calling
|
||||||
|
// mux.Vars(request).
|
||||||
|
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
|
if !r.skipClean {
|
||||||
|
path := req.URL.Path
|
||||||
|
if r.useEncodedPath {
|
||||||
|
path = req.URL.EscapedPath()
|
||||||
|
}
|
||||||
|
// Clean path to canonical form and redirect.
|
||||||
|
if p := cleanPath(path); p != path {
|
||||||
|
|
||||||
|
// Added 3 lines (Philip Schlump) - It was dropping the query string and #whatever from query.
|
||||||
|
// This matches with fix in go 1.2 r.c. 4 for same problem. Go Issue:
|
||||||
|
// http://code.google.com/p/go/issues/detail?id=5252
|
||||||
|
url := *req.URL
|
||||||
|
url.Path = p
|
||||||
|
p = url.String()
|
||||||
|
|
||||||
|
w.Header().Set("Location", p)
|
||||||
|
w.WriteHeader(http.StatusMovedPermanently)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var match RouteMatch
|
||||||
|
var handler http.Handler
|
||||||
|
if r.Match(req, &match) {
|
||||||
|
handler = match.Handler
|
||||||
|
req = requestWithVars(req, match.Vars)
|
||||||
|
req = requestWithRoute(req, match.Route)
|
||||||
|
}
|
||||||
|
|
||||||
|
if handler == nil && match.MatchErr == ErrMethodMismatch {
|
||||||
|
handler = methodNotAllowedHandler()
|
||||||
|
}
|
||||||
|
|
||||||
|
if handler == nil {
|
||||||
|
handler = http.NotFoundHandler()
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.ServeHTTP(w, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns a route registered with the given name.
|
||||||
|
func (r *Router) Get(name string) *Route {
|
||||||
|
return r.namedRoutes[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRoute returns a route registered with the given name. This method
|
||||||
|
// was renamed to Get() and remains here for backwards compatibility.
|
||||||
|
func (r *Router) GetRoute(name string) *Route {
|
||||||
|
return r.namedRoutes[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
// StrictSlash defines the trailing slash behavior for new routes. The initial
|
||||||
|
// value is false.
|
||||||
|
//
|
||||||
|
// When true, if the route path is "/path/", accessing "/path" will perform a redirect
|
||||||
|
// to the former and vice versa. In other words, your application will always
|
||||||
|
// see the path as specified in the route.
|
||||||
|
//
|
||||||
|
// When false, if the route path is "/path", accessing "/path/" will not match
|
||||||
|
// this route and vice versa.
|
||||||
|
//
|
||||||
|
// The re-direct is a HTTP 301 (Moved Permanently). Note that when this is set for
|
||||||
|
// routes with a non-idempotent method (e.g. POST, PUT), the subsequent re-directed
|
||||||
|
// request will be made as a GET by most clients. Use middleware or client settings
|
||||||
|
// to modify this behaviour as needed.
|
||||||
|
//
|
||||||
|
// Special case: when a route sets a path prefix using the PathPrefix() method,
|
||||||
|
// strict slash is ignored for that route because the redirect behavior can't
|
||||||
|
// be determined from a prefix alone. However, any subrouters created from that
|
||||||
|
// route inherit the original StrictSlash setting.
|
||||||
|
func (r *Router) StrictSlash(value bool) *Router {
|
||||||
|
r.strictSlash = value
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// SkipClean defines the path cleaning behaviour for new routes. The initial
|
||||||
|
// value is false. Users should be careful about which routes are not cleaned
|
||||||
|
//
|
||||||
|
// When true, if the route path is "/path//to", it will remain with the double
|
||||||
|
// slash. This is helpful if you have a route like: /fetch/http://xkcd.com/534/
|
||||||
|
//
|
||||||
|
// When false, the path will be cleaned, so /fetch/http://xkcd.com/534/ will
|
||||||
|
// become /fetch/http/xkcd.com/534
|
||||||
|
func (r *Router) SkipClean(value bool) *Router {
|
||||||
|
r.skipClean = value
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// UseEncodedPath tells the router to match the encoded original path
|
||||||
|
// to the routes.
|
||||||
|
// For eg. "/path/foo%2Fbar/to" will match the path "/path/{var}/to".
|
||||||
|
//
|
||||||
|
// If not called, the router will match the unencoded path to the routes.
|
||||||
|
// For eg. "/path/foo%2Fbar/to" will match the path "/path/foo/bar/to"
|
||||||
|
func (r *Router) UseEncodedPath() *Router {
|
||||||
|
r.useEncodedPath = true
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Route factories
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// NewRoute registers an empty route.
|
||||||
|
func (r *Router) NewRoute() *Route {
|
||||||
|
// initialize a route with a copy of the parent router's configuration
|
||||||
|
route := &Route{routeConf: copyRouteConf(r.routeConf), namedRoutes: r.namedRoutes}
|
||||||
|
r.routes = append(r.routes, route)
|
||||||
|
return route
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name registers a new route with a name.
|
||||||
|
// See Route.Name().
|
||||||
|
func (r *Router) Name(name string) *Route {
|
||||||
|
return r.NewRoute().Name(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle registers a new route with a matcher for the URL path.
|
||||||
|
// See Route.Path() and Route.Handler().
|
||||||
|
func (r *Router) Handle(path string, handler http.Handler) *Route {
|
||||||
|
return r.NewRoute().Path(path).Handler(handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleFunc registers a new route with a matcher for the URL path.
|
||||||
|
// See Route.Path() and Route.HandlerFunc().
|
||||||
|
func (r *Router) HandleFunc(path string, f func(http.ResponseWriter,
|
||||||
|
*http.Request)) *Route {
|
||||||
|
return r.NewRoute().Path(path).HandlerFunc(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Headers registers a new route with a matcher for request header values.
|
||||||
|
// See Route.Headers().
|
||||||
|
func (r *Router) Headers(pairs ...string) *Route {
|
||||||
|
return r.NewRoute().Headers(pairs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Host registers a new route with a matcher for the URL host.
|
||||||
|
// See Route.Host().
|
||||||
|
func (r *Router) Host(tpl string) *Route {
|
||||||
|
return r.NewRoute().Host(tpl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatcherFunc registers a new route with a custom matcher function.
|
||||||
|
// See Route.MatcherFunc().
|
||||||
|
func (r *Router) MatcherFunc(f MatcherFunc) *Route {
|
||||||
|
return r.NewRoute().MatcherFunc(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods registers a new route with a matcher for HTTP methods.
|
||||||
|
// See Route.Methods().
|
||||||
|
func (r *Router) Methods(methods ...string) *Route {
|
||||||
|
return r.NewRoute().Methods(methods...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path registers a new route with a matcher for the URL path.
|
||||||
|
// See Route.Path().
|
||||||
|
func (r *Router) Path(tpl string) *Route {
|
||||||
|
return r.NewRoute().Path(tpl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PathPrefix registers a new route with a matcher for the URL path prefix.
|
||||||
|
// See Route.PathPrefix().
|
||||||
|
func (r *Router) PathPrefix(tpl string) *Route {
|
||||||
|
return r.NewRoute().PathPrefix(tpl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Queries registers a new route with a matcher for URL query values.
|
||||||
|
// See Route.Queries().
|
||||||
|
func (r *Router) Queries(pairs ...string) *Route {
|
||||||
|
return r.NewRoute().Queries(pairs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schemes registers a new route with a matcher for URL schemes.
|
||||||
|
// See Route.Schemes().
|
||||||
|
func (r *Router) Schemes(schemes ...string) *Route {
|
||||||
|
return r.NewRoute().Schemes(schemes...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildVarsFunc registers a new route with a custom function for modifying
|
||||||
|
// route variables before building a URL.
|
||||||
|
func (r *Router) BuildVarsFunc(f BuildVarsFunc) *Route {
|
||||||
|
return r.NewRoute().BuildVarsFunc(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk walks the router and all its sub-routers, calling walkFn for each route
|
||||||
|
// in the tree. The routes are walked in the order they were added. Sub-routers
|
||||||
|
// are explored depth-first.
|
||||||
|
func (r *Router) Walk(walkFn WalkFunc) error {
|
||||||
|
return r.walk(walkFn, []*Route{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SkipRouter is used as a return value from WalkFuncs to indicate that the
|
||||||
|
// router that walk is about to descend down to should be skipped.
|
||||||
|
var SkipRouter = errors.New("skip this router")
|
||||||
|
|
||||||
|
// WalkFunc is the type of the function called for each route visited by Walk.
|
||||||
|
// At every invocation, it is given the current route, and the current router,
|
||||||
|
// and a list of ancestor routes that lead to the current route.
|
||||||
|
type WalkFunc func(route *Route, router *Router, ancestors []*Route) error
|
||||||
|
|
||||||
|
func (r *Router) walk(walkFn WalkFunc, ancestors []*Route) error {
|
||||||
|
for _, t := range r.routes {
|
||||||
|
err := walkFn(t, r, ancestors)
|
||||||
|
if err == SkipRouter {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, sr := range t.matchers {
|
||||||
|
if h, ok := sr.(*Router); ok {
|
||||||
|
ancestors = append(ancestors, t)
|
||||||
|
err := h.walk(walkFn, ancestors)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ancestors = ancestors[:len(ancestors)-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if h, ok := t.handler.(*Router); ok {
|
||||||
|
ancestors = append(ancestors, t)
|
||||||
|
err := h.walk(walkFn, ancestors)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ancestors = ancestors[:len(ancestors)-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Context
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// RouteMatch stores information about a matched route.
|
||||||
|
type RouteMatch struct {
|
||||||
|
Route *Route
|
||||||
|
Handler http.Handler
|
||||||
|
Vars map[string]string
|
||||||
|
|
||||||
|
// MatchErr is set to appropriate matching error
|
||||||
|
// It is set to ErrMethodMismatch if there is a mismatch in
|
||||||
|
// the request method and route method
|
||||||
|
MatchErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
type contextKey int
|
||||||
|
|
||||||
|
const (
|
||||||
|
varsKey contextKey = iota
|
||||||
|
routeKey
|
||||||
|
)
|
||||||
|
|
||||||
|
// Vars returns the route variables for the current request, if any.
|
||||||
|
func Vars(r *http.Request) map[string]string {
|
||||||
|
if rv := r.Context().Value(varsKey); rv != nil {
|
||||||
|
return rv.(map[string]string)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurrentRoute returns the matched route for the current request, if any.
|
||||||
|
// This only works when called inside the handler of the matched route
|
||||||
|
// because the matched route is stored in the request context which is cleared
|
||||||
|
// after the handler returns.
|
||||||
|
func CurrentRoute(r *http.Request) *Route {
|
||||||
|
if rv := r.Context().Value(routeKey); rv != nil {
|
||||||
|
return rv.(*Route)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func requestWithVars(r *http.Request, vars map[string]string) *http.Request {
|
||||||
|
ctx := context.WithValue(r.Context(), varsKey, vars)
|
||||||
|
return r.WithContext(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func requestWithRoute(r *http.Request, route *Route) *http.Request {
|
||||||
|
ctx := context.WithValue(r.Context(), routeKey, route)
|
||||||
|
return r.WithContext(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Helpers
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// cleanPath returns the canonical path for p, eliminating . and .. elements.
|
||||||
|
// Borrowed from the net/http package.
|
||||||
|
func cleanPath(p string) string {
|
||||||
|
if p == "" {
|
||||||
|
return "/"
|
||||||
|
}
|
||||||
|
if p[0] != '/' {
|
||||||
|
p = "/" + p
|
||||||
|
}
|
||||||
|
np := path.Clean(p)
|
||||||
|
// path.Clean removes trailing slash except for root;
|
||||||
|
// put the trailing slash back if necessary.
|
||||||
|
if p[len(p)-1] == '/' && np != "/" {
|
||||||
|
np += "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
return np
|
||||||
|
}
|
||||||
|
|
||||||
|
// uniqueVars returns an error if two slices contain duplicated strings.
|
||||||
|
func uniqueVars(s1, s2 []string) error {
|
||||||
|
for _, v1 := range s1 {
|
||||||
|
for _, v2 := range s2 {
|
||||||
|
if v1 == v2 {
|
||||||
|
return fmt.Errorf("mux: duplicated route variable %q", v2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkPairs returns the count of strings passed in, and an error if
|
||||||
|
// the count is not an even number.
|
||||||
|
func checkPairs(pairs ...string) (int, error) {
|
||||||
|
length := len(pairs)
|
||||||
|
if length%2 != 0 {
|
||||||
|
return length, fmt.Errorf(
|
||||||
|
"mux: number of parameters must be multiple of 2, got %v", pairs)
|
||||||
|
}
|
||||||
|
return length, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// mapFromPairsToString converts variadic string parameters to a
|
||||||
|
// string to string map.
|
||||||
|
func mapFromPairsToString(pairs ...string) (map[string]string, error) {
|
||||||
|
length, err := checkPairs(pairs...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m := make(map[string]string, length/2)
|
||||||
|
for i := 0; i < length; i += 2 {
|
||||||
|
m[pairs[i]] = pairs[i+1]
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// mapFromPairsToRegex converts variadic string parameters to a
|
||||||
|
// string to regex map.
|
||||||
|
func mapFromPairsToRegex(pairs ...string) (map[string]*regexp.Regexp, error) {
|
||||||
|
length, err := checkPairs(pairs...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m := make(map[string]*regexp.Regexp, length/2)
|
||||||
|
for i := 0; i < length; i += 2 {
|
||||||
|
regex, err := regexp.Compile(pairs[i+1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m[pairs[i]] = regex
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchInArray returns true if the given string value is in the array.
|
||||||
|
func matchInArray(arr []string, value string) bool {
|
||||||
|
for _, v := range arr {
|
||||||
|
if v == value {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchMapWithString returns true if the given key/value pairs exist in a given map.
|
||||||
|
func matchMapWithString(toCheck map[string]string, toMatch map[string][]string, canonicalKey bool) bool {
|
||||||
|
for k, v := range toCheck {
|
||||||
|
// Check if key exists.
|
||||||
|
if canonicalKey {
|
||||||
|
k = http.CanonicalHeaderKey(k)
|
||||||
|
}
|
||||||
|
if values := toMatch[k]; values == nil {
|
||||||
|
return false
|
||||||
|
} else if v != "" {
|
||||||
|
// If value was defined as an empty string we only check that the
|
||||||
|
// key exists. Otherwise we also check for equality.
|
||||||
|
valueExists := false
|
||||||
|
for _, value := range values {
|
||||||
|
if v == value {
|
||||||
|
valueExists = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !valueExists {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchMapWithRegex returns true if the given key/value pairs exist in a given map compiled against
|
||||||
|
// the given regex
|
||||||
|
func matchMapWithRegex(toCheck map[string]*regexp.Regexp, toMatch map[string][]string, canonicalKey bool) bool {
|
||||||
|
for k, v := range toCheck {
|
||||||
|
// Check if key exists.
|
||||||
|
if canonicalKey {
|
||||||
|
k = http.CanonicalHeaderKey(k)
|
||||||
|
}
|
||||||
|
if values := toMatch[k]; values == nil {
|
||||||
|
return false
|
||||||
|
} else if v != nil {
|
||||||
|
// If value was defined as an empty string we only check that the
|
||||||
|
// key exists. Otherwise we also check for equality.
|
||||||
|
valueExists := false
|
||||||
|
for _, value := range values {
|
||||||
|
if v.MatchString(value) {
|
||||||
|
valueExists = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !valueExists {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// methodNotAllowed replies to the request with an HTTP status code 405.
|
||||||
|
func methodNotAllowed(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// methodNotAllowedHandler returns a simple request handler
|
||||||
|
// that replies to each request with a status code 405.
|
||||||
|
func methodNotAllowedHandler() http.Handler { return http.HandlerFunc(methodNotAllowed) }
|
388
vendor/github.com/gorilla/mux/regexp.go
generated
vendored
Normal file
388
vendor/github.com/gorilla/mux/regexp.go
generated
vendored
Normal file
|
@ -0,0 +1,388 @@
|
||||||
|
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package mux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type routeRegexpOptions struct {
|
||||||
|
strictSlash bool
|
||||||
|
useEncodedPath bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type regexpType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
regexpTypePath regexpType = 0
|
||||||
|
regexpTypeHost regexpType = 1
|
||||||
|
regexpTypePrefix regexpType = 2
|
||||||
|
regexpTypeQuery regexpType = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
// newRouteRegexp parses a route template and returns a routeRegexp,
|
||||||
|
// used to match a host, a path or a query string.
|
||||||
|
//
|
||||||
|
// It will extract named variables, assemble a regexp to be matched, create
|
||||||
|
// a "reverse" template to build URLs and compile regexps to validate variable
|
||||||
|
// values used in URL building.
|
||||||
|
//
|
||||||
|
// Previously we accepted only Python-like identifiers for variable
|
||||||
|
// names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that
|
||||||
|
// name and pattern can't be empty, and names can't contain a colon.
|
||||||
|
func newRouteRegexp(tpl string, typ regexpType, options routeRegexpOptions) (*routeRegexp, error) {
|
||||||
|
// Check if it is well-formed.
|
||||||
|
idxs, errBraces := braceIndices(tpl)
|
||||||
|
if errBraces != nil {
|
||||||
|
return nil, errBraces
|
||||||
|
}
|
||||||
|
// Backup the original.
|
||||||
|
template := tpl
|
||||||
|
// Now let's parse it.
|
||||||
|
defaultPattern := "[^/]+"
|
||||||
|
if typ == regexpTypeQuery {
|
||||||
|
defaultPattern = ".*"
|
||||||
|
} else if typ == regexpTypeHost {
|
||||||
|
defaultPattern = "[^.]+"
|
||||||
|
}
|
||||||
|
// Only match strict slash if not matching
|
||||||
|
if typ != regexpTypePath {
|
||||||
|
options.strictSlash = false
|
||||||
|
}
|
||||||
|
// Set a flag for strictSlash.
|
||||||
|
endSlash := false
|
||||||
|
if options.strictSlash && strings.HasSuffix(tpl, "/") {
|
||||||
|
tpl = tpl[:len(tpl)-1]
|
||||||
|
endSlash = true
|
||||||
|
}
|
||||||
|
varsN := make([]string, len(idxs)/2)
|
||||||
|
varsR := make([]*regexp.Regexp, len(idxs)/2)
|
||||||
|
pattern := bytes.NewBufferString("")
|
||||||
|
pattern.WriteByte('^')
|
||||||
|
reverse := bytes.NewBufferString("")
|
||||||
|
var end int
|
||||||
|
var err error
|
||||||
|
for i := 0; i < len(idxs); i += 2 {
|
||||||
|
// Set all values we are interested in.
|
||||||
|
raw := tpl[end:idxs[i]]
|
||||||
|
end = idxs[i+1]
|
||||||
|
parts := strings.SplitN(tpl[idxs[i]+1:end-1], ":", 2)
|
||||||
|
name := parts[0]
|
||||||
|
patt := defaultPattern
|
||||||
|
if len(parts) == 2 {
|
||||||
|
patt = parts[1]
|
||||||
|
}
|
||||||
|
// Name or pattern can't be empty.
|
||||||
|
if name == "" || patt == "" {
|
||||||
|
return nil, fmt.Errorf("mux: missing name or pattern in %q",
|
||||||
|
tpl[idxs[i]:end])
|
||||||
|
}
|
||||||
|
// Build the regexp pattern.
|
||||||
|
fmt.Fprintf(pattern, "%s(?P<%s>%s)", regexp.QuoteMeta(raw), varGroupName(i/2), patt)
|
||||||
|
|
||||||
|
// Build the reverse template.
|
||||||
|
fmt.Fprintf(reverse, "%s%%s", raw)
|
||||||
|
|
||||||
|
// Append variable name and compiled pattern.
|
||||||
|
varsN[i/2] = name
|
||||||
|
varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add the remaining.
|
||||||
|
raw := tpl[end:]
|
||||||
|
pattern.WriteString(regexp.QuoteMeta(raw))
|
||||||
|
if options.strictSlash {
|
||||||
|
pattern.WriteString("[/]?")
|
||||||
|
}
|
||||||
|
if typ == regexpTypeQuery {
|
||||||
|
// Add the default pattern if the query value is empty
|
||||||
|
if queryVal := strings.SplitN(template, "=", 2)[1]; queryVal == "" {
|
||||||
|
pattern.WriteString(defaultPattern)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if typ != regexpTypePrefix {
|
||||||
|
pattern.WriteByte('$')
|
||||||
|
}
|
||||||
|
|
||||||
|
var wildcardHostPort bool
|
||||||
|
if typ == regexpTypeHost {
|
||||||
|
if !strings.Contains(pattern.String(), ":") {
|
||||||
|
wildcardHostPort = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reverse.WriteString(raw)
|
||||||
|
if endSlash {
|
||||||
|
reverse.WriteByte('/')
|
||||||
|
}
|
||||||
|
// Compile full regexp.
|
||||||
|
reg, errCompile := regexp.Compile(pattern.String())
|
||||||
|
if errCompile != nil {
|
||||||
|
return nil, errCompile
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for capturing groups which used to work in older versions
|
||||||
|
if reg.NumSubexp() != len(idxs)/2 {
|
||||||
|
panic(fmt.Sprintf("route %s contains capture groups in its regexp. ", template) +
|
||||||
|
"Only non-capturing groups are accepted: e.g. (?:pattern) instead of (pattern)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Done!
|
||||||
|
return &routeRegexp{
|
||||||
|
template: template,
|
||||||
|
regexpType: typ,
|
||||||
|
options: options,
|
||||||
|
regexp: reg,
|
||||||
|
reverse: reverse.String(),
|
||||||
|
varsN: varsN,
|
||||||
|
varsR: varsR,
|
||||||
|
wildcardHostPort: wildcardHostPort,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// routeRegexp stores a regexp to match a host or path and information to
|
||||||
|
// collect and validate route variables.
|
||||||
|
type routeRegexp struct {
|
||||||
|
// The unmodified template.
|
||||||
|
template string
|
||||||
|
// The type of match
|
||||||
|
regexpType regexpType
|
||||||
|
// Options for matching
|
||||||
|
options routeRegexpOptions
|
||||||
|
// Expanded regexp.
|
||||||
|
regexp *regexp.Regexp
|
||||||
|
// Reverse template.
|
||||||
|
reverse string
|
||||||
|
// Variable names.
|
||||||
|
varsN []string
|
||||||
|
// Variable regexps (validators).
|
||||||
|
varsR []*regexp.Regexp
|
||||||
|
// Wildcard host-port (no strict port match in hostname)
|
||||||
|
wildcardHostPort bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match matches the regexp against the URL host or path.
|
||||||
|
func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
|
||||||
|
if r.regexpType == regexpTypeHost {
|
||||||
|
host := getHost(req)
|
||||||
|
if r.wildcardHostPort {
|
||||||
|
// Don't be strict on the port match
|
||||||
|
if i := strings.Index(host, ":"); i != -1 {
|
||||||
|
host = host[:i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r.regexp.MatchString(host)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.regexpType == regexpTypeQuery {
|
||||||
|
return r.matchQueryString(req)
|
||||||
|
}
|
||||||
|
path := req.URL.Path
|
||||||
|
if r.options.useEncodedPath {
|
||||||
|
path = req.URL.EscapedPath()
|
||||||
|
}
|
||||||
|
return r.regexp.MatchString(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// url builds a URL part using the given values.
|
||||||
|
func (r *routeRegexp) url(values map[string]string) (string, error) {
|
||||||
|
urlValues := make([]interface{}, len(r.varsN), len(r.varsN))
|
||||||
|
for k, v := range r.varsN {
|
||||||
|
value, ok := values[v]
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("mux: missing route variable %q", v)
|
||||||
|
}
|
||||||
|
if r.regexpType == regexpTypeQuery {
|
||||||
|
value = url.QueryEscape(value)
|
||||||
|
}
|
||||||
|
urlValues[k] = value
|
||||||
|
}
|
||||||
|
rv := fmt.Sprintf(r.reverse, urlValues...)
|
||||||
|
if !r.regexp.MatchString(rv) {
|
||||||
|
// The URL is checked against the full regexp, instead of checking
|
||||||
|
// individual variables. This is faster but to provide a good error
|
||||||
|
// message, we check individual regexps if the URL doesn't match.
|
||||||
|
for k, v := range r.varsN {
|
||||||
|
if !r.varsR[k].MatchString(values[v]) {
|
||||||
|
return "", fmt.Errorf(
|
||||||
|
"mux: variable %q doesn't match, expected %q", values[v],
|
||||||
|
r.varsR[k].String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getURLQuery returns a single query parameter from a request URL.
|
||||||
|
// For a URL with foo=bar&baz=ding, we return only the relevant key
|
||||||
|
// value pair for the routeRegexp.
|
||||||
|
func (r *routeRegexp) getURLQuery(req *http.Request) string {
|
||||||
|
if r.regexpType != regexpTypeQuery {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
templateKey := strings.SplitN(r.template, "=", 2)[0]
|
||||||
|
val, ok := findFirstQueryKey(req.URL.RawQuery, templateKey)
|
||||||
|
if ok {
|
||||||
|
return templateKey + "=" + val
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// findFirstQueryKey returns the same result as (*url.URL).Query()[key][0].
|
||||||
|
// If key was not found, empty string and false is returned.
|
||||||
|
func findFirstQueryKey(rawQuery, key string) (value string, ok bool) {
|
||||||
|
query := []byte(rawQuery)
|
||||||
|
for len(query) > 0 {
|
||||||
|
foundKey := query
|
||||||
|
if i := bytes.IndexAny(foundKey, "&;"); i >= 0 {
|
||||||
|
foundKey, query = foundKey[:i], foundKey[i+1:]
|
||||||
|
} else {
|
||||||
|
query = query[:0]
|
||||||
|
}
|
||||||
|
if len(foundKey) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var value []byte
|
||||||
|
if i := bytes.IndexByte(foundKey, '='); i >= 0 {
|
||||||
|
foundKey, value = foundKey[:i], foundKey[i+1:]
|
||||||
|
}
|
||||||
|
if len(foundKey) < len(key) {
|
||||||
|
// Cannot possibly be key.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
keyString, err := url.QueryUnescape(string(foundKey))
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if keyString != key {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
valueString, err := url.QueryUnescape(string(value))
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return valueString, true
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *routeRegexp) matchQueryString(req *http.Request) bool {
|
||||||
|
return r.regexp.MatchString(r.getURLQuery(req))
|
||||||
|
}
|
||||||
|
|
||||||
|
// braceIndices returns the first level curly brace indices from a string.
|
||||||
|
// It returns an error in case of unbalanced braces.
|
||||||
|
func braceIndices(s string) ([]int, error) {
|
||||||
|
var level, idx int
|
||||||
|
var idxs []int
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
switch s[i] {
|
||||||
|
case '{':
|
||||||
|
if level++; level == 1 {
|
||||||
|
idx = i
|
||||||
|
}
|
||||||
|
case '}':
|
||||||
|
if level--; level == 0 {
|
||||||
|
idxs = append(idxs, idx, i+1)
|
||||||
|
} else if level < 0 {
|
||||||
|
return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if level != 0 {
|
||||||
|
return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
|
||||||
|
}
|
||||||
|
return idxs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// varGroupName builds a capturing group name for the indexed variable.
|
||||||
|
func varGroupName(idx int) string {
|
||||||
|
return "v" + strconv.Itoa(idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// routeRegexpGroup
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// routeRegexpGroup groups the route matchers that carry variables.
|
||||||
|
type routeRegexpGroup struct {
|
||||||
|
host *routeRegexp
|
||||||
|
path *routeRegexp
|
||||||
|
queries []*routeRegexp
|
||||||
|
}
|
||||||
|
|
||||||
|
// setMatch extracts the variables from the URL once a route matches.
|
||||||
|
func (v routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) {
|
||||||
|
// Store host variables.
|
||||||
|
if v.host != nil {
|
||||||
|
host := getHost(req)
|
||||||
|
if v.host.wildcardHostPort {
|
||||||
|
// Don't be strict on the port match
|
||||||
|
if i := strings.Index(host, ":"); i != -1 {
|
||||||
|
host = host[:i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
matches := v.host.regexp.FindStringSubmatchIndex(host)
|
||||||
|
if len(matches) > 0 {
|
||||||
|
extractVars(host, matches, v.host.varsN, m.Vars)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
path := req.URL.Path
|
||||||
|
if r.useEncodedPath {
|
||||||
|
path = req.URL.EscapedPath()
|
||||||
|
}
|
||||||
|
// Store path variables.
|
||||||
|
if v.path != nil {
|
||||||
|
matches := v.path.regexp.FindStringSubmatchIndex(path)
|
||||||
|
if len(matches) > 0 {
|
||||||
|
extractVars(path, matches, v.path.varsN, m.Vars)
|
||||||
|
// Check if we should redirect.
|
||||||
|
if v.path.options.strictSlash {
|
||||||
|
p1 := strings.HasSuffix(path, "/")
|
||||||
|
p2 := strings.HasSuffix(v.path.template, "/")
|
||||||
|
if p1 != p2 {
|
||||||
|
u, _ := url.Parse(req.URL.String())
|
||||||
|
if p1 {
|
||||||
|
u.Path = u.Path[:len(u.Path)-1]
|
||||||
|
} else {
|
||||||
|
u.Path += "/"
|
||||||
|
}
|
||||||
|
m.Handler = http.RedirectHandler(u.String(), http.StatusMovedPermanently)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Store query string variables.
|
||||||
|
for _, q := range v.queries {
|
||||||
|
queryURL := q.getURLQuery(req)
|
||||||
|
matches := q.regexp.FindStringSubmatchIndex(queryURL)
|
||||||
|
if len(matches) > 0 {
|
||||||
|
extractVars(queryURL, matches, q.varsN, m.Vars)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getHost tries its best to return the request host.
|
||||||
|
// According to section 14.23 of RFC 2616 the Host header
|
||||||
|
// can include the port number if the default value of 80 is not used.
|
||||||
|
func getHost(r *http.Request) string {
|
||||||
|
if r.URL.IsAbs() {
|
||||||
|
return r.URL.Host
|
||||||
|
}
|
||||||
|
return r.Host
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractVars(input string, matches []int, names []string, output map[string]string) {
|
||||||
|
for i, name := range names {
|
||||||
|
output[name] = input[matches[2*i+2]:matches[2*i+3]]
|
||||||
|
}
|
||||||
|
}
|
736
vendor/github.com/gorilla/mux/route.go
generated
vendored
Normal file
736
vendor/github.com/gorilla/mux/route.go
generated
vendored
Normal file
|
@ -0,0 +1,736 @@
|
||||||
|
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package mux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Route stores information to match a request and build URLs.
|
||||||
|
type Route struct {
|
||||||
|
// Request handler for the route.
|
||||||
|
handler http.Handler
|
||||||
|
// If true, this route never matches: it is only used to build URLs.
|
||||||
|
buildOnly bool
|
||||||
|
// The name used to build URLs.
|
||||||
|
name string
|
||||||
|
// Error resulted from building a route.
|
||||||
|
err error
|
||||||
|
|
||||||
|
// "global" reference to all named routes
|
||||||
|
namedRoutes map[string]*Route
|
||||||
|
|
||||||
|
// config possibly passed in from `Router`
|
||||||
|
routeConf
|
||||||
|
}
|
||||||
|
|
||||||
|
// SkipClean reports whether path cleaning is enabled for this route via
|
||||||
|
// Router.SkipClean.
|
||||||
|
func (r *Route) SkipClean() bool {
|
||||||
|
return r.skipClean
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match matches the route against the request.
|
||||||
|
func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
|
||||||
|
if r.buildOnly || r.err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var matchErr error
|
||||||
|
|
||||||
|
// Match everything.
|
||||||
|
for _, m := range r.matchers {
|
||||||
|
if matched := m.Match(req, match); !matched {
|
||||||
|
if _, ok := m.(methodMatcher); ok {
|
||||||
|
matchErr = ErrMethodMismatch
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore ErrNotFound errors. These errors arise from match call
|
||||||
|
// to Subrouters.
|
||||||
|
//
|
||||||
|
// This prevents subsequent matching subrouters from failing to
|
||||||
|
// run middleware. If not ignored, the middleware would see a
|
||||||
|
// non-nil MatchErr and be skipped, even when there was a
|
||||||
|
// matching route.
|
||||||
|
if match.MatchErr == ErrNotFound {
|
||||||
|
match.MatchErr = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
matchErr = nil
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if matchErr != nil {
|
||||||
|
match.MatchErr = matchErr
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if match.MatchErr == ErrMethodMismatch && r.handler != nil {
|
||||||
|
// We found a route which matches request method, clear MatchErr
|
||||||
|
match.MatchErr = nil
|
||||||
|
// Then override the mis-matched handler
|
||||||
|
match.Handler = r.handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// Yay, we have a match. Let's collect some info about it.
|
||||||
|
if match.Route == nil {
|
||||||
|
match.Route = r
|
||||||
|
}
|
||||||
|
if match.Handler == nil {
|
||||||
|
match.Handler = r.handler
|
||||||
|
}
|
||||||
|
if match.Vars == nil {
|
||||||
|
match.Vars = make(map[string]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set variables.
|
||||||
|
r.regexp.setMatch(req, match, r)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Route attributes
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// GetError returns an error resulted from building the route, if any.
|
||||||
|
func (r *Route) GetError() error {
|
||||||
|
return r.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildOnly sets the route to never match: it is only used to build URLs.
|
||||||
|
func (r *Route) BuildOnly() *Route {
|
||||||
|
r.buildOnly = true
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler --------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Handler sets a handler for the route.
|
||||||
|
func (r *Route) Handler(handler http.Handler) *Route {
|
||||||
|
if r.err == nil {
|
||||||
|
r.handler = handler
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandlerFunc sets a handler function for the route.
|
||||||
|
func (r *Route) HandlerFunc(f func(http.ResponseWriter, *http.Request)) *Route {
|
||||||
|
return r.Handler(http.HandlerFunc(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHandler returns the handler for the route, if any.
|
||||||
|
func (r *Route) GetHandler() http.Handler {
|
||||||
|
return r.handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Name sets the name for the route, used to build URLs.
|
||||||
|
// It is an error to call Name more than once on a route.
|
||||||
|
func (r *Route) Name(name string) *Route {
|
||||||
|
if r.name != "" {
|
||||||
|
r.err = fmt.Errorf("mux: route already has name %q, can't set %q",
|
||||||
|
r.name, name)
|
||||||
|
}
|
||||||
|
if r.err == nil {
|
||||||
|
r.name = name
|
||||||
|
r.namedRoutes[name] = r
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetName returns the name for the route, if any.
|
||||||
|
func (r *Route) GetName() string {
|
||||||
|
return r.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Matchers
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// matcher types try to match a request.
|
||||||
|
type matcher interface {
|
||||||
|
Match(*http.Request, *RouteMatch) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// addMatcher adds a matcher to the route.
|
||||||
|
func (r *Route) addMatcher(m matcher) *Route {
|
||||||
|
if r.err == nil {
|
||||||
|
r.matchers = append(r.matchers, m)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// addRegexpMatcher adds a host or path matcher and builder to a route.
|
||||||
|
func (r *Route) addRegexpMatcher(tpl string, typ regexpType) error {
|
||||||
|
if r.err != nil {
|
||||||
|
return r.err
|
||||||
|
}
|
||||||
|
if typ == regexpTypePath || typ == regexpTypePrefix {
|
||||||
|
if len(tpl) > 0 && tpl[0] != '/' {
|
||||||
|
return fmt.Errorf("mux: path must start with a slash, got %q", tpl)
|
||||||
|
}
|
||||||
|
if r.regexp.path != nil {
|
||||||
|
tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rr, err := newRouteRegexp(tpl, typ, routeRegexpOptions{
|
||||||
|
strictSlash: r.strictSlash,
|
||||||
|
useEncodedPath: r.useEncodedPath,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, q := range r.regexp.queries {
|
||||||
|
if err = uniqueVars(rr.varsN, q.varsN); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if typ == regexpTypeHost {
|
||||||
|
if r.regexp.path != nil {
|
||||||
|
if err = uniqueVars(rr.varsN, r.regexp.path.varsN); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.regexp.host = rr
|
||||||
|
} else {
|
||||||
|
if r.regexp.host != nil {
|
||||||
|
if err = uniqueVars(rr.varsN, r.regexp.host.varsN); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if typ == regexpTypeQuery {
|
||||||
|
r.regexp.queries = append(r.regexp.queries, rr)
|
||||||
|
} else {
|
||||||
|
r.regexp.path = rr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.addMatcher(rr)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Headers --------------------------------------------------------------------
|
||||||
|
|
||||||
|
// headerMatcher matches the request against header values.
|
||||||
|
type headerMatcher map[string]string
|
||||||
|
|
||||||
|
func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool {
|
||||||
|
return matchMapWithString(m, r.Header, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Headers adds a matcher for request header values.
|
||||||
|
// It accepts a sequence of key/value pairs to be matched. For example:
|
||||||
|
//
|
||||||
|
// r := mux.NewRouter()
|
||||||
|
// r.Headers("Content-Type", "application/json",
|
||||||
|
// "X-Requested-With", "XMLHttpRequest")
|
||||||
|
//
|
||||||
|
// The above route will only match if both request header values match.
|
||||||
|
// If the value is an empty string, it will match any value if the key is set.
|
||||||
|
func (r *Route) Headers(pairs ...string) *Route {
|
||||||
|
if r.err == nil {
|
||||||
|
var headers map[string]string
|
||||||
|
headers, r.err = mapFromPairsToString(pairs...)
|
||||||
|
return r.addMatcher(headerMatcher(headers))
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// headerRegexMatcher matches the request against the route given a regex for the header
|
||||||
|
type headerRegexMatcher map[string]*regexp.Regexp
|
||||||
|
|
||||||
|
func (m headerRegexMatcher) Match(r *http.Request, match *RouteMatch) bool {
|
||||||
|
return matchMapWithRegex(m, r.Header, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeadersRegexp accepts a sequence of key/value pairs, where the value has regex
|
||||||
|
// support. For example:
|
||||||
|
//
|
||||||
|
// r := mux.NewRouter()
|
||||||
|
// r.HeadersRegexp("Content-Type", "application/(text|json)",
|
||||||
|
// "X-Requested-With", "XMLHttpRequest")
|
||||||
|
//
|
||||||
|
// The above route will only match if both the request header matches both regular expressions.
|
||||||
|
// If the value is an empty string, it will match any value if the key is set.
|
||||||
|
// Use the start and end of string anchors (^ and $) to match an exact value.
|
||||||
|
func (r *Route) HeadersRegexp(pairs ...string) *Route {
|
||||||
|
if r.err == nil {
|
||||||
|
var headers map[string]*regexp.Regexp
|
||||||
|
headers, r.err = mapFromPairsToRegex(pairs...)
|
||||||
|
return r.addMatcher(headerRegexMatcher(headers))
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Host -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Host adds a matcher for the URL host.
|
||||||
|
// It accepts a template with zero or more URL variables enclosed by {}.
|
||||||
|
// Variables can define an optional regexp pattern to be matched:
|
||||||
|
//
|
||||||
|
// - {name} matches anything until the next dot.
|
||||||
|
//
|
||||||
|
// - {name:pattern} matches the given regexp pattern.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
//
|
||||||
|
// r := mux.NewRouter()
|
||||||
|
// r.Host("www.example.com")
|
||||||
|
// r.Host("{subdomain}.domain.com")
|
||||||
|
// r.Host("{subdomain:[a-z]+}.domain.com")
|
||||||
|
//
|
||||||
|
// Variable names must be unique in a given route. They can be retrieved
|
||||||
|
// calling mux.Vars(request).
|
||||||
|
func (r *Route) Host(tpl string) *Route {
|
||||||
|
r.err = r.addRegexpMatcher(tpl, regexpTypeHost)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatcherFunc ----------------------------------------------------------------
|
||||||
|
|
||||||
|
// MatcherFunc is the function signature used by custom matchers.
|
||||||
|
type MatcherFunc func(*http.Request, *RouteMatch) bool
|
||||||
|
|
||||||
|
// Match returns the match for a given request.
|
||||||
|
func (m MatcherFunc) Match(r *http.Request, match *RouteMatch) bool {
|
||||||
|
return m(r, match)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatcherFunc adds a custom function to be used as request matcher.
|
||||||
|
func (r *Route) MatcherFunc(f MatcherFunc) *Route {
|
||||||
|
return r.addMatcher(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods --------------------------------------------------------------------
|
||||||
|
|
||||||
|
// methodMatcher matches the request against HTTP methods.
|
||||||
|
type methodMatcher []string
|
||||||
|
|
||||||
|
func (m methodMatcher) Match(r *http.Request, match *RouteMatch) bool {
|
||||||
|
return matchInArray(m, r.Method)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods adds a matcher for HTTP methods.
|
||||||
|
// It accepts a sequence of one or more methods to be matched, e.g.:
|
||||||
|
// "GET", "POST", "PUT".
|
||||||
|
func (r *Route) Methods(methods ...string) *Route {
|
||||||
|
for k, v := range methods {
|
||||||
|
methods[k] = strings.ToUpper(v)
|
||||||
|
}
|
||||||
|
return r.addMatcher(methodMatcher(methods))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Path adds a matcher for the URL path.
|
||||||
|
// It accepts a template with zero or more URL variables enclosed by {}. The
|
||||||
|
// template must start with a "/".
|
||||||
|
// Variables can define an optional regexp pattern to be matched:
|
||||||
|
//
|
||||||
|
// - {name} matches anything until the next slash.
|
||||||
|
//
|
||||||
|
// - {name:pattern} matches the given regexp pattern.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
//
|
||||||
|
// r := mux.NewRouter()
|
||||||
|
// r.Path("/products/").Handler(ProductsHandler)
|
||||||
|
// r.Path("/products/{key}").Handler(ProductsHandler)
|
||||||
|
// r.Path("/articles/{category}/{id:[0-9]+}").
|
||||||
|
// Handler(ArticleHandler)
|
||||||
|
//
|
||||||
|
// Variable names must be unique in a given route. They can be retrieved
|
||||||
|
// calling mux.Vars(request).
|
||||||
|
func (r *Route) Path(tpl string) *Route {
|
||||||
|
r.err = r.addRegexpMatcher(tpl, regexpTypePath)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// PathPrefix -----------------------------------------------------------------
|
||||||
|
|
||||||
|
// PathPrefix adds a matcher for the URL path prefix. This matches if the given
|
||||||
|
// template is a prefix of the full URL path. See Route.Path() for details on
|
||||||
|
// the tpl argument.
|
||||||
|
//
|
||||||
|
// Note that it does not treat slashes specially ("/foobar/" will be matched by
|
||||||
|
// the prefix "/foo") so you may want to use a trailing slash here.
|
||||||
|
//
|
||||||
|
// Also note that the setting of Router.StrictSlash() has no effect on routes
|
||||||
|
// with a PathPrefix matcher.
|
||||||
|
func (r *Route) PathPrefix(tpl string) *Route {
|
||||||
|
r.err = r.addRegexpMatcher(tpl, regexpTypePrefix)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Queries adds a matcher for URL query values.
|
||||||
|
// It accepts a sequence of key/value pairs. Values may define variables.
|
||||||
|
// For example:
|
||||||
|
//
|
||||||
|
// r := mux.NewRouter()
|
||||||
|
// r.Queries("foo", "bar", "id", "{id:[0-9]+}")
|
||||||
|
//
|
||||||
|
// The above route will only match if the URL contains the defined queries
|
||||||
|
// values, e.g.: ?foo=bar&id=42.
|
||||||
|
//
|
||||||
|
// If the value is an empty string, it will match any value if the key is set.
|
||||||
|
//
|
||||||
|
// Variables can define an optional regexp pattern to be matched:
|
||||||
|
//
|
||||||
|
// - {name} matches anything until the next slash.
|
||||||
|
//
|
||||||
|
// - {name:pattern} matches the given regexp pattern.
|
||||||
|
func (r *Route) Queries(pairs ...string) *Route {
|
||||||
|
length := len(pairs)
|
||||||
|
if length%2 != 0 {
|
||||||
|
r.err = fmt.Errorf(
|
||||||
|
"mux: number of parameters must be multiple of 2, got %v", pairs)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for i := 0; i < length; i += 2 {
|
||||||
|
if r.err = r.addRegexpMatcher(pairs[i]+"="+pairs[i+1], regexpTypeQuery); r.err != nil {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schemes --------------------------------------------------------------------
|
||||||
|
|
||||||
|
// schemeMatcher matches the request against URL schemes.
|
||||||
|
type schemeMatcher []string
|
||||||
|
|
||||||
|
func (m schemeMatcher) Match(r *http.Request, match *RouteMatch) bool {
|
||||||
|
scheme := r.URL.Scheme
|
||||||
|
// https://golang.org/pkg/net/http/#Request
|
||||||
|
// "For [most] server requests, fields other than Path and RawQuery will be
|
||||||
|
// empty."
|
||||||
|
// Since we're an http muxer, the scheme is either going to be http or https
|
||||||
|
// though, so we can just set it based on the tls termination state.
|
||||||
|
if scheme == "" {
|
||||||
|
if r.TLS == nil {
|
||||||
|
scheme = "http"
|
||||||
|
} else {
|
||||||
|
scheme = "https"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matchInArray(m, scheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schemes adds a matcher for URL schemes.
|
||||||
|
// It accepts a sequence of schemes to be matched, e.g.: "http", "https".
|
||||||
|
// If the request's URL has a scheme set, it will be matched against.
|
||||||
|
// Generally, the URL scheme will only be set if a previous handler set it,
|
||||||
|
// such as the ProxyHeaders handler from gorilla/handlers.
|
||||||
|
// If unset, the scheme will be determined based on the request's TLS
|
||||||
|
// termination state.
|
||||||
|
// The first argument to Schemes will be used when constructing a route URL.
|
||||||
|
func (r *Route) Schemes(schemes ...string) *Route {
|
||||||
|
for k, v := range schemes {
|
||||||
|
schemes[k] = strings.ToLower(v)
|
||||||
|
}
|
||||||
|
if len(schemes) > 0 {
|
||||||
|
r.buildScheme = schemes[0]
|
||||||
|
}
|
||||||
|
return r.addMatcher(schemeMatcher(schemes))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildVarsFunc --------------------------------------------------------------
|
||||||
|
|
||||||
|
// BuildVarsFunc is the function signature used by custom build variable
|
||||||
|
// functions (which can modify route variables before a route's URL is built).
|
||||||
|
type BuildVarsFunc func(map[string]string) map[string]string
|
||||||
|
|
||||||
|
// BuildVarsFunc adds a custom function to be used to modify build variables
|
||||||
|
// before a route's URL is built.
|
||||||
|
func (r *Route) BuildVarsFunc(f BuildVarsFunc) *Route {
|
||||||
|
if r.buildVarsFunc != nil {
|
||||||
|
// compose the old and new functions
|
||||||
|
old := r.buildVarsFunc
|
||||||
|
r.buildVarsFunc = func(m map[string]string) map[string]string {
|
||||||
|
return f(old(m))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
r.buildVarsFunc = f
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subrouter ------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Subrouter creates a subrouter for the route.
|
||||||
|
//
|
||||||
|
// It will test the inner routes only if the parent route matched. For example:
|
||||||
|
//
|
||||||
|
// r := mux.NewRouter()
|
||||||
|
// s := r.Host("www.example.com").Subrouter()
|
||||||
|
// s.HandleFunc("/products/", ProductsHandler)
|
||||||
|
// s.HandleFunc("/products/{key}", ProductHandler)
|
||||||
|
// s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
|
||||||
|
//
|
||||||
|
// Here, the routes registered in the subrouter won't be tested if the host
|
||||||
|
// doesn't match.
|
||||||
|
func (r *Route) Subrouter() *Router {
|
||||||
|
// initialize a subrouter with a copy of the parent route's configuration
|
||||||
|
router := &Router{routeConf: copyRouteConf(r.routeConf), namedRoutes: r.namedRoutes}
|
||||||
|
r.addMatcher(router)
|
||||||
|
return router
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// URL building
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// URL builds a URL for the route.
|
||||||
|
//
|
||||||
|
// It accepts a sequence of key/value pairs for the route variables. For
|
||||||
|
// example, given this route:
|
||||||
|
//
|
||||||
|
// r := mux.NewRouter()
|
||||||
|
// r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
|
||||||
|
// Name("article")
|
||||||
|
//
|
||||||
|
// ...a URL for it can be built using:
|
||||||
|
//
|
||||||
|
// url, err := r.Get("article").URL("category", "technology", "id", "42")
|
||||||
|
//
|
||||||
|
// ...which will return an url.URL with the following path:
|
||||||
|
//
|
||||||
|
// "/articles/technology/42"
|
||||||
|
//
|
||||||
|
// This also works for host variables:
|
||||||
|
//
|
||||||
|
// r := mux.NewRouter()
|
||||||
|
// r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
|
||||||
|
// Host("{subdomain}.domain.com").
|
||||||
|
// Name("article")
|
||||||
|
//
|
||||||
|
// // url.String() will be "http://news.domain.com/articles/technology/42"
|
||||||
|
// url, err := r.Get("article").URL("subdomain", "news",
|
||||||
|
// "category", "technology",
|
||||||
|
// "id", "42")
|
||||||
|
//
|
||||||
|
// The scheme of the resulting url will be the first argument that was passed to Schemes:
|
||||||
|
//
|
||||||
|
// // url.String() will be "https://example.com"
|
||||||
|
// r := mux.NewRouter()
|
||||||
|
// url, err := r.Host("example.com")
|
||||||
|
// .Schemes("https", "http").URL()
|
||||||
|
//
|
||||||
|
// All variables defined in the route are required, and their values must
|
||||||
|
// conform to the corresponding patterns.
|
||||||
|
func (r *Route) URL(pairs ...string) (*url.URL, error) {
|
||||||
|
if r.err != nil {
|
||||||
|
return nil, r.err
|
||||||
|
}
|
||||||
|
values, err := r.prepareVars(pairs...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var scheme, host, path string
|
||||||
|
queries := make([]string, 0, len(r.regexp.queries))
|
||||||
|
if r.regexp.host != nil {
|
||||||
|
if host, err = r.regexp.host.url(values); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
scheme = "http"
|
||||||
|
if r.buildScheme != "" {
|
||||||
|
scheme = r.buildScheme
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if r.regexp.path != nil {
|
||||||
|
if path, err = r.regexp.path.url(values); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, q := range r.regexp.queries {
|
||||||
|
var query string
|
||||||
|
if query, err = q.url(values); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
queries = append(queries, query)
|
||||||
|
}
|
||||||
|
return &url.URL{
|
||||||
|
Scheme: scheme,
|
||||||
|
Host: host,
|
||||||
|
Path: path,
|
||||||
|
RawQuery: strings.Join(queries, "&"),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// URLHost builds the host part of the URL for a route. See Route.URL().
|
||||||
|
//
|
||||||
|
// The route must have a host defined.
|
||||||
|
func (r *Route) URLHost(pairs ...string) (*url.URL, error) {
|
||||||
|
if r.err != nil {
|
||||||
|
return nil, r.err
|
||||||
|
}
|
||||||
|
if r.regexp.host == nil {
|
||||||
|
return nil, errors.New("mux: route doesn't have a host")
|
||||||
|
}
|
||||||
|
values, err := r.prepareVars(pairs...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
host, err := r.regexp.host.url(values)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
u := &url.URL{
|
||||||
|
Scheme: "http",
|
||||||
|
Host: host,
|
||||||
|
}
|
||||||
|
if r.buildScheme != "" {
|
||||||
|
u.Scheme = r.buildScheme
|
||||||
|
}
|
||||||
|
return u, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// URLPath builds the path part of the URL for a route. See Route.URL().
|
||||||
|
//
|
||||||
|
// The route must have a path defined.
|
||||||
|
func (r *Route) URLPath(pairs ...string) (*url.URL, error) {
|
||||||
|
if r.err != nil {
|
||||||
|
return nil, r.err
|
||||||
|
}
|
||||||
|
if r.regexp.path == nil {
|
||||||
|
return nil, errors.New("mux: route doesn't have a path")
|
||||||
|
}
|
||||||
|
values, err := r.prepareVars(pairs...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
path, err := r.regexp.path.url(values)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &url.URL{
|
||||||
|
Path: path,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPathTemplate returns the template used to build the
|
||||||
|
// route match.
|
||||||
|
// This is useful for building simple REST API documentation and for instrumentation
|
||||||
|
// against third-party services.
|
||||||
|
// An error will be returned if the route does not define a path.
|
||||||
|
func (r *Route) GetPathTemplate() (string, error) {
|
||||||
|
if r.err != nil {
|
||||||
|
return "", r.err
|
||||||
|
}
|
||||||
|
if r.regexp.path == nil {
|
||||||
|
return "", errors.New("mux: route doesn't have a path")
|
||||||
|
}
|
||||||
|
return r.regexp.path.template, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPathRegexp returns the expanded regular expression used to match route path.
|
||||||
|
// This is useful for building simple REST API documentation and for instrumentation
|
||||||
|
// against third-party services.
|
||||||
|
// An error will be returned if the route does not define a path.
|
||||||
|
func (r *Route) GetPathRegexp() (string, error) {
|
||||||
|
if r.err != nil {
|
||||||
|
return "", r.err
|
||||||
|
}
|
||||||
|
if r.regexp.path == nil {
|
||||||
|
return "", errors.New("mux: route does not have a path")
|
||||||
|
}
|
||||||
|
return r.regexp.path.regexp.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetQueriesRegexp returns the expanded regular expressions used to match the
|
||||||
|
// route queries.
|
||||||
|
// This is useful for building simple REST API documentation and for instrumentation
|
||||||
|
// against third-party services.
|
||||||
|
// An error will be returned if the route does not have queries.
|
||||||
|
func (r *Route) GetQueriesRegexp() ([]string, error) {
|
||||||
|
if r.err != nil {
|
||||||
|
return nil, r.err
|
||||||
|
}
|
||||||
|
if r.regexp.queries == nil {
|
||||||
|
return nil, errors.New("mux: route doesn't have queries")
|
||||||
|
}
|
||||||
|
queries := make([]string, 0, len(r.regexp.queries))
|
||||||
|
for _, query := range r.regexp.queries {
|
||||||
|
queries = append(queries, query.regexp.String())
|
||||||
|
}
|
||||||
|
return queries, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetQueriesTemplates returns the templates used to build the
|
||||||
|
// query matching.
|
||||||
|
// This is useful for building simple REST API documentation and for instrumentation
|
||||||
|
// against third-party services.
|
||||||
|
// An error will be returned if the route does not define queries.
|
||||||
|
func (r *Route) GetQueriesTemplates() ([]string, error) {
|
||||||
|
if r.err != nil {
|
||||||
|
return nil, r.err
|
||||||
|
}
|
||||||
|
if r.regexp.queries == nil {
|
||||||
|
return nil, errors.New("mux: route doesn't have queries")
|
||||||
|
}
|
||||||
|
queries := make([]string, 0, len(r.regexp.queries))
|
||||||
|
for _, query := range r.regexp.queries {
|
||||||
|
queries = append(queries, query.template)
|
||||||
|
}
|
||||||
|
return queries, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMethods returns the methods the route matches against
|
||||||
|
// This is useful for building simple REST API documentation and for instrumentation
|
||||||
|
// against third-party services.
|
||||||
|
// An error will be returned if route does not have methods.
|
||||||
|
func (r *Route) GetMethods() ([]string, error) {
|
||||||
|
if r.err != nil {
|
||||||
|
return nil, r.err
|
||||||
|
}
|
||||||
|
for _, m := range r.matchers {
|
||||||
|
if methods, ok := m.(methodMatcher); ok {
|
||||||
|
return []string(methods), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.New("mux: route doesn't have methods")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHostTemplate returns the template used to build the
|
||||||
|
// route match.
|
||||||
|
// This is useful for building simple REST API documentation and for instrumentation
|
||||||
|
// against third-party services.
|
||||||
|
// An error will be returned if the route does not define a host.
|
||||||
|
func (r *Route) GetHostTemplate() (string, error) {
|
||||||
|
if r.err != nil {
|
||||||
|
return "", r.err
|
||||||
|
}
|
||||||
|
if r.regexp.host == nil {
|
||||||
|
return "", errors.New("mux: route doesn't have a host")
|
||||||
|
}
|
||||||
|
return r.regexp.host.template, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareVars converts the route variable pairs into a map. If the route has a
|
||||||
|
// BuildVarsFunc, it is invoked.
|
||||||
|
func (r *Route) prepareVars(pairs ...string) (map[string]string, error) {
|
||||||
|
m, err := mapFromPairsToString(pairs...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return r.buildVars(m), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Route) buildVars(m map[string]string) map[string]string {
|
||||||
|
if r.buildVarsFunc != nil {
|
||||||
|
m = r.buildVarsFunc(m)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
19
vendor/github.com/gorilla/mux/test_helpers.go
generated
vendored
Normal file
19
vendor/github.com/gorilla/mux/test_helpers.go
generated
vendored
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package mux
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
// SetURLVars sets the URL variables for the given request, to be accessed via
|
||||||
|
// mux.Vars for testing route behaviour. Arguments are not modified, a shallow
|
||||||
|
// copy is returned.
|
||||||
|
//
|
||||||
|
// This API should only be used for testing purposes; it provides a way to
|
||||||
|
// inject variables into the request context. Alternatively, URL variables
|
||||||
|
// can be set by making a route that captures the required variables,
|
||||||
|
// starting a server and sending the request to that server.
|
||||||
|
func SetURLVars(r *http.Request, val map[string]string) *http.Request {
|
||||||
|
return requestWithVars(r, val)
|
||||||
|
}
|
9
vendor/github.com/hashicorp/hcl/.gitignore
generated
vendored
Normal file
9
vendor/github.com/hashicorp/hcl/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
y.output
|
||||||
|
|
||||||
|
# ignore intellij files
|
||||||
|
.idea
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
*.test
|
13
vendor/github.com/hashicorp/hcl/.travis.yml
generated
vendored
Normal file
13
vendor/github.com/hashicorp/hcl/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
sudo: false
|
||||||
|
|
||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.x
|
||||||
|
- tip
|
||||||
|
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
|
||||||
|
script: make test
|
354
vendor/github.com/hashicorp/hcl/LICENSE
generated
vendored
Normal file
354
vendor/github.com/hashicorp/hcl/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,354 @@
|
||||||
|
Mozilla Public License, version 2.0
|
||||||
|
|
||||||
|
1. Definitions
|
||||||
|
|
||||||
|
1.1. “Contributor”
|
||||||
|
|
||||||
|
means each individual or legal entity that creates, contributes to the
|
||||||
|
creation of, or owns Covered Software.
|
||||||
|
|
||||||
|
1.2. “Contributor Version”
|
||||||
|
|
||||||
|
means the combination of the Contributions of others (if any) used by a
|
||||||
|
Contributor and that particular Contributor’s Contribution.
|
||||||
|
|
||||||
|
1.3. “Contribution”
|
||||||
|
|
||||||
|
means Covered Software of a particular Contributor.
|
||||||
|
|
||||||
|
1.4. “Covered Software”
|
||||||
|
|
||||||
|
means Source Code Form to which the initial Contributor has attached the
|
||||||
|
notice in Exhibit A, the Executable Form of such Source Code Form, and
|
||||||
|
Modifications of such Source Code Form, in each case including portions
|
||||||
|
thereof.
|
||||||
|
|
||||||
|
1.5. “Incompatible With Secondary Licenses”
|
||||||
|
means
|
||||||
|
|
||||||
|
a. that the initial Contributor has attached the notice described in
|
||||||
|
Exhibit B to the Covered Software; or
|
||||||
|
|
||||||
|
b. that the Covered Software was made available under the terms of version
|
||||||
|
1.1 or earlier of the License, but not also under the terms of a
|
||||||
|
Secondary License.
|
||||||
|
|
||||||
|
1.6. “Executable Form”
|
||||||
|
|
||||||
|
means any form of the work other than Source Code Form.
|
||||||
|
|
||||||
|
1.7. “Larger Work”
|
||||||
|
|
||||||
|
means a work that combines Covered Software with other material, in a separate
|
||||||
|
file or files, that is not Covered Software.
|
||||||
|
|
||||||
|
1.8. “License”
|
||||||
|
|
||||||
|
means this document.
|
||||||
|
|
||||||
|
1.9. “Licensable”
|
||||||
|
|
||||||
|
means having the right to grant, to the maximum extent possible, whether at the
|
||||||
|
time of the initial grant or subsequently, any and all of the rights conveyed by
|
||||||
|
this License.
|
||||||
|
|
||||||
|
1.10. “Modifications”
|
||||||
|
|
||||||
|
means any of the following:
|
||||||
|
|
||||||
|
a. any file in Source Code Form that results from an addition to, deletion
|
||||||
|
from, or modification of the contents of Covered Software; or
|
||||||
|
|
||||||
|
b. any new file in Source Code Form that contains any Covered Software.
|
||||||
|
|
||||||
|
1.11. “Patent Claims” of a Contributor
|
||||||
|
|
||||||
|
means any patent claim(s), including without limitation, method, process,
|
||||||
|
and apparatus claims, in any patent Licensable by such Contributor that
|
||||||
|
would be infringed, but for the grant of the License, by the making,
|
||||||
|
using, selling, offering for sale, having made, import, or transfer of
|
||||||
|
either its Contributions or its Contributor Version.
|
||||||
|
|
||||||
|
1.12. “Secondary License”
|
||||||
|
|
||||||
|
means either the GNU General Public License, Version 2.0, the GNU Lesser
|
||||||
|
General Public License, Version 2.1, the GNU Affero General Public
|
||||||
|
License, Version 3.0, or any later versions of those licenses.
|
||||||
|
|
||||||
|
1.13. “Source Code Form”
|
||||||
|
|
||||||
|
means the form of the work preferred for making modifications.
|
||||||
|
|
||||||
|
1.14. “You” (or “Your”)
|
||||||
|
|
||||||
|
means an individual or a legal entity exercising rights under this
|
||||||
|
License. For legal entities, “You” includes any entity that controls, is
|
||||||
|
controlled by, or is under common control with You. For purposes of this
|
||||||
|
definition, “control” means (a) the power, direct or indirect, to cause
|
||||||
|
the direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (b) ownership of more than fifty percent (50%) of the
|
||||||
|
outstanding shares or beneficial ownership of such entity.
|
||||||
|
|
||||||
|
|
||||||
|
2. License Grants and Conditions
|
||||||
|
|
||||||
|
2.1. Grants
|
||||||
|
|
||||||
|
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||||
|
non-exclusive license:
|
||||||
|
|
||||||
|
a. under intellectual property rights (other than patent or trademark)
|
||||||
|
Licensable by such Contributor to use, reproduce, make available,
|
||||||
|
modify, display, perform, distribute, and otherwise exploit its
|
||||||
|
Contributions, either on an unmodified basis, with Modifications, or as
|
||||||
|
part of a Larger Work; and
|
||||||
|
|
||||||
|
b. under Patent Claims of such Contributor to make, use, sell, offer for
|
||||||
|
sale, have made, import, and otherwise transfer either its Contributions
|
||||||
|
or its Contributor Version.
|
||||||
|
|
||||||
|
2.2. Effective Date
|
||||||
|
|
||||||
|
The licenses granted in Section 2.1 with respect to any Contribution become
|
||||||
|
effective for each Contribution on the date the Contributor first distributes
|
||||||
|
such Contribution.
|
||||||
|
|
||||||
|
2.3. Limitations on Grant Scope
|
||||||
|
|
||||||
|
The licenses granted in this Section 2 are the only rights granted under this
|
||||||
|
License. No additional rights or licenses will be implied from the distribution
|
||||||
|
or licensing of Covered Software under this License. Notwithstanding Section
|
||||||
|
2.1(b) above, no patent license is granted by a Contributor:
|
||||||
|
|
||||||
|
a. for any code that a Contributor has removed from Covered Software; or
|
||||||
|
|
||||||
|
b. for infringements caused by: (i) Your and any other third party’s
|
||||||
|
modifications of Covered Software, or (ii) the combination of its
|
||||||
|
Contributions with other software (except as part of its Contributor
|
||||||
|
Version); or
|
||||||
|
|
||||||
|
c. under Patent Claims infringed by Covered Software in the absence of its
|
||||||
|
Contributions.
|
||||||
|
|
||||||
|
This License does not grant any rights in the trademarks, service marks, or
|
||||||
|
logos of any Contributor (except as may be necessary to comply with the
|
||||||
|
notice requirements in Section 3.4).
|
||||||
|
|
||||||
|
2.4. Subsequent Licenses
|
||||||
|
|
||||||
|
No Contributor makes additional grants as a result of Your choice to
|
||||||
|
distribute the Covered Software under a subsequent version of this License
|
||||||
|
(see Section 10.2) or under the terms of a Secondary License (if permitted
|
||||||
|
under the terms of Section 3.3).
|
||||||
|
|
||||||
|
2.5. Representation
|
||||||
|
|
||||||
|
Each Contributor represents that the Contributor believes its Contributions
|
||||||
|
are its original creation(s) or it has sufficient rights to grant the
|
||||||
|
rights to its Contributions conveyed by this License.
|
||||||
|
|
||||||
|
2.6. Fair Use
|
||||||
|
|
||||||
|
This License is not intended to limit any rights You have under applicable
|
||||||
|
copyright doctrines of fair use, fair dealing, or other equivalents.
|
||||||
|
|
||||||
|
2.7. Conditions
|
||||||
|
|
||||||
|
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
|
||||||
|
Section 2.1.
|
||||||
|
|
||||||
|
|
||||||
|
3. Responsibilities
|
||||||
|
|
||||||
|
3.1. Distribution of Source Form
|
||||||
|
|
||||||
|
All distribution of Covered Software in Source Code Form, including any
|
||||||
|
Modifications that You create or to which You contribute, must be under the
|
||||||
|
terms of this License. You must inform recipients that the Source Code Form
|
||||||
|
of the Covered Software is governed by the terms of this License, and how
|
||||||
|
they can obtain a copy of this License. You may not attempt to alter or
|
||||||
|
restrict the recipients’ rights in the Source Code Form.
|
||||||
|
|
||||||
|
3.2. Distribution of Executable Form
|
||||||
|
|
||||||
|
If You distribute Covered Software in Executable Form then:
|
||||||
|
|
||||||
|
a. such Covered Software must also be made available in Source Code Form,
|
||||||
|
as described in Section 3.1, and You must inform recipients of the
|
||||||
|
Executable Form how they can obtain a copy of such Source Code Form by
|
||||||
|
reasonable means in a timely manner, at a charge no more than the cost
|
||||||
|
of distribution to the recipient; and
|
||||||
|
|
||||||
|
b. You may distribute such Executable Form under the terms of this License,
|
||||||
|
or sublicense it under different terms, provided that the license for
|
||||||
|
the Executable Form does not attempt to limit or alter the recipients’
|
||||||
|
rights in the Source Code Form under this License.
|
||||||
|
|
||||||
|
3.3. Distribution of a Larger Work
|
||||||
|
|
||||||
|
You may create and distribute a Larger Work under terms of Your choice,
|
||||||
|
provided that You also comply with the requirements of this License for the
|
||||||
|
Covered Software. If the Larger Work is a combination of Covered Software
|
||||||
|
with a work governed by one or more Secondary Licenses, and the Covered
|
||||||
|
Software is not Incompatible With Secondary Licenses, this License permits
|
||||||
|
You to additionally distribute such Covered Software under the terms of
|
||||||
|
such Secondary License(s), so that the recipient of the Larger Work may, at
|
||||||
|
their option, further distribute the Covered Software under the terms of
|
||||||
|
either this License or such Secondary License(s).
|
||||||
|
|
||||||
|
3.4. Notices
|
||||||
|
|
||||||
|
You may not remove or alter the substance of any license notices (including
|
||||||
|
copyright notices, patent notices, disclaimers of warranty, or limitations
|
||||||
|
of liability) contained within the Source Code Form of the Covered
|
||||||
|
Software, except that You may alter any license notices to the extent
|
||||||
|
required to remedy known factual inaccuracies.
|
||||||
|
|
||||||
|
3.5. Application of Additional Terms
|
||||||
|
|
||||||
|
You may choose to offer, and to charge a fee for, warranty, support,
|
||||||
|
indemnity or liability obligations to one or more recipients of Covered
|
||||||
|
Software. However, You may do so only on Your own behalf, and not on behalf
|
||||||
|
of any Contributor. You must make it absolutely clear that any such
|
||||||
|
warranty, support, indemnity, or liability obligation is offered by You
|
||||||
|
alone, and You hereby agree to indemnify every Contributor for any
|
||||||
|
liability incurred by such Contributor as a result of warranty, support,
|
||||||
|
indemnity or liability terms You offer. You may include additional
|
||||||
|
disclaimers of warranty and limitations of liability specific to any
|
||||||
|
jurisdiction.
|
||||||
|
|
||||||
|
4. Inability to Comply Due to Statute or Regulation
|
||||||
|
|
||||||
|
If it is impossible for You to comply with any of the terms of this License
|
||||||
|
with respect to some or all of the Covered Software due to statute, judicial
|
||||||
|
order, or regulation then You must: (a) comply with the terms of this License
|
||||||
|
to the maximum extent possible; and (b) describe the limitations and the code
|
||||||
|
they affect. Such description must be placed in a text file included with all
|
||||||
|
distributions of the Covered Software under this License. Except to the
|
||||||
|
extent prohibited by statute or regulation, such description must be
|
||||||
|
sufficiently detailed for a recipient of ordinary skill to be able to
|
||||||
|
understand it.
|
||||||
|
|
||||||
|
5. Termination
|
||||||
|
|
||||||
|
5.1. The rights granted under this License will terminate automatically if You
|
||||||
|
fail to comply with any of its terms. However, if You become compliant,
|
||||||
|
then the rights granted under this License from a particular Contributor
|
||||||
|
are reinstated (a) provisionally, unless and until such Contributor
|
||||||
|
explicitly and finally terminates Your grants, and (b) on an ongoing basis,
|
||||||
|
if such Contributor fails to notify You of the non-compliance by some
|
||||||
|
reasonable means prior to 60 days after You have come back into compliance.
|
||||||
|
Moreover, Your grants from a particular Contributor are reinstated on an
|
||||||
|
ongoing basis if such Contributor notifies You of the non-compliance by
|
||||||
|
some reasonable means, this is the first time You have received notice of
|
||||||
|
non-compliance with this License from such Contributor, and You become
|
||||||
|
compliant prior to 30 days after Your receipt of the notice.
|
||||||
|
|
||||||
|
5.2. If You initiate litigation against any entity by asserting a patent
|
||||||
|
infringement claim (excluding declaratory judgment actions, counter-claims,
|
||||||
|
and cross-claims) alleging that a Contributor Version directly or
|
||||||
|
indirectly infringes any patent, then the rights granted to You by any and
|
||||||
|
all Contributors for the Covered Software under Section 2.1 of this License
|
||||||
|
shall terminate.
|
||||||
|
|
||||||
|
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
|
||||||
|
license agreements (excluding distributors and resellers) which have been
|
||||||
|
validly granted by You or Your distributors under this License prior to
|
||||||
|
termination shall survive termination.
|
||||||
|
|
||||||
|
6. Disclaimer of Warranty
|
||||||
|
|
||||||
|
Covered Software is provided under this License on an “as is” basis, without
|
||||||
|
warranty of any kind, either expressed, implied, or statutory, including,
|
||||||
|
without limitation, warranties that the Covered Software is free of defects,
|
||||||
|
merchantable, fit for a particular purpose or non-infringing. The entire
|
||||||
|
risk as to the quality and performance of the Covered Software is with You.
|
||||||
|
Should any Covered Software prove defective in any respect, You (not any
|
||||||
|
Contributor) assume the cost of any necessary servicing, repair, or
|
||||||
|
correction. This disclaimer of warranty constitutes an essential part of this
|
||||||
|
License. No use of any Covered Software is authorized under this License
|
||||||
|
except under this disclaimer.
|
||||||
|
|
||||||
|
7. Limitation of Liability
|
||||||
|
|
||||||
|
Under no circumstances and under no legal theory, whether tort (including
|
||||||
|
negligence), contract, or otherwise, shall any Contributor, or anyone who
|
||||||
|
distributes Covered Software as permitted above, be liable to You for any
|
||||||
|
direct, indirect, special, incidental, or consequential damages of any
|
||||||
|
character including, without limitation, damages for lost profits, loss of
|
||||||
|
goodwill, work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses, even if such party shall have been
|
||||||
|
informed of the possibility of such damages. This limitation of liability
|
||||||
|
shall not apply to liability for death or personal injury resulting from such
|
||||||
|
party’s negligence to the extent applicable law prohibits such limitation.
|
||||||
|
Some jurisdictions do not allow the exclusion or limitation of incidental or
|
||||||
|
consequential damages, so this exclusion and limitation may not apply to You.
|
||||||
|
|
||||||
|
8. Litigation
|
||||||
|
|
||||||
|
Any litigation relating to this License may be brought only in the courts of
|
||||||
|
a jurisdiction where the defendant maintains its principal place of business
|
||||||
|
and such litigation shall be governed by laws of that jurisdiction, without
|
||||||
|
reference to its conflict-of-law provisions. Nothing in this Section shall
|
||||||
|
prevent a party’s ability to bring cross-claims or counter-claims.
|
||||||
|
|
||||||
|
9. Miscellaneous
|
||||||
|
|
||||||
|
This License represents the complete agreement concerning the subject matter
|
||||||
|
hereof. If any provision of this License is held to be unenforceable, such
|
||||||
|
provision shall be reformed only to the extent necessary to make it
|
||||||
|
enforceable. Any law or regulation which provides that the language of a
|
||||||
|
contract shall be construed against the drafter shall not be used to construe
|
||||||
|
this License against a Contributor.
|
||||||
|
|
||||||
|
|
||||||
|
10. Versions of the License
|
||||||
|
|
||||||
|
10.1. New Versions
|
||||||
|
|
||||||
|
Mozilla Foundation is the license steward. Except as provided in Section
|
||||||
|
10.3, no one other than the license steward has the right to modify or
|
||||||
|
publish new versions of this License. Each version will be given a
|
||||||
|
distinguishing version number.
|
||||||
|
|
||||||
|
10.2. Effect of New Versions
|
||||||
|
|
||||||
|
You may distribute the Covered Software under the terms of the version of
|
||||||
|
the License under which You originally received the Covered Software, or
|
||||||
|
under the terms of any subsequent version published by the license
|
||||||
|
steward.
|
||||||
|
|
||||||
|
10.3. Modified Versions
|
||||||
|
|
||||||
|
If you create software not governed by this License, and you want to
|
||||||
|
create a new license for such software, you may create and use a modified
|
||||||
|
version of this License if you rename the license and remove any
|
||||||
|
references to the name of the license steward (except to note that such
|
||||||
|
modified license differs from this License).
|
||||||
|
|
||||||
|
10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
|
||||||
|
If You choose to distribute Source Code Form that is Incompatible With
|
||||||
|
Secondary Licenses under the terms of this version of the License, the
|
||||||
|
notice described in Exhibit B of this License must be attached.
|
||||||
|
|
||||||
|
Exhibit A - Source Code Form License Notice
|
||||||
|
|
||||||
|
This Source Code Form is subject to the
|
||||||
|
terms of the Mozilla Public License, v.
|
||||||
|
2.0. If a copy of the MPL was not
|
||||||
|
distributed with this file, You can
|
||||||
|
obtain one at
|
||||||
|
http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
If it is not possible or desirable to put the notice in a particular file, then
|
||||||
|
You may include the notice in a location (such as a LICENSE file in a relevant
|
||||||
|
directory) where a recipient would be likely to look for such a notice.
|
||||||
|
|
||||||
|
You may add additional accurate notices of copyright ownership.
|
||||||
|
|
||||||
|
Exhibit B - “Incompatible With Secondary Licenses” Notice
|
||||||
|
|
||||||
|
This Source Code Form is “Incompatible
|
||||||
|
With Secondary Licenses”, as defined by
|
||||||
|
the Mozilla Public License, v. 2.0.
|
||||||
|
|
18
vendor/github.com/hashicorp/hcl/Makefile
generated
vendored
Normal file
18
vendor/github.com/hashicorp/hcl/Makefile
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
TEST?=./...
|
||||||
|
|
||||||
|
default: test
|
||||||
|
|
||||||
|
fmt: generate
|
||||||
|
go fmt ./...
|
||||||
|
|
||||||
|
test: generate
|
||||||
|
go get -t ./...
|
||||||
|
go test $(TEST) $(TESTARGS)
|
||||||
|
|
||||||
|
generate:
|
||||||
|
go generate ./...
|
||||||
|
|
||||||
|
updatedeps:
|
||||||
|
go get -u golang.org/x/tools/cmd/stringer
|
||||||
|
|
||||||
|
.PHONY: default generate test updatedeps
|
125
vendor/github.com/hashicorp/hcl/README.md
generated
vendored
Normal file
125
vendor/github.com/hashicorp/hcl/README.md
generated
vendored
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
# HCL
|
||||||
|
|
||||||
|
[![GoDoc](https://godoc.org/github.com/hashicorp/hcl?status.png)](https://godoc.org/github.com/hashicorp/hcl) [![Build Status](https://travis-ci.org/hashicorp/hcl.svg?branch=master)](https://travis-ci.org/hashicorp/hcl)
|
||||||
|
|
||||||
|
HCL (HashiCorp Configuration Language) is a configuration language built
|
||||||
|
by HashiCorp. The goal of HCL is to build a structured configuration language
|
||||||
|
that is both human and machine friendly for use with command-line tools, but
|
||||||
|
specifically targeted towards DevOps tools, servers, etc.
|
||||||
|
|
||||||
|
HCL is also fully JSON compatible. That is, JSON can be used as completely
|
||||||
|
valid input to a system expecting HCL. This helps makes systems
|
||||||
|
interoperable with other systems.
|
||||||
|
|
||||||
|
HCL is heavily inspired by
|
||||||
|
[libucl](https://github.com/vstakhov/libucl),
|
||||||
|
nginx configuration, and others similar.
|
||||||
|
|
||||||
|
## Why?
|
||||||
|
|
||||||
|
A common question when viewing HCL is to ask the question: why not
|
||||||
|
JSON, YAML, etc.?
|
||||||
|
|
||||||
|
Prior to HCL, the tools we built at [HashiCorp](http://www.hashicorp.com)
|
||||||
|
used a variety of configuration languages from full programming languages
|
||||||
|
such as Ruby to complete data structure languages such as JSON. What we
|
||||||
|
learned is that some people wanted human-friendly configuration languages
|
||||||
|
and some people wanted machine-friendly languages.
|
||||||
|
|
||||||
|
JSON fits a nice balance in this, but is fairly verbose and most
|
||||||
|
importantly doesn't support comments. With YAML, we found that beginners
|
||||||
|
had a really hard time determining what the actual structure was, and
|
||||||
|
ended up guessing more often than not whether to use a hyphen, colon, etc.
|
||||||
|
in order to represent some configuration key.
|
||||||
|
|
||||||
|
Full programming languages such as Ruby enable complex behavior
|
||||||
|
a configuration language shouldn't usually allow, and also forces
|
||||||
|
people to learn some set of Ruby.
|
||||||
|
|
||||||
|
Because of this, we decided to create our own configuration language
|
||||||
|
that is JSON-compatible. Our configuration language (HCL) is designed
|
||||||
|
to be written and modified by humans. The API for HCL allows JSON
|
||||||
|
as an input so that it is also machine-friendly (machines can generate
|
||||||
|
JSON instead of trying to generate HCL).
|
||||||
|
|
||||||
|
Our goal with HCL is not to alienate other configuration languages.
|
||||||
|
It is instead to provide HCL as a specialized language for our tools,
|
||||||
|
and JSON as the interoperability layer.
|
||||||
|
|
||||||
|
## Syntax
|
||||||
|
|
||||||
|
For a complete grammar, please see the parser itself. A high-level overview
|
||||||
|
of the syntax and grammar is listed here.
|
||||||
|
|
||||||
|
* Single line comments start with `#` or `//`
|
||||||
|
|
||||||
|
* Multi-line comments are wrapped in `/*` and `*/`. Nested block comments
|
||||||
|
are not allowed. A multi-line comment (also known as a block comment)
|
||||||
|
terminates at the first `*/` found.
|
||||||
|
|
||||||
|
* Values are assigned with the syntax `key = value` (whitespace doesn't
|
||||||
|
matter). The value can be any primitive: a string, number, boolean,
|
||||||
|
object, or list.
|
||||||
|
|
||||||
|
* Strings are double-quoted and can contain any UTF-8 characters.
|
||||||
|
Example: `"Hello, World"`
|
||||||
|
|
||||||
|
* Multi-line strings start with `<<EOF` at the end of a line, and end
|
||||||
|
with `EOF` on its own line ([here documents](https://en.wikipedia.org/wiki/Here_document)).
|
||||||
|
Any text may be used in place of `EOF`. Example:
|
||||||
|
```
|
||||||
|
<<FOO
|
||||||
|
hello
|
||||||
|
world
|
||||||
|
FOO
|
||||||
|
```
|
||||||
|
|
||||||
|
* Numbers are assumed to be base 10. If you prefix a number with 0x,
|
||||||
|
it is treated as a hexadecimal. If it is prefixed with 0, it is
|
||||||
|
treated as an octal. Numbers can be in scientific notation: "1e10".
|
||||||
|
|
||||||
|
* Boolean values: `true`, `false`
|
||||||
|
|
||||||
|
* Arrays can be made by wrapping it in `[]`. Example:
|
||||||
|
`["foo", "bar", 42]`. Arrays can contain primitives,
|
||||||
|
other arrays, and objects. As an alternative, lists
|
||||||
|
of objects can be created with repeated blocks, using
|
||||||
|
this structure:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
service {
|
||||||
|
key = "value"
|
||||||
|
}
|
||||||
|
|
||||||
|
service {
|
||||||
|
key = "value"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Objects and nested objects are created using the structure shown below:
|
||||||
|
|
||||||
|
```
|
||||||
|
variable "ami" {
|
||||||
|
description = "the AMI to use"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
This would be equivalent to the following json:
|
||||||
|
``` json
|
||||||
|
{
|
||||||
|
"variable": {
|
||||||
|
"ami": {
|
||||||
|
"description": "the AMI to use"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Thanks
|
||||||
|
|
||||||
|
Thanks to:
|
||||||
|
|
||||||
|
* [@vstakhov](https://github.com/vstakhov) - The original libucl parser
|
||||||
|
and syntax that HCL was based off of.
|
||||||
|
|
||||||
|
* [@fatih](https://github.com/fatih) - The rewritten HCL parser
|
||||||
|
in pure Go (no goyacc) and support for a printer.
|
19
vendor/github.com/hashicorp/hcl/appveyor.yml
generated
vendored
Normal file
19
vendor/github.com/hashicorp/hcl/appveyor.yml
generated
vendored
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
version: "build-{branch}-{build}"
|
||||||
|
image: Visual Studio 2015
|
||||||
|
clone_folder: c:\gopath\src\github.com\hashicorp\hcl
|
||||||
|
environment:
|
||||||
|
GOPATH: c:\gopath
|
||||||
|
init:
|
||||||
|
- git config --global core.autocrlf false
|
||||||
|
install:
|
||||||
|
- cmd: >-
|
||||||
|
echo %Path%
|
||||||
|
|
||||||
|
go version
|
||||||
|
|
||||||
|
go env
|
||||||
|
|
||||||
|
go get -t ./...
|
||||||
|
|
||||||
|
build_script:
|
||||||
|
- cmd: go test -v ./...
|
729
vendor/github.com/hashicorp/hcl/decoder.go
generated
vendored
Normal file
729
vendor/github.com/hashicorp/hcl/decoder.go
generated
vendored
Normal file
|
@ -0,0 +1,729 @@
|
||||||
|
package hcl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/hcl/hcl/ast"
|
||||||
|
"github.com/hashicorp/hcl/hcl/parser"
|
||||||
|
"github.com/hashicorp/hcl/hcl/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is the tag to use with structures to have settings for HCL
|
||||||
|
const tagName = "hcl"
|
||||||
|
|
||||||
|
var (
|
||||||
|
// nodeType holds a reference to the type of ast.Node
|
||||||
|
nodeType reflect.Type = findNodeType()
|
||||||
|
)
|
||||||
|
|
||||||
|
// Unmarshal accepts a byte slice as input and writes the
|
||||||
|
// data to the value pointed to by v.
|
||||||
|
func Unmarshal(bs []byte, v interface{}) error {
|
||||||
|
root, err := parse(bs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return DecodeObject(v, root)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode reads the given input and decodes it into the structure
|
||||||
|
// given by `out`.
|
||||||
|
func Decode(out interface{}, in string) error {
|
||||||
|
obj, err := Parse(in)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return DecodeObject(out, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeObject is a lower-level version of Decode. It decodes a
|
||||||
|
// raw Object into the given output.
|
||||||
|
func DecodeObject(out interface{}, n ast.Node) error {
|
||||||
|
val := reflect.ValueOf(out)
|
||||||
|
if val.Kind() != reflect.Ptr {
|
||||||
|
return errors.New("result must be a pointer")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have the file, we really decode the root node
|
||||||
|
if f, ok := n.(*ast.File); ok {
|
||||||
|
n = f.Node
|
||||||
|
}
|
||||||
|
|
||||||
|
var d decoder
|
||||||
|
return d.decode("root", n, val.Elem())
|
||||||
|
}
|
||||||
|
|
||||||
|
type decoder struct {
|
||||||
|
stack []reflect.Kind
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) decode(name string, node ast.Node, result reflect.Value) error {
|
||||||
|
k := result
|
||||||
|
|
||||||
|
// If we have an interface with a valid value, we use that
|
||||||
|
// for the check.
|
||||||
|
if result.Kind() == reflect.Interface {
|
||||||
|
elem := result.Elem()
|
||||||
|
if elem.IsValid() {
|
||||||
|
k = elem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push current onto stack unless it is an interface.
|
||||||
|
if k.Kind() != reflect.Interface {
|
||||||
|
d.stack = append(d.stack, k.Kind())
|
||||||
|
|
||||||
|
// Schedule a pop
|
||||||
|
defer func() {
|
||||||
|
d.stack = d.stack[:len(d.stack)-1]
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch k.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
return d.decodeBool(name, node, result)
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return d.decodeFloat(name, node, result)
|
||||||
|
case reflect.Int, reflect.Int32, reflect.Int64:
|
||||||
|
return d.decodeInt(name, node, result)
|
||||||
|
case reflect.Interface:
|
||||||
|
// When we see an interface, we make our own thing
|
||||||
|
return d.decodeInterface(name, node, result)
|
||||||
|
case reflect.Map:
|
||||||
|
return d.decodeMap(name, node, result)
|
||||||
|
case reflect.Ptr:
|
||||||
|
return d.decodePtr(name, node, result)
|
||||||
|
case reflect.Slice:
|
||||||
|
return d.decodeSlice(name, node, result)
|
||||||
|
case reflect.String:
|
||||||
|
return d.decodeString(name, node, result)
|
||||||
|
case reflect.Struct:
|
||||||
|
return d.decodeStruct(name, node, result)
|
||||||
|
default:
|
||||||
|
return &parser.PosError{
|
||||||
|
Pos: node.Pos(),
|
||||||
|
Err: fmt.Errorf("%s: unknown kind to decode into: %s", name, k.Kind()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) decodeBool(name string, node ast.Node, result reflect.Value) error {
|
||||||
|
switch n := node.(type) {
|
||||||
|
case *ast.LiteralType:
|
||||||
|
if n.Token.Type == token.BOOL {
|
||||||
|
v, err := strconv.ParseBool(n.Token.Text)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Set(reflect.ValueOf(v))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &parser.PosError{
|
||||||
|
Pos: node.Pos(),
|
||||||
|
Err: fmt.Errorf("%s: unknown type %T", name, node),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) decodeFloat(name string, node ast.Node, result reflect.Value) error {
|
||||||
|
switch n := node.(type) {
|
||||||
|
case *ast.LiteralType:
|
||||||
|
if n.Token.Type == token.FLOAT || n.Token.Type == token.NUMBER {
|
||||||
|
v, err := strconv.ParseFloat(n.Token.Text, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Set(reflect.ValueOf(v).Convert(result.Type()))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &parser.PosError{
|
||||||
|
Pos: node.Pos(),
|
||||||
|
Err: fmt.Errorf("%s: unknown type %T", name, node),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) decodeInt(name string, node ast.Node, result reflect.Value) error {
|
||||||
|
switch n := node.(type) {
|
||||||
|
case *ast.LiteralType:
|
||||||
|
switch n.Token.Type {
|
||||||
|
case token.NUMBER:
|
||||||
|
v, err := strconv.ParseInt(n.Token.Text, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Kind() == reflect.Interface {
|
||||||
|
result.Set(reflect.ValueOf(int(v)))
|
||||||
|
} else {
|
||||||
|
result.SetInt(v)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case token.STRING:
|
||||||
|
v, err := strconv.ParseInt(n.Token.Value().(string), 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Kind() == reflect.Interface {
|
||||||
|
result.Set(reflect.ValueOf(int(v)))
|
||||||
|
} else {
|
||||||
|
result.SetInt(v)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &parser.PosError{
|
||||||
|
Pos: node.Pos(),
|
||||||
|
Err: fmt.Errorf("%s: unknown type %T", name, node),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) decodeInterface(name string, node ast.Node, result reflect.Value) error {
|
||||||
|
// When we see an ast.Node, we retain the value to enable deferred decoding.
|
||||||
|
// Very useful in situations where we want to preserve ast.Node information
|
||||||
|
// like Pos
|
||||||
|
if result.Type() == nodeType && result.CanSet() {
|
||||||
|
result.Set(reflect.ValueOf(node))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var set reflect.Value
|
||||||
|
redecode := true
|
||||||
|
|
||||||
|
// For testing types, ObjectType should just be treated as a list. We
|
||||||
|
// set this to a temporary var because we want to pass in the real node.
|
||||||
|
testNode := node
|
||||||
|
if ot, ok := node.(*ast.ObjectType); ok {
|
||||||
|
testNode = ot.List
|
||||||
|
}
|
||||||
|
|
||||||
|
switch n := testNode.(type) {
|
||||||
|
case *ast.ObjectList:
|
||||||
|
// If we're at the root or we're directly within a slice, then we
|
||||||
|
// decode objects into map[string]interface{}, otherwise we decode
|
||||||
|
// them into lists.
|
||||||
|
if len(d.stack) == 0 || d.stack[len(d.stack)-1] == reflect.Slice {
|
||||||
|
var temp map[string]interface{}
|
||||||
|
tempVal := reflect.ValueOf(temp)
|
||||||
|
result := reflect.MakeMap(
|
||||||
|
reflect.MapOf(
|
||||||
|
reflect.TypeOf(""),
|
||||||
|
tempVal.Type().Elem()))
|
||||||
|
|
||||||
|
set = result
|
||||||
|
} else {
|
||||||
|
var temp []map[string]interface{}
|
||||||
|
tempVal := reflect.ValueOf(temp)
|
||||||
|
result := reflect.MakeSlice(
|
||||||
|
reflect.SliceOf(tempVal.Type().Elem()), 0, len(n.Items))
|
||||||
|
set = result
|
||||||
|
}
|
||||||
|
case *ast.ObjectType:
|
||||||
|
// If we're at the root or we're directly within a slice, then we
|
||||||
|
// decode objects into map[string]interface{}, otherwise we decode
|
||||||
|
// them into lists.
|
||||||
|
if len(d.stack) == 0 || d.stack[len(d.stack)-1] == reflect.Slice {
|
||||||
|
var temp map[string]interface{}
|
||||||
|
tempVal := reflect.ValueOf(temp)
|
||||||
|
result := reflect.MakeMap(
|
||||||
|
reflect.MapOf(
|
||||||
|
reflect.TypeOf(""),
|
||||||
|
tempVal.Type().Elem()))
|
||||||
|
|
||||||
|
set = result
|
||||||
|
} else {
|
||||||
|
var temp []map[string]interface{}
|
||||||
|
tempVal := reflect.ValueOf(temp)
|
||||||
|
result := reflect.MakeSlice(
|
||||||
|
reflect.SliceOf(tempVal.Type().Elem()), 0, 1)
|
||||||
|
set = result
|
||||||
|
}
|
||||||
|
case *ast.ListType:
|
||||||
|
var temp []interface{}
|
||||||
|
tempVal := reflect.ValueOf(temp)
|
||||||
|
result := reflect.MakeSlice(
|
||||||
|
reflect.SliceOf(tempVal.Type().Elem()), 0, 0)
|
||||||
|
set = result
|
||||||
|
case *ast.LiteralType:
|
||||||
|
switch n.Token.Type {
|
||||||
|
case token.BOOL:
|
||||||
|
var result bool
|
||||||
|
set = reflect.Indirect(reflect.New(reflect.TypeOf(result)))
|
||||||
|
case token.FLOAT:
|
||||||
|
var result float64
|
||||||
|
set = reflect.Indirect(reflect.New(reflect.TypeOf(result)))
|
||||||
|
case token.NUMBER:
|
||||||
|
var result int
|
||||||
|
set = reflect.Indirect(reflect.New(reflect.TypeOf(result)))
|
||||||
|
case token.STRING, token.HEREDOC:
|
||||||
|
set = reflect.Indirect(reflect.New(reflect.TypeOf("")))
|
||||||
|
default:
|
||||||
|
return &parser.PosError{
|
||||||
|
Pos: node.Pos(),
|
||||||
|
Err: fmt.Errorf("%s: cannot decode into interface: %T", name, node),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf(
|
||||||
|
"%s: cannot decode into interface: %T",
|
||||||
|
name, node)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the result to what its supposed to be, then reset
|
||||||
|
// result so we don't reflect into this method anymore.
|
||||||
|
result.Set(set)
|
||||||
|
|
||||||
|
if redecode {
|
||||||
|
// Revisit the node so that we can use the newly instantiated
|
||||||
|
// thing and populate it.
|
||||||
|
if err := d.decode(name, node, result); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) decodeMap(name string, node ast.Node, result reflect.Value) error {
|
||||||
|
if item, ok := node.(*ast.ObjectItem); ok {
|
||||||
|
node = &ast.ObjectList{Items: []*ast.ObjectItem{item}}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ot, ok := node.(*ast.ObjectType); ok {
|
||||||
|
node = ot.List
|
||||||
|
}
|
||||||
|
|
||||||
|
n, ok := node.(*ast.ObjectList)
|
||||||
|
if !ok {
|
||||||
|
return &parser.PosError{
|
||||||
|
Pos: node.Pos(),
|
||||||
|
Err: fmt.Errorf("%s: not an object type for map (%T)", name, node),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have an interface, then we can address the interface,
|
||||||
|
// but not the slice itself, so get the element but set the interface
|
||||||
|
set := result
|
||||||
|
if result.Kind() == reflect.Interface {
|
||||||
|
result = result.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
resultType := result.Type()
|
||||||
|
resultElemType := resultType.Elem()
|
||||||
|
resultKeyType := resultType.Key()
|
||||||
|
if resultKeyType.Kind() != reflect.String {
|
||||||
|
return &parser.PosError{
|
||||||
|
Pos: node.Pos(),
|
||||||
|
Err: fmt.Errorf("%s: map must have string keys", name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a map if it is nil
|
||||||
|
resultMap := result
|
||||||
|
if result.IsNil() {
|
||||||
|
resultMap = reflect.MakeMap(
|
||||||
|
reflect.MapOf(resultKeyType, resultElemType))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go through each element and decode it.
|
||||||
|
done := make(map[string]struct{})
|
||||||
|
for _, item := range n.Items {
|
||||||
|
if item.Val == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// github.com/hashicorp/terraform/issue/5740
|
||||||
|
if len(item.Keys) == 0 {
|
||||||
|
return &parser.PosError{
|
||||||
|
Pos: node.Pos(),
|
||||||
|
Err: fmt.Errorf("%s: map must have string keys", name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the key we're dealing with, which is the first item
|
||||||
|
keyStr := item.Keys[0].Token.Value().(string)
|
||||||
|
|
||||||
|
// If we've already processed this key, then ignore it
|
||||||
|
if _, ok := done[keyStr]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the value. If we have more than one key, then we
|
||||||
|
// get the objectlist of only these keys.
|
||||||
|
itemVal := item.Val
|
||||||
|
if len(item.Keys) > 1 {
|
||||||
|
itemVal = n.Filter(keyStr)
|
||||||
|
done[keyStr] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the field name
|
||||||
|
fieldName := fmt.Sprintf("%s.%s", name, keyStr)
|
||||||
|
|
||||||
|
// Get the key/value as reflection values
|
||||||
|
key := reflect.ValueOf(keyStr)
|
||||||
|
val := reflect.Indirect(reflect.New(resultElemType))
|
||||||
|
|
||||||
|
// If we have a pre-existing value in the map, use that
|
||||||
|
oldVal := resultMap.MapIndex(key)
|
||||||
|
if oldVal.IsValid() {
|
||||||
|
val.Set(oldVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode!
|
||||||
|
if err := d.decode(fieldName, itemVal, val); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the value on the map
|
||||||
|
resultMap.SetMapIndex(key, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the final map if we can
|
||||||
|
set.Set(resultMap)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) decodePtr(name string, node ast.Node, result reflect.Value) error {
|
||||||
|
// Create an element of the concrete (non pointer) type and decode
|
||||||
|
// into that. Then set the value of the pointer to this type.
|
||||||
|
resultType := result.Type()
|
||||||
|
resultElemType := resultType.Elem()
|
||||||
|
val := reflect.New(resultElemType)
|
||||||
|
if err := d.decode(name, node, reflect.Indirect(val)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Set(val)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) decodeSlice(name string, node ast.Node, result reflect.Value) error {
|
||||||
|
// If we have an interface, then we can address the interface,
|
||||||
|
// but not the slice itself, so get the element but set the interface
|
||||||
|
set := result
|
||||||
|
if result.Kind() == reflect.Interface {
|
||||||
|
result = result.Elem()
|
||||||
|
}
|
||||||
|
// Create the slice if it isn't nil
|
||||||
|
resultType := result.Type()
|
||||||
|
resultElemType := resultType.Elem()
|
||||||
|
if result.IsNil() {
|
||||||
|
resultSliceType := reflect.SliceOf(resultElemType)
|
||||||
|
result = reflect.MakeSlice(
|
||||||
|
resultSliceType, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Figure out the items we'll be copying into the slice
|
||||||
|
var items []ast.Node
|
||||||
|
switch n := node.(type) {
|
||||||
|
case *ast.ObjectList:
|
||||||
|
items = make([]ast.Node, len(n.Items))
|
||||||
|
for i, item := range n.Items {
|
||||||
|
items[i] = item
|
||||||
|
}
|
||||||
|
case *ast.ObjectType:
|
||||||
|
items = []ast.Node{n}
|
||||||
|
case *ast.ListType:
|
||||||
|
items = n.List
|
||||||
|
default:
|
||||||
|
return &parser.PosError{
|
||||||
|
Pos: node.Pos(),
|
||||||
|
Err: fmt.Errorf("unknown slice type: %T", node),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, item := range items {
|
||||||
|
fieldName := fmt.Sprintf("%s[%d]", name, i)
|
||||||
|
|
||||||
|
// Decode
|
||||||
|
val := reflect.Indirect(reflect.New(resultElemType))
|
||||||
|
|
||||||
|
// if item is an object that was decoded from ambiguous JSON and
|
||||||
|
// flattened, make sure it's expanded if it needs to decode into a
|
||||||
|
// defined structure.
|
||||||
|
item := expandObject(item, val)
|
||||||
|
|
||||||
|
if err := d.decode(fieldName, item, val); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append it onto the slice
|
||||||
|
result = reflect.Append(result, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
set.Set(result)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// expandObject detects if an ambiguous JSON object was flattened to a List which
|
||||||
|
// should be decoded into a struct, and expands the ast to properly deocode.
|
||||||
|
func expandObject(node ast.Node, result reflect.Value) ast.Node {
|
||||||
|
item, ok := node.(*ast.ObjectItem)
|
||||||
|
if !ok {
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
elemType := result.Type()
|
||||||
|
|
||||||
|
// our target type must be a struct
|
||||||
|
switch elemType.Kind() {
|
||||||
|
case reflect.Ptr:
|
||||||
|
switch elemType.Elem().Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
//OK
|
||||||
|
default:
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
case reflect.Struct:
|
||||||
|
//OK
|
||||||
|
default:
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
// A list value will have a key and field name. If it had more fields,
|
||||||
|
// it wouldn't have been flattened.
|
||||||
|
if len(item.Keys) != 2 {
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
keyToken := item.Keys[0].Token
|
||||||
|
item.Keys = item.Keys[1:]
|
||||||
|
|
||||||
|
// we need to un-flatten the ast enough to decode
|
||||||
|
newNode := &ast.ObjectItem{
|
||||||
|
Keys: []*ast.ObjectKey{
|
||||||
|
&ast.ObjectKey{
|
||||||
|
Token: keyToken,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Val: &ast.ObjectType{
|
||||||
|
List: &ast.ObjectList{
|
||||||
|
Items: []*ast.ObjectItem{item},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return newNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) decodeString(name string, node ast.Node, result reflect.Value) error {
|
||||||
|
switch n := node.(type) {
|
||||||
|
case *ast.LiteralType:
|
||||||
|
switch n.Token.Type {
|
||||||
|
case token.NUMBER:
|
||||||
|
result.Set(reflect.ValueOf(n.Token.Text).Convert(result.Type()))
|
||||||
|
return nil
|
||||||
|
case token.STRING, token.HEREDOC:
|
||||||
|
result.Set(reflect.ValueOf(n.Token.Value()).Convert(result.Type()))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &parser.PosError{
|
||||||
|
Pos: node.Pos(),
|
||||||
|
Err: fmt.Errorf("%s: unknown type for string %T", name, node),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) decodeStruct(name string, node ast.Node, result reflect.Value) error {
|
||||||
|
var item *ast.ObjectItem
|
||||||
|
if it, ok := node.(*ast.ObjectItem); ok {
|
||||||
|
item = it
|
||||||
|
node = it.Val
|
||||||
|
}
|
||||||
|
|
||||||
|
if ot, ok := node.(*ast.ObjectType); ok {
|
||||||
|
node = ot.List
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the special case where the object itself is a literal. Previously
|
||||||
|
// the yacc parser would always ensure top-level elements were arrays. The new
|
||||||
|
// parser does not make the same guarantees, thus we need to convert any
|
||||||
|
// top-level literal elements into a list.
|
||||||
|
if _, ok := node.(*ast.LiteralType); ok && item != nil {
|
||||||
|
node = &ast.ObjectList{Items: []*ast.ObjectItem{item}}
|
||||||
|
}
|
||||||
|
|
||||||
|
list, ok := node.(*ast.ObjectList)
|
||||||
|
if !ok {
|
||||||
|
return &parser.PosError{
|
||||||
|
Pos: node.Pos(),
|
||||||
|
Err: fmt.Errorf("%s: not an object type for struct (%T)", name, node),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This slice will keep track of all the structs we'll be decoding.
|
||||||
|
// There can be more than one struct if there are embedded structs
|
||||||
|
// that are squashed.
|
||||||
|
structs := make([]reflect.Value, 1, 5)
|
||||||
|
structs[0] = result
|
||||||
|
|
||||||
|
// Compile the list of all the fields that we're going to be decoding
|
||||||
|
// from all the structs.
|
||||||
|
type field struct {
|
||||||
|
field reflect.StructField
|
||||||
|
val reflect.Value
|
||||||
|
}
|
||||||
|
fields := []field{}
|
||||||
|
for len(structs) > 0 {
|
||||||
|
structVal := structs[0]
|
||||||
|
structs = structs[1:]
|
||||||
|
|
||||||
|
structType := structVal.Type()
|
||||||
|
for i := 0; i < structType.NumField(); i++ {
|
||||||
|
fieldType := structType.Field(i)
|
||||||
|
tagParts := strings.Split(fieldType.Tag.Get(tagName), ",")
|
||||||
|
|
||||||
|
// Ignore fields with tag name "-"
|
||||||
|
if tagParts[0] == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if fieldType.Anonymous {
|
||||||
|
fieldKind := fieldType.Type.Kind()
|
||||||
|
if fieldKind != reflect.Struct {
|
||||||
|
return &parser.PosError{
|
||||||
|
Pos: node.Pos(),
|
||||||
|
Err: fmt.Errorf("%s: unsupported type to struct: %s",
|
||||||
|
fieldType.Name, fieldKind),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have an embedded field. We "squash" the fields down
|
||||||
|
// if specified in the tag.
|
||||||
|
squash := false
|
||||||
|
for _, tag := range tagParts[1:] {
|
||||||
|
if tag == "squash" {
|
||||||
|
squash = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if squash {
|
||||||
|
structs = append(
|
||||||
|
structs, result.FieldByName(fieldType.Name))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal struct field, store it away
|
||||||
|
fields = append(fields, field{fieldType, structVal.Field(i)})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
usedKeys := make(map[string]struct{})
|
||||||
|
decodedFields := make([]string, 0, len(fields))
|
||||||
|
decodedFieldsVal := make([]reflect.Value, 0)
|
||||||
|
unusedKeysVal := make([]reflect.Value, 0)
|
||||||
|
for _, f := range fields {
|
||||||
|
field, fieldValue := f.field, f.val
|
||||||
|
if !fieldValue.IsValid() {
|
||||||
|
// This should never happen
|
||||||
|
panic("field is not valid")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we can't set the field, then it is unexported or something,
|
||||||
|
// and we just continue onwards.
|
||||||
|
if !fieldValue.CanSet() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldName := field.Name
|
||||||
|
|
||||||
|
tagValue := field.Tag.Get(tagName)
|
||||||
|
tagParts := strings.SplitN(tagValue, ",", 2)
|
||||||
|
if len(tagParts) >= 2 {
|
||||||
|
switch tagParts[1] {
|
||||||
|
case "decodedFields":
|
||||||
|
decodedFieldsVal = append(decodedFieldsVal, fieldValue)
|
||||||
|
continue
|
||||||
|
case "key":
|
||||||
|
if item == nil {
|
||||||
|
return &parser.PosError{
|
||||||
|
Pos: node.Pos(),
|
||||||
|
Err: fmt.Errorf("%s: %s asked for 'key', impossible",
|
||||||
|
name, fieldName),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldValue.SetString(item.Keys[0].Token.Value().(string))
|
||||||
|
continue
|
||||||
|
case "unusedKeys":
|
||||||
|
unusedKeysVal = append(unusedKeysVal, fieldValue)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tagParts[0] != "" {
|
||||||
|
fieldName = tagParts[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the element we'll use to decode. If it is a single
|
||||||
|
// match (only object with the field), then we decode it exactly.
|
||||||
|
// If it is a prefix match, then we decode the matches.
|
||||||
|
filter := list.Filter(fieldName)
|
||||||
|
|
||||||
|
prefixMatches := filter.Children()
|
||||||
|
matches := filter.Elem()
|
||||||
|
if len(matches.Items) == 0 && len(prefixMatches.Items) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track the used key
|
||||||
|
usedKeys[fieldName] = struct{}{}
|
||||||
|
|
||||||
|
// Create the field name and decode. We range over the elements
|
||||||
|
// because we actually want the value.
|
||||||
|
fieldName = fmt.Sprintf("%s.%s", name, fieldName)
|
||||||
|
if len(prefixMatches.Items) > 0 {
|
||||||
|
if err := d.decode(fieldName, prefixMatches, fieldValue); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, match := range matches.Items {
|
||||||
|
var decodeNode ast.Node = match.Val
|
||||||
|
if ot, ok := decodeNode.(*ast.ObjectType); ok {
|
||||||
|
decodeNode = &ast.ObjectList{Items: ot.List.Items}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := d.decode(fieldName, decodeNode, fieldValue); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
decodedFields = append(decodedFields, field.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(decodedFieldsVal) > 0 {
|
||||||
|
// Sort it so that it is deterministic
|
||||||
|
sort.Strings(decodedFields)
|
||||||
|
|
||||||
|
for _, v := range decodedFieldsVal {
|
||||||
|
v.Set(reflect.ValueOf(decodedFields))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// findNodeType returns the type of ast.Node
|
||||||
|
func findNodeType() reflect.Type {
|
||||||
|
var nodeContainer struct {
|
||||||
|
Node ast.Node
|
||||||
|
}
|
||||||
|
value := reflect.ValueOf(nodeContainer).FieldByName("Node")
|
||||||
|
return value.Type()
|
||||||
|
}
|
11
vendor/github.com/hashicorp/hcl/hcl.go
generated
vendored
Normal file
11
vendor/github.com/hashicorp/hcl/hcl.go
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
// Package hcl decodes HCL into usable Go structures.
|
||||||
|
//
|
||||||
|
// hcl input can come in either pure HCL format or JSON format.
|
||||||
|
// It can be parsed into an AST, and then decoded into a structure,
|
||||||
|
// or it can be decoded directly from a string into a structure.
|
||||||
|
//
|
||||||
|
// If you choose to parse HCL into a raw AST, the benefit is that you
|
||||||
|
// can write custom visitor implementations to implement custom
|
||||||
|
// semantic checks. By default, HCL does not perform any semantic
|
||||||
|
// checks.
|
||||||
|
package hcl
|
219
vendor/github.com/hashicorp/hcl/hcl/ast/ast.go
generated
vendored
Normal file
219
vendor/github.com/hashicorp/hcl/hcl/ast/ast.go
generated
vendored
Normal file
|
@ -0,0 +1,219 @@
|
||||||
|
// Package ast declares the types used to represent syntax trees for HCL
|
||||||
|
// (HashiCorp Configuration Language)
|
||||||
|
package ast
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/hcl/hcl/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Node is an element in the abstract syntax tree.
|
||||||
|
type Node interface {
|
||||||
|
node()
|
||||||
|
Pos() token.Pos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (File) node() {}
|
||||||
|
func (ObjectList) node() {}
|
||||||
|
func (ObjectKey) node() {}
|
||||||
|
func (ObjectItem) node() {}
|
||||||
|
func (Comment) node() {}
|
||||||
|
func (CommentGroup) node() {}
|
||||||
|
func (ObjectType) node() {}
|
||||||
|
func (LiteralType) node() {}
|
||||||
|
func (ListType) node() {}
|
||||||
|
|
||||||
|
// File represents a single HCL file
|
||||||
|
type File struct {
|
||||||
|
Node Node // usually a *ObjectList
|
||||||
|
Comments []*CommentGroup // list of all comments in the source
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) Pos() token.Pos {
|
||||||
|
return f.Node.Pos()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObjectList represents a list of ObjectItems. An HCL file itself is an
|
||||||
|
// ObjectList.
|
||||||
|
type ObjectList struct {
|
||||||
|
Items []*ObjectItem
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ObjectList) Add(item *ObjectItem) {
|
||||||
|
o.Items = append(o.Items, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter filters out the objects with the given key list as a prefix.
|
||||||
|
//
|
||||||
|
// The returned list of objects contain ObjectItems where the keys have
|
||||||
|
// this prefix already stripped off. This might result in objects with
|
||||||
|
// zero-length key lists if they have no children.
|
||||||
|
//
|
||||||
|
// If no matches are found, an empty ObjectList (non-nil) is returned.
|
||||||
|
func (o *ObjectList) Filter(keys ...string) *ObjectList {
|
||||||
|
var result ObjectList
|
||||||
|
for _, item := range o.Items {
|
||||||
|
// If there aren't enough keys, then ignore this
|
||||||
|
if len(item.Keys) < len(keys) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
match := true
|
||||||
|
for i, key := range item.Keys[:len(keys)] {
|
||||||
|
key := key.Token.Value().(string)
|
||||||
|
if key != keys[i] && !strings.EqualFold(key, keys[i]) {
|
||||||
|
match = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !match {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip off the prefix from the children
|
||||||
|
newItem := *item
|
||||||
|
newItem.Keys = newItem.Keys[len(keys):]
|
||||||
|
result.Add(&newItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Children returns further nested objects (key length > 0) within this
|
||||||
|
// ObjectList. This should be used with Filter to get at child items.
|
||||||
|
func (o *ObjectList) Children() *ObjectList {
|
||||||
|
var result ObjectList
|
||||||
|
for _, item := range o.Items {
|
||||||
|
if len(item.Keys) > 0 {
|
||||||
|
result.Add(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Elem returns items in the list that are direct element assignments
|
||||||
|
// (key length == 0). This should be used with Filter to get at elements.
|
||||||
|
func (o *ObjectList) Elem() *ObjectList {
|
||||||
|
var result ObjectList
|
||||||
|
for _, item := range o.Items {
|
||||||
|
if len(item.Keys) == 0 {
|
||||||
|
result.Add(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ObjectList) Pos() token.Pos {
|
||||||
|
// always returns the uninitiliazed position
|
||||||
|
return o.Items[0].Pos()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObjectItem represents a HCL Object Item. An item is represented with a key
|
||||||
|
// (or keys). It can be an assignment or an object (both normal and nested)
|
||||||
|
type ObjectItem struct {
|
||||||
|
// keys is only one length long if it's of type assignment. If it's a
|
||||||
|
// nested object it can be larger than one. In that case "assign" is
|
||||||
|
// invalid as there is no assignments for a nested object.
|
||||||
|
Keys []*ObjectKey
|
||||||
|
|
||||||
|
// assign contains the position of "=", if any
|
||||||
|
Assign token.Pos
|
||||||
|
|
||||||
|
// val is the item itself. It can be an object,list, number, bool or a
|
||||||
|
// string. If key length is larger than one, val can be only of type
|
||||||
|
// Object.
|
||||||
|
Val Node
|
||||||
|
|
||||||
|
LeadComment *CommentGroup // associated lead comment
|
||||||
|
LineComment *CommentGroup // associated line comment
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ObjectItem) Pos() token.Pos {
|
||||||
|
// I'm not entirely sure what causes this, but removing this causes
|
||||||
|
// a test failure. We should investigate at some point.
|
||||||
|
if len(o.Keys) == 0 {
|
||||||
|
return token.Pos{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return o.Keys[0].Pos()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObjectKeys are either an identifier or of type string.
|
||||||
|
type ObjectKey struct {
|
||||||
|
Token token.Token
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ObjectKey) Pos() token.Pos {
|
||||||
|
return o.Token.Pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// LiteralType represents a literal of basic type. Valid types are:
|
||||||
|
// token.NUMBER, token.FLOAT, token.BOOL and token.STRING
|
||||||
|
type LiteralType struct {
|
||||||
|
Token token.Token
|
||||||
|
|
||||||
|
// comment types, only used when in a list
|
||||||
|
LeadComment *CommentGroup
|
||||||
|
LineComment *CommentGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LiteralType) Pos() token.Pos {
|
||||||
|
return l.Token.Pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListStatement represents a HCL List type
|
||||||
|
type ListType struct {
|
||||||
|
Lbrack token.Pos // position of "["
|
||||||
|
Rbrack token.Pos // position of "]"
|
||||||
|
List []Node // the elements in lexical order
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ListType) Pos() token.Pos {
|
||||||
|
return l.Lbrack
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ListType) Add(node Node) {
|
||||||
|
l.List = append(l.List, node)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObjectType represents a HCL Object Type
|
||||||
|
type ObjectType struct {
|
||||||
|
Lbrace token.Pos // position of "{"
|
||||||
|
Rbrace token.Pos // position of "}"
|
||||||
|
List *ObjectList // the nodes in lexical order
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ObjectType) Pos() token.Pos {
|
||||||
|
return o.Lbrace
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comment node represents a single //, # style or /*- style commment
|
||||||
|
type Comment struct {
|
||||||
|
Start token.Pos // position of / or #
|
||||||
|
Text string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Comment) Pos() token.Pos {
|
||||||
|
return c.Start
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommentGroup node represents a sequence of comments with no other tokens and
|
||||||
|
// no empty lines between.
|
||||||
|
type CommentGroup struct {
|
||||||
|
List []*Comment // len(List) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommentGroup) Pos() token.Pos {
|
||||||
|
return c.List[0].Pos()
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------
|
||||||
|
// GoStringer
|
||||||
|
//-------------------------------------------------------------------
|
||||||
|
|
||||||
|
func (o *ObjectKey) GoString() string { return fmt.Sprintf("*%#v", *o) }
|
||||||
|
func (o *ObjectList) GoString() string { return fmt.Sprintf("*%#v", *o) }
|
52
vendor/github.com/hashicorp/hcl/hcl/ast/walk.go
generated
vendored
Normal file
52
vendor/github.com/hashicorp/hcl/hcl/ast/walk.go
generated
vendored
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
package ast
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// WalkFunc describes a function to be called for each node during a Walk. The
|
||||||
|
// returned node can be used to rewrite the AST. Walking stops the returned
|
||||||
|
// bool is false.
|
||||||
|
type WalkFunc func(Node) (Node, bool)
|
||||||
|
|
||||||
|
// Walk traverses an AST in depth-first order: It starts by calling fn(node);
|
||||||
|
// node must not be nil. If fn returns true, Walk invokes fn recursively for
|
||||||
|
// each of the non-nil children of node, followed by a call of fn(nil). The
|
||||||
|
// returned node of fn can be used to rewrite the passed node to fn.
|
||||||
|
func Walk(node Node, fn WalkFunc) Node {
|
||||||
|
rewritten, ok := fn(node)
|
||||||
|
if !ok {
|
||||||
|
return rewritten
|
||||||
|
}
|
||||||
|
|
||||||
|
switch n := node.(type) {
|
||||||
|
case *File:
|
||||||
|
n.Node = Walk(n.Node, fn)
|
||||||
|
case *ObjectList:
|
||||||
|
for i, item := range n.Items {
|
||||||
|
n.Items[i] = Walk(item, fn).(*ObjectItem)
|
||||||
|
}
|
||||||
|
case *ObjectKey:
|
||||||
|
// nothing to do
|
||||||
|
case *ObjectItem:
|
||||||
|
for i, k := range n.Keys {
|
||||||
|
n.Keys[i] = Walk(k, fn).(*ObjectKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.Val != nil {
|
||||||
|
n.Val = Walk(n.Val, fn)
|
||||||
|
}
|
||||||
|
case *LiteralType:
|
||||||
|
// nothing to do
|
||||||
|
case *ListType:
|
||||||
|
for i, l := range n.List {
|
||||||
|
n.List[i] = Walk(l, fn)
|
||||||
|
}
|
||||||
|
case *ObjectType:
|
||||||
|
n.List = Walk(n.List, fn).(*ObjectList)
|
||||||
|
default:
|
||||||
|
// should we panic here?
|
||||||
|
fmt.Printf("unknown type: %T\n", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn(nil)
|
||||||
|
return rewritten
|
||||||
|
}
|
17
vendor/github.com/hashicorp/hcl/hcl/parser/error.go
generated
vendored
Normal file
17
vendor/github.com/hashicorp/hcl/hcl/parser/error.go
generated
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/hcl/hcl/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PosError is a parse error that contains a position.
|
||||||
|
type PosError struct {
|
||||||
|
Pos token.Pos
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *PosError) Error() string {
|
||||||
|
return fmt.Sprintf("At %s: %s", e.Pos, e.Err)
|
||||||
|
}
|
532
vendor/github.com/hashicorp/hcl/hcl/parser/parser.go
generated
vendored
Normal file
532
vendor/github.com/hashicorp/hcl/hcl/parser/parser.go
generated
vendored
Normal file
|
@ -0,0 +1,532 @@
|
||||||
|
// Package parser implements a parser for HCL (HashiCorp Configuration
|
||||||
|
// Language)
|
||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/hcl/hcl/ast"
|
||||||
|
"github.com/hashicorp/hcl/hcl/scanner"
|
||||||
|
"github.com/hashicorp/hcl/hcl/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Parser struct {
|
||||||
|
sc *scanner.Scanner
|
||||||
|
|
||||||
|
// Last read token
|
||||||
|
tok token.Token
|
||||||
|
commaPrev token.Token
|
||||||
|
|
||||||
|
comments []*ast.CommentGroup
|
||||||
|
leadComment *ast.CommentGroup // last lead comment
|
||||||
|
lineComment *ast.CommentGroup // last line comment
|
||||||
|
|
||||||
|
enableTrace bool
|
||||||
|
indent int
|
||||||
|
n int // buffer size (max = 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newParser(src []byte) *Parser {
|
||||||
|
return &Parser{
|
||||||
|
sc: scanner.New(src),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse returns the fully parsed source and returns the abstract syntax tree.
|
||||||
|
func Parse(src []byte) (*ast.File, error) {
|
||||||
|
// normalize all line endings
|
||||||
|
// since the scanner and output only work with "\n" line endings, we may
|
||||||
|
// end up with dangling "\r" characters in the parsed data.
|
||||||
|
src = bytes.Replace(src, []byte("\r\n"), []byte("\n"), -1)
|
||||||
|
|
||||||
|
p := newParser(src)
|
||||||
|
return p.Parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
var errEofToken = errors.New("EOF token found")
|
||||||
|
|
||||||
|
// Parse returns the fully parsed source and returns the abstract syntax tree.
|
||||||
|
func (p *Parser) Parse() (*ast.File, error) {
|
||||||
|
f := &ast.File{}
|
||||||
|
var err, scerr error
|
||||||
|
p.sc.Error = func(pos token.Pos, msg string) {
|
||||||
|
scerr = &PosError{Pos: pos, Err: errors.New(msg)}
|
||||||
|
}
|
||||||
|
|
||||||
|
f.Node, err = p.objectList(false)
|
||||||
|
if scerr != nil {
|
||||||
|
return nil, scerr
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
f.Comments = p.comments
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// objectList parses a list of items within an object (generally k/v pairs).
|
||||||
|
// The parameter" obj" tells this whether to we are within an object (braces:
|
||||||
|
// '{', '}') or just at the top level. If we're within an object, we end
|
||||||
|
// at an RBRACE.
|
||||||
|
func (p *Parser) objectList(obj bool) (*ast.ObjectList, error) {
|
||||||
|
defer un(trace(p, "ParseObjectList"))
|
||||||
|
node := &ast.ObjectList{}
|
||||||
|
|
||||||
|
for {
|
||||||
|
if obj {
|
||||||
|
tok := p.scan()
|
||||||
|
p.unscan()
|
||||||
|
if tok.Type == token.RBRACE {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := p.objectItem()
|
||||||
|
if err == errEofToken {
|
||||||
|
break // we are finished
|
||||||
|
}
|
||||||
|
|
||||||
|
// we don't return a nil node, because might want to use already
|
||||||
|
// collected items.
|
||||||
|
if err != nil {
|
||||||
|
return node, err
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Add(n)
|
||||||
|
|
||||||
|
// object lists can be optionally comma-delimited e.g. when a list of maps
|
||||||
|
// is being expressed, so a comma is allowed here - it's simply consumed
|
||||||
|
tok := p.scan()
|
||||||
|
if tok.Type != token.COMMA {
|
||||||
|
p.unscan()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return node, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) consumeComment() (comment *ast.Comment, endline int) {
|
||||||
|
endline = p.tok.Pos.Line
|
||||||
|
|
||||||
|
// count the endline if it's multiline comment, ie starting with /*
|
||||||
|
if len(p.tok.Text) > 1 && p.tok.Text[1] == '*' {
|
||||||
|
// don't use range here - no need to decode Unicode code points
|
||||||
|
for i := 0; i < len(p.tok.Text); i++ {
|
||||||
|
if p.tok.Text[i] == '\n' {
|
||||||
|
endline++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
comment = &ast.Comment{Start: p.tok.Pos, Text: p.tok.Text}
|
||||||
|
p.tok = p.sc.Scan()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) consumeCommentGroup(n int) (comments *ast.CommentGroup, endline int) {
|
||||||
|
var list []*ast.Comment
|
||||||
|
endline = p.tok.Pos.Line
|
||||||
|
|
||||||
|
for p.tok.Type == token.COMMENT && p.tok.Pos.Line <= endline+n {
|
||||||
|
var comment *ast.Comment
|
||||||
|
comment, endline = p.consumeComment()
|
||||||
|
list = append(list, comment)
|
||||||
|
}
|
||||||
|
|
||||||
|
// add comment group to the comments list
|
||||||
|
comments = &ast.CommentGroup{List: list}
|
||||||
|
p.comments = append(p.comments, comments)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// objectItem parses a single object item
|
||||||
|
func (p *Parser) objectItem() (*ast.ObjectItem, error) {
|
||||||
|
defer un(trace(p, "ParseObjectItem"))
|
||||||
|
|
||||||
|
keys, err := p.objectKey()
|
||||||
|
if len(keys) > 0 && err == errEofToken {
|
||||||
|
// We ignore eof token here since it is an error if we didn't
|
||||||
|
// receive a value (but we did receive a key) for the item.
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
if len(keys) > 0 && err != nil && p.tok.Type == token.RBRACE {
|
||||||
|
// This is a strange boolean statement, but what it means is:
|
||||||
|
// We have keys with no value, and we're likely in an object
|
||||||
|
// (since RBrace ends an object). For this, we set err to nil so
|
||||||
|
// we continue and get the error below of having the wrong value
|
||||||
|
// type.
|
||||||
|
err = nil
|
||||||
|
|
||||||
|
// Reset the token type so we don't think it completed fine. See
|
||||||
|
// objectType which uses p.tok.Type to check if we're done with
|
||||||
|
// the object.
|
||||||
|
p.tok.Type = token.EOF
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
o := &ast.ObjectItem{
|
||||||
|
Keys: keys,
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.leadComment != nil {
|
||||||
|
o.LeadComment = p.leadComment
|
||||||
|
p.leadComment = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch p.tok.Type {
|
||||||
|
case token.ASSIGN:
|
||||||
|
o.Assign = p.tok.Pos
|
||||||
|
o.Val, err = p.object()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case token.LBRACE:
|
||||||
|
o.Val, err = p.objectType()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
keyStr := make([]string, 0, len(keys))
|
||||||
|
for _, k := range keys {
|
||||||
|
keyStr = append(keyStr, k.Token.Text)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, &PosError{
|
||||||
|
Pos: p.tok.Pos,
|
||||||
|
Err: fmt.Errorf(
|
||||||
|
"key '%s' expected start of object ('{') or assignment ('=')",
|
||||||
|
strings.Join(keyStr, " ")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// key=#comment
|
||||||
|
// val
|
||||||
|
if p.lineComment != nil {
|
||||||
|
o.LineComment, p.lineComment = p.lineComment, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// do a look-ahead for line comment
|
||||||
|
p.scan()
|
||||||
|
if len(keys) > 0 && o.Val.Pos().Line == keys[0].Pos().Line && p.lineComment != nil {
|
||||||
|
o.LineComment = p.lineComment
|
||||||
|
p.lineComment = nil
|
||||||
|
}
|
||||||
|
p.unscan()
|
||||||
|
return o, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// objectKey parses an object key and returns a ObjectKey AST
|
||||||
|
func (p *Parser) objectKey() ([]*ast.ObjectKey, error) {
|
||||||
|
keyCount := 0
|
||||||
|
keys := make([]*ast.ObjectKey, 0)
|
||||||
|
|
||||||
|
for {
|
||||||
|
tok := p.scan()
|
||||||
|
switch tok.Type {
|
||||||
|
case token.EOF:
|
||||||
|
// It is very important to also return the keys here as well as
|
||||||
|
// the error. This is because we need to be able to tell if we
|
||||||
|
// did parse keys prior to finding the EOF, or if we just found
|
||||||
|
// a bare EOF.
|
||||||
|
return keys, errEofToken
|
||||||
|
case token.ASSIGN:
|
||||||
|
// assignment or object only, but not nested objects. this is not
|
||||||
|
// allowed: `foo bar = {}`
|
||||||
|
if keyCount > 1 {
|
||||||
|
return nil, &PosError{
|
||||||
|
Pos: p.tok.Pos,
|
||||||
|
Err: fmt.Errorf("nested object expected: LBRACE got: %s", p.tok.Type),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if keyCount == 0 {
|
||||||
|
return nil, &PosError{
|
||||||
|
Pos: p.tok.Pos,
|
||||||
|
Err: errors.New("no object keys found!"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys, nil
|
||||||
|
case token.LBRACE:
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// If we have no keys, then it is a syntax error. i.e. {{}} is not
|
||||||
|
// allowed.
|
||||||
|
if len(keys) == 0 {
|
||||||
|
err = &PosError{
|
||||||
|
Pos: p.tok.Pos,
|
||||||
|
Err: fmt.Errorf("expected: IDENT | STRING got: %s", p.tok.Type),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// object
|
||||||
|
return keys, err
|
||||||
|
case token.IDENT, token.STRING:
|
||||||
|
keyCount++
|
||||||
|
keys = append(keys, &ast.ObjectKey{Token: p.tok})
|
||||||
|
case token.ILLEGAL:
|
||||||
|
return keys, &PosError{
|
||||||
|
Pos: p.tok.Pos,
|
||||||
|
Err: fmt.Errorf("illegal character"),
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return keys, &PosError{
|
||||||
|
Pos: p.tok.Pos,
|
||||||
|
Err: fmt.Errorf("expected: IDENT | STRING | ASSIGN | LBRACE got: %s", p.tok.Type),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// object parses any type of object, such as number, bool, string, object or
|
||||||
|
// list.
|
||||||
|
func (p *Parser) object() (ast.Node, error) {
|
||||||
|
defer un(trace(p, "ParseType"))
|
||||||
|
tok := p.scan()
|
||||||
|
|
||||||
|
switch tok.Type {
|
||||||
|
case token.NUMBER, token.FLOAT, token.BOOL, token.STRING, token.HEREDOC:
|
||||||
|
return p.literalType()
|
||||||
|
case token.LBRACE:
|
||||||
|
return p.objectType()
|
||||||
|
case token.LBRACK:
|
||||||
|
return p.listType()
|
||||||
|
case token.COMMENT:
|
||||||
|
// implement comment
|
||||||
|
case token.EOF:
|
||||||
|
return nil, errEofToken
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, &PosError{
|
||||||
|
Pos: tok.Pos,
|
||||||
|
Err: fmt.Errorf("Unknown token: %+v", tok),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// objectType parses an object type and returns a ObjectType AST
|
||||||
|
func (p *Parser) objectType() (*ast.ObjectType, error) {
|
||||||
|
defer un(trace(p, "ParseObjectType"))
|
||||||
|
|
||||||
|
// we assume that the currently scanned token is a LBRACE
|
||||||
|
o := &ast.ObjectType{
|
||||||
|
Lbrace: p.tok.Pos,
|
||||||
|
}
|
||||||
|
|
||||||
|
l, err := p.objectList(true)
|
||||||
|
|
||||||
|
// if we hit RBRACE, we are good to go (means we parsed all Items), if it's
|
||||||
|
// not a RBRACE, it's an syntax error and we just return it.
|
||||||
|
if err != nil && p.tok.Type != token.RBRACE {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// No error, scan and expect the ending to be a brace
|
||||||
|
if tok := p.scan(); tok.Type != token.RBRACE {
|
||||||
|
return nil, &PosError{
|
||||||
|
Pos: tok.Pos,
|
||||||
|
Err: fmt.Errorf("object expected closing RBRACE got: %s", tok.Type),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
o.List = l
|
||||||
|
o.Rbrace = p.tok.Pos // advanced via parseObjectList
|
||||||
|
return o, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// listType parses a list type and returns a ListType AST
|
||||||
|
func (p *Parser) listType() (*ast.ListType, error) {
|
||||||
|
defer un(trace(p, "ParseListType"))
|
||||||
|
|
||||||
|
// we assume that the currently scanned token is a LBRACK
|
||||||
|
l := &ast.ListType{
|
||||||
|
Lbrack: p.tok.Pos,
|
||||||
|
}
|
||||||
|
|
||||||
|
needComma := false
|
||||||
|
for {
|
||||||
|
tok := p.scan()
|
||||||
|
if needComma {
|
||||||
|
switch tok.Type {
|
||||||
|
case token.COMMA, token.RBRACK:
|
||||||
|
default:
|
||||||
|
return nil, &PosError{
|
||||||
|
Pos: tok.Pos,
|
||||||
|
Err: fmt.Errorf(
|
||||||
|
"error parsing list, expected comma or list end, got: %s",
|
||||||
|
tok.Type),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch tok.Type {
|
||||||
|
case token.BOOL, token.NUMBER, token.FLOAT, token.STRING, token.HEREDOC:
|
||||||
|
node, err := p.literalType()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is a lead comment, apply it
|
||||||
|
if p.leadComment != nil {
|
||||||
|
node.LeadComment = p.leadComment
|
||||||
|
p.leadComment = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Add(node)
|
||||||
|
needComma = true
|
||||||
|
case token.COMMA:
|
||||||
|
// get next list item or we are at the end
|
||||||
|
// do a look-ahead for line comment
|
||||||
|
p.scan()
|
||||||
|
if p.lineComment != nil && len(l.List) > 0 {
|
||||||
|
lit, ok := l.List[len(l.List)-1].(*ast.LiteralType)
|
||||||
|
if ok {
|
||||||
|
lit.LineComment = p.lineComment
|
||||||
|
l.List[len(l.List)-1] = lit
|
||||||
|
p.lineComment = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.unscan()
|
||||||
|
|
||||||
|
needComma = false
|
||||||
|
continue
|
||||||
|
case token.LBRACE:
|
||||||
|
// Looks like a nested object, so parse it out
|
||||||
|
node, err := p.objectType()
|
||||||
|
if err != nil {
|
||||||
|
return nil, &PosError{
|
||||||
|
Pos: tok.Pos,
|
||||||
|
Err: fmt.Errorf(
|
||||||
|
"error while trying to parse object within list: %s", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l.Add(node)
|
||||||
|
needComma = true
|
||||||
|
case token.LBRACK:
|
||||||
|
node, err := p.listType()
|
||||||
|
if err != nil {
|
||||||
|
return nil, &PosError{
|
||||||
|
Pos: tok.Pos,
|
||||||
|
Err: fmt.Errorf(
|
||||||
|
"error while trying to parse list within list: %s", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l.Add(node)
|
||||||
|
case token.RBRACK:
|
||||||
|
// finished
|
||||||
|
l.Rbrack = p.tok.Pos
|
||||||
|
return l, nil
|
||||||
|
default:
|
||||||
|
return nil, &PosError{
|
||||||
|
Pos: tok.Pos,
|
||||||
|
Err: fmt.Errorf("unexpected token while parsing list: %s", tok.Type),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// literalType parses a literal type and returns a LiteralType AST
|
||||||
|
func (p *Parser) literalType() (*ast.LiteralType, error) {
|
||||||
|
defer un(trace(p, "ParseLiteral"))
|
||||||
|
|
||||||
|
return &ast.LiteralType{
|
||||||
|
Token: p.tok,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// scan returns the next token from the underlying scanner. If a token has
|
||||||
|
// been unscanned then read that instead. In the process, it collects any
|
||||||
|
// comment groups encountered, and remembers the last lead and line comments.
|
||||||
|
func (p *Parser) scan() token.Token {
|
||||||
|
// If we have a token on the buffer, then return it.
|
||||||
|
if p.n != 0 {
|
||||||
|
p.n = 0
|
||||||
|
return p.tok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise read the next token from the scanner and Save it to the buffer
|
||||||
|
// in case we unscan later.
|
||||||
|
prev := p.tok
|
||||||
|
p.tok = p.sc.Scan()
|
||||||
|
|
||||||
|
if p.tok.Type == token.COMMENT {
|
||||||
|
var comment *ast.CommentGroup
|
||||||
|
var endline int
|
||||||
|
|
||||||
|
// fmt.Printf("p.tok.Pos.Line = %+v prev: %d endline %d \n",
|
||||||
|
// p.tok.Pos.Line, prev.Pos.Line, endline)
|
||||||
|
if p.tok.Pos.Line == prev.Pos.Line {
|
||||||
|
// The comment is on same line as the previous token; it
|
||||||
|
// cannot be a lead comment but may be a line comment.
|
||||||
|
comment, endline = p.consumeCommentGroup(0)
|
||||||
|
if p.tok.Pos.Line != endline {
|
||||||
|
// The next token is on a different line, thus
|
||||||
|
// the last comment group is a line comment.
|
||||||
|
p.lineComment = comment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// consume successor comments, if any
|
||||||
|
endline = -1
|
||||||
|
for p.tok.Type == token.COMMENT {
|
||||||
|
comment, endline = p.consumeCommentGroup(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if endline+1 == p.tok.Pos.Line && p.tok.Type != token.RBRACE {
|
||||||
|
switch p.tok.Type {
|
||||||
|
case token.RBRACE, token.RBRACK:
|
||||||
|
// Do not count for these cases
|
||||||
|
default:
|
||||||
|
// The next token is following on the line immediately after the
|
||||||
|
// comment group, thus the last comment group is a lead comment.
|
||||||
|
p.leadComment = comment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.tok
|
||||||
|
}
|
||||||
|
|
||||||
|
// unscan pushes the previously read token back onto the buffer.
|
||||||
|
func (p *Parser) unscan() {
|
||||||
|
p.n = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Parsing support
|
||||||
|
|
||||||
|
func (p *Parser) printTrace(a ...interface{}) {
|
||||||
|
if !p.enableTrace {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const dots = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . "
|
||||||
|
const n = len(dots)
|
||||||
|
fmt.Printf("%5d:%3d: ", p.tok.Pos.Line, p.tok.Pos.Column)
|
||||||
|
|
||||||
|
i := 2 * p.indent
|
||||||
|
for i > n {
|
||||||
|
fmt.Print(dots)
|
||||||
|
i -= n
|
||||||
|
}
|
||||||
|
// i <= n
|
||||||
|
fmt.Print(dots[0:i])
|
||||||
|
fmt.Println(a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func trace(p *Parser, msg string) *Parser {
|
||||||
|
p.printTrace(msg, "(")
|
||||||
|
p.indent++
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage pattern: defer un(trace(p, "..."))
|
||||||
|
func un(p *Parser) {
|
||||||
|
p.indent--
|
||||||
|
p.printTrace(")")
|
||||||
|
}
|
789
vendor/github.com/hashicorp/hcl/hcl/printer/nodes.go
generated
vendored
Normal file
789
vendor/github.com/hashicorp/hcl/hcl/printer/nodes.go
generated
vendored
Normal file
|
@ -0,0 +1,789 @@
|
||||||
|
package printer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/hashicorp/hcl/hcl/ast"
|
||||||
|
"github.com/hashicorp/hcl/hcl/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
blank = byte(' ')
|
||||||
|
newline = byte('\n')
|
||||||
|
tab = byte('\t')
|
||||||
|
infinity = 1 << 30 // offset or line
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
unindent = []byte("\uE123") // in the private use space
|
||||||
|
)
|
||||||
|
|
||||||
|
type printer struct {
|
||||||
|
cfg Config
|
||||||
|
prev token.Pos
|
||||||
|
|
||||||
|
comments []*ast.CommentGroup // may be nil, contains all comments
|
||||||
|
standaloneComments []*ast.CommentGroup // contains all standalone comments (not assigned to any node)
|
||||||
|
|
||||||
|
enableTrace bool
|
||||||
|
indentTrace int
|
||||||
|
}
|
||||||
|
|
||||||
|
type ByPosition []*ast.CommentGroup
|
||||||
|
|
||||||
|
func (b ByPosition) Len() int { return len(b) }
|
||||||
|
func (b ByPosition) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
|
||||||
|
func (b ByPosition) Less(i, j int) bool { return b[i].Pos().Before(b[j].Pos()) }
|
||||||
|
|
||||||
|
// collectComments comments all standalone comments which are not lead or line
|
||||||
|
// comment
|
||||||
|
func (p *printer) collectComments(node ast.Node) {
|
||||||
|
// first collect all comments. This is already stored in
|
||||||
|
// ast.File.(comments)
|
||||||
|
ast.Walk(node, func(nn ast.Node) (ast.Node, bool) {
|
||||||
|
switch t := nn.(type) {
|
||||||
|
case *ast.File:
|
||||||
|
p.comments = t.Comments
|
||||||
|
return nn, false
|
||||||
|
}
|
||||||
|
return nn, true
|
||||||
|
})
|
||||||
|
|
||||||
|
standaloneComments := make(map[token.Pos]*ast.CommentGroup, 0)
|
||||||
|
for _, c := range p.comments {
|
||||||
|
standaloneComments[c.Pos()] = c
|
||||||
|
}
|
||||||
|
|
||||||
|
// next remove all lead and line comments from the overall comment map.
|
||||||
|
// This will give us comments which are standalone, comments which are not
|
||||||
|
// assigned to any kind of node.
|
||||||
|
ast.Walk(node, func(nn ast.Node) (ast.Node, bool) {
|
||||||
|
switch t := nn.(type) {
|
||||||
|
case *ast.LiteralType:
|
||||||
|
if t.LeadComment != nil {
|
||||||
|
for _, comment := range t.LeadComment.List {
|
||||||
|
if _, ok := standaloneComments[comment.Pos()]; ok {
|
||||||
|
delete(standaloneComments, comment.Pos())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.LineComment != nil {
|
||||||
|
for _, comment := range t.LineComment.List {
|
||||||
|
if _, ok := standaloneComments[comment.Pos()]; ok {
|
||||||
|
delete(standaloneComments, comment.Pos())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case *ast.ObjectItem:
|
||||||
|
if t.LeadComment != nil {
|
||||||
|
for _, comment := range t.LeadComment.List {
|
||||||
|
if _, ok := standaloneComments[comment.Pos()]; ok {
|
||||||
|
delete(standaloneComments, comment.Pos())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.LineComment != nil {
|
||||||
|
for _, comment := range t.LineComment.List {
|
||||||
|
if _, ok := standaloneComments[comment.Pos()]; ok {
|
||||||
|
delete(standaloneComments, comment.Pos())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nn, true
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, c := range standaloneComments {
|
||||||
|
p.standaloneComments = append(p.standaloneComments, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(ByPosition(p.standaloneComments))
|
||||||
|
}
|
||||||
|
|
||||||
|
// output prints creates b printable HCL output and returns it.
|
||||||
|
func (p *printer) output(n interface{}) []byte {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
switch t := n.(type) {
|
||||||
|
case *ast.File:
|
||||||
|
// File doesn't trace so we add the tracing here
|
||||||
|
defer un(trace(p, "File"))
|
||||||
|
return p.output(t.Node)
|
||||||
|
case *ast.ObjectList:
|
||||||
|
defer un(trace(p, "ObjectList"))
|
||||||
|
|
||||||
|
var index int
|
||||||
|
for {
|
||||||
|
// Determine the location of the next actual non-comment
|
||||||
|
// item. If we're at the end, the next item is at "infinity"
|
||||||
|
var nextItem token.Pos
|
||||||
|
if index != len(t.Items) {
|
||||||
|
nextItem = t.Items[index].Pos()
|
||||||
|
} else {
|
||||||
|
nextItem = token.Pos{Offset: infinity, Line: infinity}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go through the standalone comments in the file and print out
|
||||||
|
// the comments that we should be for this object item.
|
||||||
|
for _, c := range p.standaloneComments {
|
||||||
|
// Go through all the comments in the group. The group
|
||||||
|
// should be printed together, not separated by double newlines.
|
||||||
|
printed := false
|
||||||
|
newlinePrinted := false
|
||||||
|
for _, comment := range c.List {
|
||||||
|
// We only care about comments after the previous item
|
||||||
|
// we've printed so that comments are printed in the
|
||||||
|
// correct locations (between two objects for example).
|
||||||
|
// And before the next item.
|
||||||
|
if comment.Pos().After(p.prev) && comment.Pos().Before(nextItem) {
|
||||||
|
// if we hit the end add newlines so we can print the comment
|
||||||
|
// we don't do this if prev is invalid which means the
|
||||||
|
// beginning of the file since the first comment should
|
||||||
|
// be at the first line.
|
||||||
|
if !newlinePrinted && p.prev.IsValid() && index == len(t.Items) {
|
||||||
|
buf.Write([]byte{newline, newline})
|
||||||
|
newlinePrinted = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the actual comment.
|
||||||
|
buf.WriteString(comment.Text)
|
||||||
|
buf.WriteByte(newline)
|
||||||
|
|
||||||
|
// Set printed to true to note that we printed something
|
||||||
|
printed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're not at the last item, write a new line so
|
||||||
|
// that there is a newline separating this comment from
|
||||||
|
// the next object.
|
||||||
|
if printed && index != len(t.Items) {
|
||||||
|
buf.WriteByte(newline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if index == len(t.Items) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Write(p.output(t.Items[index]))
|
||||||
|
if index != len(t.Items)-1 {
|
||||||
|
// Always write a newline to separate us from the next item
|
||||||
|
buf.WriteByte(newline)
|
||||||
|
|
||||||
|
// Need to determine if we're going to separate the next item
|
||||||
|
// with a blank line. The logic here is simple, though there
|
||||||
|
// are a few conditions:
|
||||||
|
//
|
||||||
|
// 1. The next object is more than one line away anyways,
|
||||||
|
// so we need an empty line.
|
||||||
|
//
|
||||||
|
// 2. The next object is not a "single line" object, so
|
||||||
|
// we need an empty line.
|
||||||
|
//
|
||||||
|
// 3. This current object is not a single line object,
|
||||||
|
// so we need an empty line.
|
||||||
|
current := t.Items[index]
|
||||||
|
next := t.Items[index+1]
|
||||||
|
if next.Pos().Line != t.Items[index].Pos().Line+1 ||
|
||||||
|
!p.isSingleLineObject(next) ||
|
||||||
|
!p.isSingleLineObject(current) {
|
||||||
|
buf.WriteByte(newline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
case *ast.ObjectKey:
|
||||||
|
buf.WriteString(t.Token.Text)
|
||||||
|
case *ast.ObjectItem:
|
||||||
|
p.prev = t.Pos()
|
||||||
|
buf.Write(p.objectItem(t))
|
||||||
|
case *ast.LiteralType:
|
||||||
|
buf.Write(p.literalType(t))
|
||||||
|
case *ast.ListType:
|
||||||
|
buf.Write(p.list(t))
|
||||||
|
case *ast.ObjectType:
|
||||||
|
buf.Write(p.objectType(t))
|
||||||
|
default:
|
||||||
|
fmt.Printf(" unknown type: %T\n", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *printer) literalType(lit *ast.LiteralType) []byte {
|
||||||
|
result := []byte(lit.Token.Text)
|
||||||
|
switch lit.Token.Type {
|
||||||
|
case token.HEREDOC:
|
||||||
|
// Clear the trailing newline from heredocs
|
||||||
|
if result[len(result)-1] == '\n' {
|
||||||
|
result = result[:len(result)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Poison lines 2+ so that we don't indent them
|
||||||
|
result = p.heredocIndent(result)
|
||||||
|
case token.STRING:
|
||||||
|
// If this is a multiline string, poison lines 2+ so we don't
|
||||||
|
// indent them.
|
||||||
|
if bytes.IndexRune(result, '\n') >= 0 {
|
||||||
|
result = p.heredocIndent(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// objectItem returns the printable HCL form of an object item. An object type
|
||||||
|
// starts with one/multiple keys and has a value. The value might be of any
|
||||||
|
// type.
|
||||||
|
func (p *printer) objectItem(o *ast.ObjectItem) []byte {
|
||||||
|
defer un(trace(p, fmt.Sprintf("ObjectItem: %s", o.Keys[0].Token.Text)))
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
if o.LeadComment != nil {
|
||||||
|
for _, comment := range o.LeadComment.List {
|
||||||
|
buf.WriteString(comment.Text)
|
||||||
|
buf.WriteByte(newline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If key and val are on different lines, treat line comments like lead comments.
|
||||||
|
if o.LineComment != nil && o.Val.Pos().Line != o.Keys[0].Pos().Line {
|
||||||
|
for _, comment := range o.LineComment.List {
|
||||||
|
buf.WriteString(comment.Text)
|
||||||
|
buf.WriteByte(newline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, k := range o.Keys {
|
||||||
|
buf.WriteString(k.Token.Text)
|
||||||
|
buf.WriteByte(blank)
|
||||||
|
|
||||||
|
// reach end of key
|
||||||
|
if o.Assign.IsValid() && i == len(o.Keys)-1 && len(o.Keys) == 1 {
|
||||||
|
buf.WriteString("=")
|
||||||
|
buf.WriteByte(blank)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Write(p.output(o.Val))
|
||||||
|
|
||||||
|
if o.LineComment != nil && o.Val.Pos().Line == o.Keys[0].Pos().Line {
|
||||||
|
buf.WriteByte(blank)
|
||||||
|
for _, comment := range o.LineComment.List {
|
||||||
|
buf.WriteString(comment.Text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// objectType returns the printable HCL form of an object type. An object type
|
||||||
|
// begins with a brace and ends with a brace.
|
||||||
|
func (p *printer) objectType(o *ast.ObjectType) []byte {
|
||||||
|
defer un(trace(p, "ObjectType"))
|
||||||
|
var buf bytes.Buffer
|
||||||
|
buf.WriteString("{")
|
||||||
|
|
||||||
|
var index int
|
||||||
|
var nextItem token.Pos
|
||||||
|
var commented, newlinePrinted bool
|
||||||
|
for {
|
||||||
|
// Determine the location of the next actual non-comment
|
||||||
|
// item. If we're at the end, the next item is the closing brace
|
||||||
|
if index != len(o.List.Items) {
|
||||||
|
nextItem = o.List.Items[index].Pos()
|
||||||
|
} else {
|
||||||
|
nextItem = o.Rbrace
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go through the standalone comments in the file and print out
|
||||||
|
// the comments that we should be for this object item.
|
||||||
|
for _, c := range p.standaloneComments {
|
||||||
|
printed := false
|
||||||
|
var lastCommentPos token.Pos
|
||||||
|
for _, comment := range c.List {
|
||||||
|
// We only care about comments after the previous item
|
||||||
|
// we've printed so that comments are printed in the
|
||||||
|
// correct locations (between two objects for example).
|
||||||
|
// And before the next item.
|
||||||
|
if comment.Pos().After(p.prev) && comment.Pos().Before(nextItem) {
|
||||||
|
// If there are standalone comments and the initial newline has not
|
||||||
|
// been printed yet, do it now.
|
||||||
|
if !newlinePrinted {
|
||||||
|
newlinePrinted = true
|
||||||
|
buf.WriteByte(newline)
|
||||||
|
}
|
||||||
|
|
||||||
|
// add newline if it's between other printed nodes
|
||||||
|
if index > 0 {
|
||||||
|
commented = true
|
||||||
|
buf.WriteByte(newline)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store this position
|
||||||
|
lastCommentPos = comment.Pos()
|
||||||
|
|
||||||
|
// output the comment itself
|
||||||
|
buf.Write(p.indent(p.heredocIndent([]byte(comment.Text))))
|
||||||
|
|
||||||
|
// Set printed to true to note that we printed something
|
||||||
|
printed = true
|
||||||
|
|
||||||
|
/*
|
||||||
|
if index != len(o.List.Items) {
|
||||||
|
buf.WriteByte(newline) // do not print on the end
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stuff to do if we had comments
|
||||||
|
if printed {
|
||||||
|
// Always write a newline
|
||||||
|
buf.WriteByte(newline)
|
||||||
|
|
||||||
|
// If there is another item in the object and our comment
|
||||||
|
// didn't hug it directly, then make sure there is a blank
|
||||||
|
// line separating them.
|
||||||
|
if nextItem != o.Rbrace && nextItem.Line != lastCommentPos.Line+1 {
|
||||||
|
buf.WriteByte(newline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if index == len(o.List.Items) {
|
||||||
|
p.prev = o.Rbrace
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point we are sure that it's not a totally empty block: print
|
||||||
|
// the initial newline if it hasn't been printed yet by the previous
|
||||||
|
// block about standalone comments.
|
||||||
|
if !newlinePrinted {
|
||||||
|
buf.WriteByte(newline)
|
||||||
|
newlinePrinted = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if we have adjacent one liner items. If yes we'll going to align
|
||||||
|
// the comments.
|
||||||
|
var aligned []*ast.ObjectItem
|
||||||
|
for _, item := range o.List.Items[index:] {
|
||||||
|
// we don't group one line lists
|
||||||
|
if len(o.List.Items) == 1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// one means a oneliner with out any lead comment
|
||||||
|
// two means a oneliner with lead comment
|
||||||
|
// anything else might be something else
|
||||||
|
cur := lines(string(p.objectItem(item)))
|
||||||
|
if cur > 2 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
curPos := item.Pos()
|
||||||
|
|
||||||
|
nextPos := token.Pos{}
|
||||||
|
if index != len(o.List.Items)-1 {
|
||||||
|
nextPos = o.List.Items[index+1].Pos()
|
||||||
|
}
|
||||||
|
|
||||||
|
prevPos := token.Pos{}
|
||||||
|
if index != 0 {
|
||||||
|
prevPos = o.List.Items[index-1].Pos()
|
||||||
|
}
|
||||||
|
|
||||||
|
// fmt.Println("DEBUG ----------------")
|
||||||
|
// fmt.Printf("prev = %+v prevPos: %s\n", prev, prevPos)
|
||||||
|
// fmt.Printf("cur = %+v curPos: %s\n", cur, curPos)
|
||||||
|
// fmt.Printf("next = %+v nextPos: %s\n", next, nextPos)
|
||||||
|
|
||||||
|
if curPos.Line+1 == nextPos.Line {
|
||||||
|
aligned = append(aligned, item)
|
||||||
|
index++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if curPos.Line-1 == prevPos.Line {
|
||||||
|
aligned = append(aligned, item)
|
||||||
|
index++
|
||||||
|
|
||||||
|
// finish if we have a new line or comment next. This happens
|
||||||
|
// if the next item is not adjacent
|
||||||
|
if curPos.Line+1 != nextPos.Line {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// put newlines if the items are between other non aligned items.
|
||||||
|
// newlines are also added if there is a standalone comment already, so
|
||||||
|
// check it too
|
||||||
|
if !commented && index != len(aligned) {
|
||||||
|
buf.WriteByte(newline)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(aligned) >= 1 {
|
||||||
|
p.prev = aligned[len(aligned)-1].Pos()
|
||||||
|
|
||||||
|
items := p.alignedItems(aligned)
|
||||||
|
buf.Write(p.indent(items))
|
||||||
|
} else {
|
||||||
|
p.prev = o.List.Items[index].Pos()
|
||||||
|
|
||||||
|
buf.Write(p.indent(p.objectItem(o.List.Items[index])))
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteByte(newline)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteString("}")
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *printer) alignedItems(items []*ast.ObjectItem) []byte {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// find the longest key and value length, needed for alignment
|
||||||
|
var longestKeyLen int // longest key length
|
||||||
|
var longestValLen int // longest value length
|
||||||
|
for _, item := range items {
|
||||||
|
key := len(item.Keys[0].Token.Text)
|
||||||
|
val := len(p.output(item.Val))
|
||||||
|
|
||||||
|
if key > longestKeyLen {
|
||||||
|
longestKeyLen = key
|
||||||
|
}
|
||||||
|
|
||||||
|
if val > longestValLen {
|
||||||
|
longestValLen = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, item := range items {
|
||||||
|
if item.LeadComment != nil {
|
||||||
|
for _, comment := range item.LeadComment.List {
|
||||||
|
buf.WriteString(comment.Text)
|
||||||
|
buf.WriteByte(newline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, k := range item.Keys {
|
||||||
|
keyLen := len(k.Token.Text)
|
||||||
|
buf.WriteString(k.Token.Text)
|
||||||
|
for i := 0; i < longestKeyLen-keyLen+1; i++ {
|
||||||
|
buf.WriteByte(blank)
|
||||||
|
}
|
||||||
|
|
||||||
|
// reach end of key
|
||||||
|
if i == len(item.Keys)-1 && len(item.Keys) == 1 {
|
||||||
|
buf.WriteString("=")
|
||||||
|
buf.WriteByte(blank)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val := p.output(item.Val)
|
||||||
|
valLen := len(val)
|
||||||
|
buf.Write(val)
|
||||||
|
|
||||||
|
if item.Val.Pos().Line == item.Keys[0].Pos().Line && item.LineComment != nil {
|
||||||
|
for i := 0; i < longestValLen-valLen+1; i++ {
|
||||||
|
buf.WriteByte(blank)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, comment := range item.LineComment.List {
|
||||||
|
buf.WriteString(comment.Text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// do not print for the last item
|
||||||
|
if i != len(items)-1 {
|
||||||
|
buf.WriteByte(newline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// list returns the printable HCL form of an list type.
|
||||||
|
func (p *printer) list(l *ast.ListType) []byte {
|
||||||
|
if p.isSingleLineList(l) {
|
||||||
|
return p.singleLineList(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
buf.WriteString("[")
|
||||||
|
buf.WriteByte(newline)
|
||||||
|
|
||||||
|
var longestLine int
|
||||||
|
for _, item := range l.List {
|
||||||
|
// for now we assume that the list only contains literal types
|
||||||
|
if lit, ok := item.(*ast.LiteralType); ok {
|
||||||
|
lineLen := len(lit.Token.Text)
|
||||||
|
if lineLen > longestLine {
|
||||||
|
longestLine = lineLen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
haveEmptyLine := false
|
||||||
|
for i, item := range l.List {
|
||||||
|
// If we have a lead comment, then we want to write that first
|
||||||
|
leadComment := false
|
||||||
|
if lit, ok := item.(*ast.LiteralType); ok && lit.LeadComment != nil {
|
||||||
|
leadComment = true
|
||||||
|
|
||||||
|
// Ensure an empty line before every element with a
|
||||||
|
// lead comment (except the first item in a list).
|
||||||
|
if !haveEmptyLine && i != 0 {
|
||||||
|
buf.WriteByte(newline)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, comment := range lit.LeadComment.List {
|
||||||
|
buf.Write(p.indent([]byte(comment.Text)))
|
||||||
|
buf.WriteByte(newline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// also indent each line
|
||||||
|
val := p.output(item)
|
||||||
|
curLen := len(val)
|
||||||
|
buf.Write(p.indent(val))
|
||||||
|
|
||||||
|
// if this item is a heredoc, then we output the comma on
|
||||||
|
// the next line. This is the only case this happens.
|
||||||
|
comma := []byte{','}
|
||||||
|
if lit, ok := item.(*ast.LiteralType); ok && lit.Token.Type == token.HEREDOC {
|
||||||
|
buf.WriteByte(newline)
|
||||||
|
comma = p.indent(comma)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Write(comma)
|
||||||
|
|
||||||
|
if lit, ok := item.(*ast.LiteralType); ok && lit.LineComment != nil {
|
||||||
|
// if the next item doesn't have any comments, do not align
|
||||||
|
buf.WriteByte(blank) // align one space
|
||||||
|
for i := 0; i < longestLine-curLen; i++ {
|
||||||
|
buf.WriteByte(blank)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, comment := range lit.LineComment.List {
|
||||||
|
buf.WriteString(comment.Text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteByte(newline)
|
||||||
|
|
||||||
|
// Ensure an empty line after every element with a
|
||||||
|
// lead comment (except the first item in a list).
|
||||||
|
haveEmptyLine = leadComment && i != len(l.List)-1
|
||||||
|
if haveEmptyLine {
|
||||||
|
buf.WriteByte(newline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteString("]")
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// isSingleLineList returns true if:
|
||||||
|
// * they were previously formatted entirely on one line
|
||||||
|
// * they consist entirely of literals
|
||||||
|
// * there are either no heredoc strings or the list has exactly one element
|
||||||
|
// * there are no line comments
|
||||||
|
func (printer) isSingleLineList(l *ast.ListType) bool {
|
||||||
|
for _, item := range l.List {
|
||||||
|
if item.Pos().Line != l.Lbrack.Line {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
lit, ok := item.(*ast.LiteralType)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if lit.Token.Type == token.HEREDOC && len(l.List) != 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if lit.LineComment != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// singleLineList prints a simple single line list.
|
||||||
|
// For a definition of "simple", see isSingleLineList above.
|
||||||
|
func (p *printer) singleLineList(l *ast.ListType) []byte {
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
|
||||||
|
buf.WriteString("[")
|
||||||
|
for i, item := range l.List {
|
||||||
|
if i != 0 {
|
||||||
|
buf.WriteString(", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output the item itself
|
||||||
|
buf.Write(p.output(item))
|
||||||
|
|
||||||
|
// The heredoc marker needs to be at the end of line.
|
||||||
|
if lit, ok := item.(*ast.LiteralType); ok && lit.Token.Type == token.HEREDOC {
|
||||||
|
buf.WriteByte(newline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteString("]")
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// indent indents the lines of the given buffer for each non-empty line
|
||||||
|
func (p *printer) indent(buf []byte) []byte {
|
||||||
|
var prefix []byte
|
||||||
|
if p.cfg.SpacesWidth != 0 {
|
||||||
|
for i := 0; i < p.cfg.SpacesWidth; i++ {
|
||||||
|
prefix = append(prefix, blank)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
prefix = []byte{tab}
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []byte
|
||||||
|
bol := true
|
||||||
|
for _, c := range buf {
|
||||||
|
if bol && c != '\n' {
|
||||||
|
res = append(res, prefix...)
|
||||||
|
}
|
||||||
|
|
||||||
|
res = append(res, c)
|
||||||
|
bol = c == '\n'
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// unindent removes all the indentation from the tombstoned lines
|
||||||
|
func (p *printer) unindent(buf []byte) []byte {
|
||||||
|
var res []byte
|
||||||
|
for i := 0; i < len(buf); i++ {
|
||||||
|
skip := len(buf)-i <= len(unindent)
|
||||||
|
if !skip {
|
||||||
|
skip = !bytes.Equal(unindent, buf[i:i+len(unindent)])
|
||||||
|
}
|
||||||
|
if skip {
|
||||||
|
res = append(res, buf[i])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have a marker. we have to backtrace here and clean out
|
||||||
|
// any whitespace ahead of our tombstone up to a \n
|
||||||
|
for j := len(res) - 1; j >= 0; j-- {
|
||||||
|
if res[j] == '\n' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
res = res[:j]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip the entire unindent marker
|
||||||
|
i += len(unindent) - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// heredocIndent marks all the 2nd and further lines as unindentable
|
||||||
|
func (p *printer) heredocIndent(buf []byte) []byte {
|
||||||
|
var res []byte
|
||||||
|
bol := false
|
||||||
|
for _, c := range buf {
|
||||||
|
if bol && c != '\n' {
|
||||||
|
res = append(res, unindent...)
|
||||||
|
}
|
||||||
|
res = append(res, c)
|
||||||
|
bol = c == '\n'
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// isSingleLineObject tells whether the given object item is a single
|
||||||
|
// line object such as "obj {}".
|
||||||
|
//
|
||||||
|
// A single line object:
|
||||||
|
//
|
||||||
|
// * has no lead comments (hence multi-line)
|
||||||
|
// * has no assignment
|
||||||
|
// * has no values in the stanza (within {})
|
||||||
|
//
|
||||||
|
func (p *printer) isSingleLineObject(val *ast.ObjectItem) bool {
|
||||||
|
// If there is a lead comment, can't be one line
|
||||||
|
if val.LeadComment != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is assignment, we always break by line
|
||||||
|
if val.Assign.IsValid() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it isn't an object type, then its not a single line object
|
||||||
|
ot, ok := val.Val.(*ast.ObjectType)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the object has no items, it is single line!
|
||||||
|
return len(ot.List.Items) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func lines(txt string) int {
|
||||||
|
endline := 1
|
||||||
|
for i := 0; i < len(txt); i++ {
|
||||||
|
if txt[i] == '\n' {
|
||||||
|
endline++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return endline
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Tracing support
|
||||||
|
|
||||||
|
func (p *printer) printTrace(a ...interface{}) {
|
||||||
|
if !p.enableTrace {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const dots = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . "
|
||||||
|
const n = len(dots)
|
||||||
|
i := 2 * p.indentTrace
|
||||||
|
for i > n {
|
||||||
|
fmt.Print(dots)
|
||||||
|
i -= n
|
||||||
|
}
|
||||||
|
// i <= n
|
||||||
|
fmt.Print(dots[0:i])
|
||||||
|
fmt.Println(a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func trace(p *printer, msg string) *printer {
|
||||||
|
p.printTrace(msg, "(")
|
||||||
|
p.indentTrace++
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage pattern: defer un(trace(p, "..."))
|
||||||
|
func un(p *printer) {
|
||||||
|
p.indentTrace--
|
||||||
|
p.printTrace(")")
|
||||||
|
}
|
66
vendor/github.com/hashicorp/hcl/hcl/printer/printer.go
generated
vendored
Normal file
66
vendor/github.com/hashicorp/hcl/hcl/printer/printer.go
generated
vendored
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
// Package printer implements printing of AST nodes to HCL format.
|
||||||
|
package printer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"text/tabwriter"
|
||||||
|
|
||||||
|
"github.com/hashicorp/hcl/hcl/ast"
|
||||||
|
"github.com/hashicorp/hcl/hcl/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
var DefaultConfig = Config{
|
||||||
|
SpacesWidth: 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Config node controls the output of Fprint.
|
||||||
|
type Config struct {
|
||||||
|
SpacesWidth int // if set, it will use spaces instead of tabs for alignment
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) Fprint(output io.Writer, node ast.Node) error {
|
||||||
|
p := &printer{
|
||||||
|
cfg: *c,
|
||||||
|
comments: make([]*ast.CommentGroup, 0),
|
||||||
|
standaloneComments: make([]*ast.CommentGroup, 0),
|
||||||
|
// enableTrace: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
p.collectComments(node)
|
||||||
|
|
||||||
|
if _, err := output.Write(p.unindent(p.output(node))); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// flush tabwriter, if any
|
||||||
|
var err error
|
||||||
|
if tw, _ := output.(*tabwriter.Writer); tw != nil {
|
||||||
|
err = tw.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprint "pretty-prints" an HCL node to output
|
||||||
|
// It calls Config.Fprint with default settings.
|
||||||
|
func Fprint(output io.Writer, node ast.Node) error {
|
||||||
|
return DefaultConfig.Fprint(output, node)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format formats src HCL and returns the result.
|
||||||
|
func Format(src []byte) ([]byte, error) {
|
||||||
|
node, err := parser.Parse(src)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := DefaultConfig.Fprint(&buf, node); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add trailing newline to result
|
||||||
|
buf.WriteString("\n")
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
652
vendor/github.com/hashicorp/hcl/hcl/scanner/scanner.go
generated
vendored
Normal file
652
vendor/github.com/hashicorp/hcl/hcl/scanner/scanner.go
generated
vendored
Normal file
|
@ -0,0 +1,652 @@
|
||||||
|
// Package scanner implements a scanner for HCL (HashiCorp Configuration
|
||||||
|
// Language) source text.
|
||||||
|
package scanner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/hashicorp/hcl/hcl/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
// eof represents a marker rune for the end of the reader.
|
||||||
|
const eof = rune(0)
|
||||||
|
|
||||||
|
// Scanner defines a lexical scanner
|
||||||
|
type Scanner struct {
|
||||||
|
buf *bytes.Buffer // Source buffer for advancing and scanning
|
||||||
|
src []byte // Source buffer for immutable access
|
||||||
|
|
||||||
|
// Source Position
|
||||||
|
srcPos token.Pos // current position
|
||||||
|
prevPos token.Pos // previous position, used for peek() method
|
||||||
|
|
||||||
|
lastCharLen int // length of last character in bytes
|
||||||
|
lastLineLen int // length of last line in characters (for correct column reporting)
|
||||||
|
|
||||||
|
tokStart int // token text start position
|
||||||
|
tokEnd int // token text end position
|
||||||
|
|
||||||
|
// Error is called for each error encountered. If no Error
|
||||||
|
// function is set, the error is reported to os.Stderr.
|
||||||
|
Error func(pos token.Pos, msg string)
|
||||||
|
|
||||||
|
// ErrorCount is incremented by one for each error encountered.
|
||||||
|
ErrorCount int
|
||||||
|
|
||||||
|
// tokPos is the start position of most recently scanned token; set by
|
||||||
|
// Scan. The Filename field is always left untouched by the Scanner. If
|
||||||
|
// an error is reported (via Error) and Position is invalid, the scanner is
|
||||||
|
// not inside a token.
|
||||||
|
tokPos token.Pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates and initializes a new instance of Scanner using src as
|
||||||
|
// its source content.
|
||||||
|
func New(src []byte) *Scanner {
|
||||||
|
// even though we accept a src, we read from a io.Reader compatible type
|
||||||
|
// (*bytes.Buffer). So in the future we might easily change it to streaming
|
||||||
|
// read.
|
||||||
|
b := bytes.NewBuffer(src)
|
||||||
|
s := &Scanner{
|
||||||
|
buf: b,
|
||||||
|
src: src,
|
||||||
|
}
|
||||||
|
|
||||||
|
// srcPosition always starts with 1
|
||||||
|
s.srcPos.Line = 1
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// next reads the next rune from the bufferred reader. Returns the rune(0) if
|
||||||
|
// an error occurs (or io.EOF is returned).
|
||||||
|
func (s *Scanner) next() rune {
|
||||||
|
ch, size, err := s.buf.ReadRune()
|
||||||
|
if err != nil {
|
||||||
|
// advance for error reporting
|
||||||
|
s.srcPos.Column++
|
||||||
|
s.srcPos.Offset += size
|
||||||
|
s.lastCharLen = size
|
||||||
|
return eof
|
||||||
|
}
|
||||||
|
|
||||||
|
// remember last position
|
||||||
|
s.prevPos = s.srcPos
|
||||||
|
|
||||||
|
s.srcPos.Column++
|
||||||
|
s.lastCharLen = size
|
||||||
|
s.srcPos.Offset += size
|
||||||
|
|
||||||
|
if ch == utf8.RuneError && size == 1 {
|
||||||
|
s.err("illegal UTF-8 encoding")
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch == '\n' {
|
||||||
|
s.srcPos.Line++
|
||||||
|
s.lastLineLen = s.srcPos.Column
|
||||||
|
s.srcPos.Column = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch == '\x00' {
|
||||||
|
s.err("unexpected null character (0x00)")
|
||||||
|
return eof
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch == '\uE123' {
|
||||||
|
s.err("unicode code point U+E123 reserved for internal use")
|
||||||
|
return utf8.RuneError
|
||||||
|
}
|
||||||
|
|
||||||
|
// debug
|
||||||
|
// fmt.Printf("ch: %q, offset:column: %d:%d\n", ch, s.srcPos.Offset, s.srcPos.Column)
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
// unread unreads the previous read Rune and updates the source position
|
||||||
|
func (s *Scanner) unread() {
|
||||||
|
if err := s.buf.UnreadRune(); err != nil {
|
||||||
|
panic(err) // this is user fault, we should catch it
|
||||||
|
}
|
||||||
|
s.srcPos = s.prevPos // put back last position
|
||||||
|
}
|
||||||
|
|
||||||
|
// peek returns the next rune without advancing the reader.
|
||||||
|
func (s *Scanner) peek() rune {
|
||||||
|
peek, _, err := s.buf.ReadRune()
|
||||||
|
if err != nil {
|
||||||
|
return eof
|
||||||
|
}
|
||||||
|
|
||||||
|
s.buf.UnreadRune()
|
||||||
|
return peek
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan scans the next token and returns the token.
|
||||||
|
func (s *Scanner) Scan() token.Token {
|
||||||
|
ch := s.next()
|
||||||
|
|
||||||
|
// skip white space
|
||||||
|
for isWhitespace(ch) {
|
||||||
|
ch = s.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
var tok token.Type
|
||||||
|
|
||||||
|
// token text markings
|
||||||
|
s.tokStart = s.srcPos.Offset - s.lastCharLen
|
||||||
|
|
||||||
|
// token position, initial next() is moving the offset by one(size of rune
|
||||||
|
// actually), though we are interested with the starting point
|
||||||
|
s.tokPos.Offset = s.srcPos.Offset - s.lastCharLen
|
||||||
|
if s.srcPos.Column > 0 {
|
||||||
|
// common case: last character was not a '\n'
|
||||||
|
s.tokPos.Line = s.srcPos.Line
|
||||||
|
s.tokPos.Column = s.srcPos.Column
|
||||||
|
} else {
|
||||||
|
// last character was a '\n'
|
||||||
|
// (we cannot be at the beginning of the source
|
||||||
|
// since we have called next() at least once)
|
||||||
|
s.tokPos.Line = s.srcPos.Line - 1
|
||||||
|
s.tokPos.Column = s.lastLineLen
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case isLetter(ch):
|
||||||
|
tok = token.IDENT
|
||||||
|
lit := s.scanIdentifier()
|
||||||
|
if lit == "true" || lit == "false" {
|
||||||
|
tok = token.BOOL
|
||||||
|
}
|
||||||
|
case isDecimal(ch):
|
||||||
|
tok = s.scanNumber(ch)
|
||||||
|
default:
|
||||||
|
switch ch {
|
||||||
|
case eof:
|
||||||
|
tok = token.EOF
|
||||||
|
case '"':
|
||||||
|
tok = token.STRING
|
||||||
|
s.scanString()
|
||||||
|
case '#', '/':
|
||||||
|
tok = token.COMMENT
|
||||||
|
s.scanComment(ch)
|
||||||
|
case '.':
|
||||||
|
tok = token.PERIOD
|
||||||
|
ch = s.peek()
|
||||||
|
if isDecimal(ch) {
|
||||||
|
tok = token.FLOAT
|
||||||
|
ch = s.scanMantissa(ch)
|
||||||
|
ch = s.scanExponent(ch)
|
||||||
|
}
|
||||||
|
case '<':
|
||||||
|
tok = token.HEREDOC
|
||||||
|
s.scanHeredoc()
|
||||||
|
case '[':
|
||||||
|
tok = token.LBRACK
|
||||||
|
case ']':
|
||||||
|
tok = token.RBRACK
|
||||||
|
case '{':
|
||||||
|
tok = token.LBRACE
|
||||||
|
case '}':
|
||||||
|
tok = token.RBRACE
|
||||||
|
case ',':
|
||||||
|
tok = token.COMMA
|
||||||
|
case '=':
|
||||||
|
tok = token.ASSIGN
|
||||||
|
case '+':
|
||||||
|
tok = token.ADD
|
||||||
|
case '-':
|
||||||
|
if isDecimal(s.peek()) {
|
||||||
|
ch := s.next()
|
||||||
|
tok = s.scanNumber(ch)
|
||||||
|
} else {
|
||||||
|
tok = token.SUB
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
s.err("illegal char")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finish token ending
|
||||||
|
s.tokEnd = s.srcPos.Offset
|
||||||
|
|
||||||
|
// create token literal
|
||||||
|
var tokenText string
|
||||||
|
if s.tokStart >= 0 {
|
||||||
|
tokenText = string(s.src[s.tokStart:s.tokEnd])
|
||||||
|
}
|
||||||
|
s.tokStart = s.tokEnd // ensure idempotency of tokenText() call
|
||||||
|
|
||||||
|
return token.Token{
|
||||||
|
Type: tok,
|
||||||
|
Pos: s.tokPos,
|
||||||
|
Text: tokenText,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scanner) scanComment(ch rune) {
|
||||||
|
// single line comments
|
||||||
|
if ch == '#' || (ch == '/' && s.peek() != '*') {
|
||||||
|
if ch == '/' && s.peek() != '/' {
|
||||||
|
s.err("expected '/' for comment")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ch = s.next()
|
||||||
|
for ch != '\n' && ch >= 0 && ch != eof {
|
||||||
|
ch = s.next()
|
||||||
|
}
|
||||||
|
if ch != eof && ch >= 0 {
|
||||||
|
s.unread()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// be sure we get the character after /* This allows us to find comment's
|
||||||
|
// that are not erminated
|
||||||
|
if ch == '/' {
|
||||||
|
s.next()
|
||||||
|
ch = s.next() // read character after "/*"
|
||||||
|
}
|
||||||
|
|
||||||
|
// look for /* - style comments
|
||||||
|
for {
|
||||||
|
if ch < 0 || ch == eof {
|
||||||
|
s.err("comment not terminated")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
ch0 := ch
|
||||||
|
ch = s.next()
|
||||||
|
if ch0 == '*' && ch == '/' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanNumber scans a HCL number definition starting with the given rune
|
||||||
|
func (s *Scanner) scanNumber(ch rune) token.Type {
|
||||||
|
if ch == '0' {
|
||||||
|
// check for hexadecimal, octal or float
|
||||||
|
ch = s.next()
|
||||||
|
if ch == 'x' || ch == 'X' {
|
||||||
|
// hexadecimal
|
||||||
|
ch = s.next()
|
||||||
|
found := false
|
||||||
|
for isHexadecimal(ch) {
|
||||||
|
ch = s.next()
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
s.err("illegal hexadecimal number")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch != eof {
|
||||||
|
s.unread()
|
||||||
|
}
|
||||||
|
|
||||||
|
return token.NUMBER
|
||||||
|
}
|
||||||
|
|
||||||
|
// now it's either something like: 0421(octal) or 0.1231(float)
|
||||||
|
illegalOctal := false
|
||||||
|
for isDecimal(ch) {
|
||||||
|
ch = s.next()
|
||||||
|
if ch == '8' || ch == '9' {
|
||||||
|
// this is just a possibility. For example 0159 is illegal, but
|
||||||
|
// 0159.23 is valid. So we mark a possible illegal octal. If
|
||||||
|
// the next character is not a period, we'll print the error.
|
||||||
|
illegalOctal = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch == 'e' || ch == 'E' {
|
||||||
|
ch = s.scanExponent(ch)
|
||||||
|
return token.FLOAT
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch == '.' {
|
||||||
|
ch = s.scanFraction(ch)
|
||||||
|
|
||||||
|
if ch == 'e' || ch == 'E' {
|
||||||
|
ch = s.next()
|
||||||
|
ch = s.scanExponent(ch)
|
||||||
|
}
|
||||||
|
return token.FLOAT
|
||||||
|
}
|
||||||
|
|
||||||
|
if illegalOctal {
|
||||||
|
s.err("illegal octal number")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch != eof {
|
||||||
|
s.unread()
|
||||||
|
}
|
||||||
|
return token.NUMBER
|
||||||
|
}
|
||||||
|
|
||||||
|
s.scanMantissa(ch)
|
||||||
|
ch = s.next() // seek forward
|
||||||
|
if ch == 'e' || ch == 'E' {
|
||||||
|
ch = s.scanExponent(ch)
|
||||||
|
return token.FLOAT
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch == '.' {
|
||||||
|
ch = s.scanFraction(ch)
|
||||||
|
if ch == 'e' || ch == 'E' {
|
||||||
|
ch = s.next()
|
||||||
|
ch = s.scanExponent(ch)
|
||||||
|
}
|
||||||
|
return token.FLOAT
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch != eof {
|
||||||
|
s.unread()
|
||||||
|
}
|
||||||
|
return token.NUMBER
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanMantissa scans the mantissa beginning from the rune. It returns the next
|
||||||
|
// non decimal rune. It's used to determine wheter it's a fraction or exponent.
|
||||||
|
func (s *Scanner) scanMantissa(ch rune) rune {
|
||||||
|
scanned := false
|
||||||
|
for isDecimal(ch) {
|
||||||
|
ch = s.next()
|
||||||
|
scanned = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if scanned && ch != eof {
|
||||||
|
s.unread()
|
||||||
|
}
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanFraction scans the fraction after the '.' rune
|
||||||
|
func (s *Scanner) scanFraction(ch rune) rune {
|
||||||
|
if ch == '.' {
|
||||||
|
ch = s.peek() // we peek just to see if we can move forward
|
||||||
|
ch = s.scanMantissa(ch)
|
||||||
|
}
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanExponent scans the remaining parts of an exponent after the 'e' or 'E'
|
||||||
|
// rune.
|
||||||
|
func (s *Scanner) scanExponent(ch rune) rune {
|
||||||
|
if ch == 'e' || ch == 'E' {
|
||||||
|
ch = s.next()
|
||||||
|
if ch == '-' || ch == '+' {
|
||||||
|
ch = s.next()
|
||||||
|
}
|
||||||
|
ch = s.scanMantissa(ch)
|
||||||
|
}
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanHeredoc scans a heredoc string
|
||||||
|
func (s *Scanner) scanHeredoc() {
|
||||||
|
// Scan the second '<' in example: '<<EOF'
|
||||||
|
if s.next() != '<' {
|
||||||
|
s.err("heredoc expected second '<', didn't see it")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the original offset so we can read just the heredoc ident
|
||||||
|
offs := s.srcPos.Offset
|
||||||
|
|
||||||
|
// Scan the identifier
|
||||||
|
ch := s.next()
|
||||||
|
|
||||||
|
// Indented heredoc syntax
|
||||||
|
if ch == '-' {
|
||||||
|
ch = s.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
for isLetter(ch) || isDigit(ch) {
|
||||||
|
ch = s.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we reached an EOF then that is not good
|
||||||
|
if ch == eof {
|
||||||
|
s.err("heredoc not terminated")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore the '\r' in Windows line endings
|
||||||
|
if ch == '\r' {
|
||||||
|
if s.peek() == '\n' {
|
||||||
|
ch = s.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we didn't reach a newline then that is also not good
|
||||||
|
if ch != '\n' {
|
||||||
|
s.err("invalid characters in heredoc anchor")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the identifier
|
||||||
|
identBytes := s.src[offs : s.srcPos.Offset-s.lastCharLen]
|
||||||
|
if len(identBytes) == 0 || (len(identBytes) == 1 && identBytes[0] == '-') {
|
||||||
|
s.err("zero-length heredoc anchor")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var identRegexp *regexp.Regexp
|
||||||
|
if identBytes[0] == '-' {
|
||||||
|
identRegexp = regexp.MustCompile(fmt.Sprintf(`^[[:space:]]*%s\r*\z`, identBytes[1:]))
|
||||||
|
} else {
|
||||||
|
identRegexp = regexp.MustCompile(fmt.Sprintf(`^[[:space:]]*%s\r*\z`, identBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the actual string value
|
||||||
|
lineStart := s.srcPos.Offset
|
||||||
|
for {
|
||||||
|
ch := s.next()
|
||||||
|
|
||||||
|
// Special newline handling.
|
||||||
|
if ch == '\n' {
|
||||||
|
// Math is fast, so we first compare the byte counts to see if we have a chance
|
||||||
|
// of seeing the same identifier - if the length is less than the number of bytes
|
||||||
|
// in the identifier, this cannot be a valid terminator.
|
||||||
|
lineBytesLen := s.srcPos.Offset - s.lastCharLen - lineStart
|
||||||
|
if lineBytesLen >= len(identBytes) && identRegexp.Match(s.src[lineStart:s.srcPos.Offset-s.lastCharLen]) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not an anchor match, record the start of a new line
|
||||||
|
lineStart = s.srcPos.Offset
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch == eof {
|
||||||
|
s.err("heredoc not terminated")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanString scans a quoted string
|
||||||
|
func (s *Scanner) scanString() {
|
||||||
|
braces := 0
|
||||||
|
for {
|
||||||
|
// '"' opening already consumed
|
||||||
|
// read character after quote
|
||||||
|
ch := s.next()
|
||||||
|
|
||||||
|
if (ch == '\n' && braces == 0) || ch < 0 || ch == eof {
|
||||||
|
s.err("literal not terminated")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch == '"' && braces == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're going into a ${} then we can ignore quotes for awhile
|
||||||
|
if braces == 0 && ch == '$' && s.peek() == '{' {
|
||||||
|
braces++
|
||||||
|
s.next()
|
||||||
|
} else if braces > 0 && ch == '{' {
|
||||||
|
braces++
|
||||||
|
}
|
||||||
|
if braces > 0 && ch == '}' {
|
||||||
|
braces--
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch == '\\' {
|
||||||
|
s.scanEscape()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanEscape scans an escape sequence
|
||||||
|
func (s *Scanner) scanEscape() rune {
|
||||||
|
// http://en.cppreference.com/w/cpp/language/escape
|
||||||
|
ch := s.next() // read character after '/'
|
||||||
|
switch ch {
|
||||||
|
case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', '"':
|
||||||
|
// nothing to do
|
||||||
|
case '0', '1', '2', '3', '4', '5', '6', '7':
|
||||||
|
// octal notation
|
||||||
|
ch = s.scanDigits(ch, 8, 3)
|
||||||
|
case 'x':
|
||||||
|
// hexademical notation
|
||||||
|
ch = s.scanDigits(s.next(), 16, 2)
|
||||||
|
case 'u':
|
||||||
|
// universal character name
|
||||||
|
ch = s.scanDigits(s.next(), 16, 4)
|
||||||
|
case 'U':
|
||||||
|
// universal character name
|
||||||
|
ch = s.scanDigits(s.next(), 16, 8)
|
||||||
|
default:
|
||||||
|
s.err("illegal char escape")
|
||||||
|
}
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanDigits scans a rune with the given base for n times. For example an
|
||||||
|
// octal notation \184 would yield in scanDigits(ch, 8, 3)
|
||||||
|
func (s *Scanner) scanDigits(ch rune, base, n int) rune {
|
||||||
|
start := n
|
||||||
|
for n > 0 && digitVal(ch) < base {
|
||||||
|
ch = s.next()
|
||||||
|
if ch == eof {
|
||||||
|
// If we see an EOF, we halt any more scanning of digits
|
||||||
|
// immediately.
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
n--
|
||||||
|
}
|
||||||
|
if n > 0 {
|
||||||
|
s.err("illegal char escape")
|
||||||
|
}
|
||||||
|
|
||||||
|
if n != start && ch != eof {
|
||||||
|
// we scanned all digits, put the last non digit char back,
|
||||||
|
// only if we read anything at all
|
||||||
|
s.unread()
|
||||||
|
}
|
||||||
|
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanIdentifier scans an identifier and returns the literal string
|
||||||
|
func (s *Scanner) scanIdentifier() string {
|
||||||
|
offs := s.srcPos.Offset - s.lastCharLen
|
||||||
|
ch := s.next()
|
||||||
|
for isLetter(ch) || isDigit(ch) || ch == '-' || ch == '.' {
|
||||||
|
ch = s.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch != eof {
|
||||||
|
s.unread() // we got identifier, put back latest char
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(s.src[offs:s.srcPos.Offset])
|
||||||
|
}
|
||||||
|
|
||||||
|
// recentPosition returns the position of the character immediately after the
|
||||||
|
// character or token returned by the last call to Scan.
|
||||||
|
func (s *Scanner) recentPosition() (pos token.Pos) {
|
||||||
|
pos.Offset = s.srcPos.Offset - s.lastCharLen
|
||||||
|
switch {
|
||||||
|
case s.srcPos.Column > 0:
|
||||||
|
// common case: last character was not a '\n'
|
||||||
|
pos.Line = s.srcPos.Line
|
||||||
|
pos.Column = s.srcPos.Column
|
||||||
|
case s.lastLineLen > 0:
|
||||||
|
// last character was a '\n'
|
||||||
|
// (we cannot be at the beginning of the source
|
||||||
|
// since we have called next() at least once)
|
||||||
|
pos.Line = s.srcPos.Line - 1
|
||||||
|
pos.Column = s.lastLineLen
|
||||||
|
default:
|
||||||
|
// at the beginning of the source
|
||||||
|
pos.Line = 1
|
||||||
|
pos.Column = 1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// err prints the error of any scanning to s.Error function. If the function is
|
||||||
|
// not defined, by default it prints them to os.Stderr
|
||||||
|
func (s *Scanner) err(msg string) {
|
||||||
|
s.ErrorCount++
|
||||||
|
pos := s.recentPosition()
|
||||||
|
|
||||||
|
if s.Error != nil {
|
||||||
|
s.Error(pos, msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "%s: %s\n", pos, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isHexadecimal returns true if the given rune is a letter
|
||||||
|
func isLetter(ch rune) bool {
|
||||||
|
return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= 0x80 && unicode.IsLetter(ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isDigit returns true if the given rune is a decimal digit
|
||||||
|
func isDigit(ch rune) bool {
|
||||||
|
return '0' <= ch && ch <= '9' || ch >= 0x80 && unicode.IsDigit(ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isDecimal returns true if the given rune is a decimal number
|
||||||
|
func isDecimal(ch rune) bool {
|
||||||
|
return '0' <= ch && ch <= '9'
|
||||||
|
}
|
||||||
|
|
||||||
|
// isHexadecimal returns true if the given rune is an hexadecimal number
|
||||||
|
func isHexadecimal(ch rune) bool {
|
||||||
|
return '0' <= ch && ch <= '9' || 'a' <= ch && ch <= 'f' || 'A' <= ch && ch <= 'F'
|
||||||
|
}
|
||||||
|
|
||||||
|
// isWhitespace returns true if the rune is a space, tab, newline or carriage return
|
||||||
|
func isWhitespace(ch rune) bool {
|
||||||
|
return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'
|
||||||
|
}
|
||||||
|
|
||||||
|
// digitVal returns the integer value of a given octal,decimal or hexadecimal rune
|
||||||
|
func digitVal(ch rune) int {
|
||||||
|
switch {
|
||||||
|
case '0' <= ch && ch <= '9':
|
||||||
|
return int(ch - '0')
|
||||||
|
case 'a' <= ch && ch <= 'f':
|
||||||
|
return int(ch - 'a' + 10)
|
||||||
|
case 'A' <= ch && ch <= 'F':
|
||||||
|
return int(ch - 'A' + 10)
|
||||||
|
}
|
||||||
|
return 16 // larger than any legal digit val
|
||||||
|
}
|
241
vendor/github.com/hashicorp/hcl/hcl/strconv/quote.go
generated
vendored
Normal file
241
vendor/github.com/hashicorp/hcl/hcl/strconv/quote.go
generated
vendored
Normal file
|
@ -0,0 +1,241 @@
|
||||||
|
package strconv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrSyntax indicates that a value does not have the right syntax for the target type.
|
||||||
|
var ErrSyntax = errors.New("invalid syntax")
|
||||||
|
|
||||||
|
// Unquote interprets s as a single-quoted, double-quoted,
|
||||||
|
// or backquoted Go string literal, returning the string value
|
||||||
|
// that s quotes. (If s is single-quoted, it would be a Go
|
||||||
|
// character literal; Unquote returns the corresponding
|
||||||
|
// one-character string.)
|
||||||
|
func Unquote(s string) (t string, err error) {
|
||||||
|
n := len(s)
|
||||||
|
if n < 2 {
|
||||||
|
return "", ErrSyntax
|
||||||
|
}
|
||||||
|
quote := s[0]
|
||||||
|
if quote != s[n-1] {
|
||||||
|
return "", ErrSyntax
|
||||||
|
}
|
||||||
|
s = s[1 : n-1]
|
||||||
|
|
||||||
|
if quote != '"' {
|
||||||
|
return "", ErrSyntax
|
||||||
|
}
|
||||||
|
if !contains(s, '$') && !contains(s, '{') && contains(s, '\n') {
|
||||||
|
return "", ErrSyntax
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is it trivial? Avoid allocation.
|
||||||
|
if !contains(s, '\\') && !contains(s, quote) && !contains(s, '$') {
|
||||||
|
switch quote {
|
||||||
|
case '"':
|
||||||
|
return s, nil
|
||||||
|
case '\'':
|
||||||
|
r, size := utf8.DecodeRuneInString(s)
|
||||||
|
if size == len(s) && (r != utf8.RuneError || size != 1) {
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var runeTmp [utf8.UTFMax]byte
|
||||||
|
buf := make([]byte, 0, 3*len(s)/2) // Try to avoid more allocations.
|
||||||
|
for len(s) > 0 {
|
||||||
|
// If we're starting a '${}' then let it through un-unquoted.
|
||||||
|
// Specifically: we don't unquote any characters within the `${}`
|
||||||
|
// section.
|
||||||
|
if s[0] == '$' && len(s) > 1 && s[1] == '{' {
|
||||||
|
buf = append(buf, '$', '{')
|
||||||
|
s = s[2:]
|
||||||
|
|
||||||
|
// Continue reading until we find the closing brace, copying as-is
|
||||||
|
braces := 1
|
||||||
|
for len(s) > 0 && braces > 0 {
|
||||||
|
r, size := utf8.DecodeRuneInString(s)
|
||||||
|
if r == utf8.RuneError {
|
||||||
|
return "", ErrSyntax
|
||||||
|
}
|
||||||
|
|
||||||
|
s = s[size:]
|
||||||
|
|
||||||
|
n := utf8.EncodeRune(runeTmp[:], r)
|
||||||
|
buf = append(buf, runeTmp[:n]...)
|
||||||
|
|
||||||
|
switch r {
|
||||||
|
case '{':
|
||||||
|
braces++
|
||||||
|
case '}':
|
||||||
|
braces--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if braces != 0 {
|
||||||
|
return "", ErrSyntax
|
||||||
|
}
|
||||||
|
if len(s) == 0 {
|
||||||
|
// If there's no string left, we're done!
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
// If there's more left, we need to pop back up to the top of the loop
|
||||||
|
// in case there's another interpolation in this string.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if s[0] == '\n' {
|
||||||
|
return "", ErrSyntax
|
||||||
|
}
|
||||||
|
|
||||||
|
c, multibyte, ss, err := unquoteChar(s, quote)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
s = ss
|
||||||
|
if c < utf8.RuneSelf || !multibyte {
|
||||||
|
buf = append(buf, byte(c))
|
||||||
|
} else {
|
||||||
|
n := utf8.EncodeRune(runeTmp[:], c)
|
||||||
|
buf = append(buf, runeTmp[:n]...)
|
||||||
|
}
|
||||||
|
if quote == '\'' && len(s) != 0 {
|
||||||
|
// single-quoted must be single character
|
||||||
|
return "", ErrSyntax
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// contains reports whether the string contains the byte c.
|
||||||
|
func contains(s string, c byte) bool {
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
if s[i] == c {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func unhex(b byte) (v rune, ok bool) {
|
||||||
|
c := rune(b)
|
||||||
|
switch {
|
||||||
|
case '0' <= c && c <= '9':
|
||||||
|
return c - '0', true
|
||||||
|
case 'a' <= c && c <= 'f':
|
||||||
|
return c - 'a' + 10, true
|
||||||
|
case 'A' <= c && c <= 'F':
|
||||||
|
return c - 'A' + 10, true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func unquoteChar(s string, quote byte) (value rune, multibyte bool, tail string, err error) {
|
||||||
|
// easy cases
|
||||||
|
switch c := s[0]; {
|
||||||
|
case c == quote && (quote == '\'' || quote == '"'):
|
||||||
|
err = ErrSyntax
|
||||||
|
return
|
||||||
|
case c >= utf8.RuneSelf:
|
||||||
|
r, size := utf8.DecodeRuneInString(s)
|
||||||
|
return r, true, s[size:], nil
|
||||||
|
case c != '\\':
|
||||||
|
return rune(s[0]), false, s[1:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// hard case: c is backslash
|
||||||
|
if len(s) <= 1 {
|
||||||
|
err = ErrSyntax
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c := s[1]
|
||||||
|
s = s[2:]
|
||||||
|
|
||||||
|
switch c {
|
||||||
|
case 'a':
|
||||||
|
value = '\a'
|
||||||
|
case 'b':
|
||||||
|
value = '\b'
|
||||||
|
case 'f':
|
||||||
|
value = '\f'
|
||||||
|
case 'n':
|
||||||
|
value = '\n'
|
||||||
|
case 'r':
|
||||||
|
value = '\r'
|
||||||
|
case 't':
|
||||||
|
value = '\t'
|
||||||
|
case 'v':
|
||||||
|
value = '\v'
|
||||||
|
case 'x', 'u', 'U':
|
||||||
|
n := 0
|
||||||
|
switch c {
|
||||||
|
case 'x':
|
||||||
|
n = 2
|
||||||
|
case 'u':
|
||||||
|
n = 4
|
||||||
|
case 'U':
|
||||||
|
n = 8
|
||||||
|
}
|
||||||
|
var v rune
|
||||||
|
if len(s) < n {
|
||||||
|
err = ErrSyntax
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for j := 0; j < n; j++ {
|
||||||
|
x, ok := unhex(s[j])
|
||||||
|
if !ok {
|
||||||
|
err = ErrSyntax
|
||||||
|
return
|
||||||
|
}
|
||||||
|
v = v<<4 | x
|
||||||
|
}
|
||||||
|
s = s[n:]
|
||||||
|
if c == 'x' {
|
||||||
|
// single-byte string, possibly not UTF-8
|
||||||
|
value = v
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if v > utf8.MaxRune {
|
||||||
|
err = ErrSyntax
|
||||||
|
return
|
||||||
|
}
|
||||||
|
value = v
|
||||||
|
multibyte = true
|
||||||
|
case '0', '1', '2', '3', '4', '5', '6', '7':
|
||||||
|
v := rune(c) - '0'
|
||||||
|
if len(s) < 2 {
|
||||||
|
err = ErrSyntax
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for j := 0; j < 2; j++ { // one digit already; two more
|
||||||
|
x := rune(s[j]) - '0'
|
||||||
|
if x < 0 || x > 7 {
|
||||||
|
err = ErrSyntax
|
||||||
|
return
|
||||||
|
}
|
||||||
|
v = (v << 3) | x
|
||||||
|
}
|
||||||
|
s = s[2:]
|
||||||
|
if v > 255 {
|
||||||
|
err = ErrSyntax
|
||||||
|
return
|
||||||
|
}
|
||||||
|
value = v
|
||||||
|
case '\\':
|
||||||
|
value = '\\'
|
||||||
|
case '\'', '"':
|
||||||
|
if c != quote {
|
||||||
|
err = ErrSyntax
|
||||||
|
return
|
||||||
|
}
|
||||||
|
value = rune(c)
|
||||||
|
default:
|
||||||
|
err = ErrSyntax
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tail = s
|
||||||
|
return
|
||||||
|
}
|
46
vendor/github.com/hashicorp/hcl/hcl/token/position.go
generated
vendored
Normal file
46
vendor/github.com/hashicorp/hcl/hcl/token/position.go
generated
vendored
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
package token
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Pos describes an arbitrary source position
|
||||||
|
// including the file, line, and column location.
|
||||||
|
// A Position is valid if the line number is > 0.
|
||||||
|
type Pos struct {
|
||||||
|
Filename string // filename, if any
|
||||||
|
Offset int // offset, starting at 0
|
||||||
|
Line int // line number, starting at 1
|
||||||
|
Column int // column number, starting at 1 (character count)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValid returns true if the position is valid.
|
||||||
|
func (p *Pos) IsValid() bool { return p.Line > 0 }
|
||||||
|
|
||||||
|
// String returns a string in one of several forms:
|
||||||
|
//
|
||||||
|
// file:line:column valid position with file name
|
||||||
|
// line:column valid position without file name
|
||||||
|
// file invalid position with file name
|
||||||
|
// - invalid position without file name
|
||||||
|
func (p Pos) String() string {
|
||||||
|
s := p.Filename
|
||||||
|
if p.IsValid() {
|
||||||
|
if s != "" {
|
||||||
|
s += ":"
|
||||||
|
}
|
||||||
|
s += fmt.Sprintf("%d:%d", p.Line, p.Column)
|
||||||
|
}
|
||||||
|
if s == "" {
|
||||||
|
s = "-"
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Before reports whether the position p is before u.
|
||||||
|
func (p Pos) Before(u Pos) bool {
|
||||||
|
return u.Offset > p.Offset || u.Line > p.Line
|
||||||
|
}
|
||||||
|
|
||||||
|
// After reports whether the position p is after u.
|
||||||
|
func (p Pos) After(u Pos) bool {
|
||||||
|
return u.Offset < p.Offset || u.Line < p.Line
|
||||||
|
}
|
219
vendor/github.com/hashicorp/hcl/hcl/token/token.go
generated
vendored
Normal file
219
vendor/github.com/hashicorp/hcl/hcl/token/token.go
generated
vendored
Normal file
|
@ -0,0 +1,219 @@
|
||||||
|
// Package token defines constants representing the lexical tokens for HCL
|
||||||
|
// (HashiCorp Configuration Language)
|
||||||
|
package token
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
hclstrconv "github.com/hashicorp/hcl/hcl/strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Token defines a single HCL token which can be obtained via the Scanner
|
||||||
|
type Token struct {
|
||||||
|
Type Type
|
||||||
|
Pos Pos
|
||||||
|
Text string
|
||||||
|
JSON bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type is the set of lexical tokens of the HCL (HashiCorp Configuration Language)
|
||||||
|
type Type int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Special tokens
|
||||||
|
ILLEGAL Type = iota
|
||||||
|
EOF
|
||||||
|
COMMENT
|
||||||
|
|
||||||
|
identifier_beg
|
||||||
|
IDENT // literals
|
||||||
|
literal_beg
|
||||||
|
NUMBER // 12345
|
||||||
|
FLOAT // 123.45
|
||||||
|
BOOL // true,false
|
||||||
|
STRING // "abc"
|
||||||
|
HEREDOC // <<FOO\nbar\nFOO
|
||||||
|
literal_end
|
||||||
|
identifier_end
|
||||||
|
|
||||||
|
operator_beg
|
||||||
|
LBRACK // [
|
||||||
|
LBRACE // {
|
||||||
|
COMMA // ,
|
||||||
|
PERIOD // .
|
||||||
|
|
||||||
|
RBRACK // ]
|
||||||
|
RBRACE // }
|
||||||
|
|
||||||
|
ASSIGN // =
|
||||||
|
ADD // +
|
||||||
|
SUB // -
|
||||||
|
operator_end
|
||||||
|
)
|
||||||
|
|
||||||
|
var tokens = [...]string{
|
||||||
|
ILLEGAL: "ILLEGAL",
|
||||||
|
|
||||||
|
EOF: "EOF",
|
||||||
|
COMMENT: "COMMENT",
|
||||||
|
|
||||||
|
IDENT: "IDENT",
|
||||||
|
NUMBER: "NUMBER",
|
||||||
|
FLOAT: "FLOAT",
|
||||||
|
BOOL: "BOOL",
|
||||||
|
STRING: "STRING",
|
||||||
|
|
||||||
|
LBRACK: "LBRACK",
|
||||||
|
LBRACE: "LBRACE",
|
||||||
|
COMMA: "COMMA",
|
||||||
|
PERIOD: "PERIOD",
|
||||||
|
HEREDOC: "HEREDOC",
|
||||||
|
|
||||||
|
RBRACK: "RBRACK",
|
||||||
|
RBRACE: "RBRACE",
|
||||||
|
|
||||||
|
ASSIGN: "ASSIGN",
|
||||||
|
ADD: "ADD",
|
||||||
|
SUB: "SUB",
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string corresponding to the token tok.
|
||||||
|
func (t Type) String() string {
|
||||||
|
s := ""
|
||||||
|
if 0 <= t && t < Type(len(tokens)) {
|
||||||
|
s = tokens[t]
|
||||||
|
}
|
||||||
|
if s == "" {
|
||||||
|
s = "token(" + strconv.Itoa(int(t)) + ")"
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsIdentifier returns true for tokens corresponding to identifiers and basic
|
||||||
|
// type literals; it returns false otherwise.
|
||||||
|
func (t Type) IsIdentifier() bool { return identifier_beg < t && t < identifier_end }
|
||||||
|
|
||||||
|
// IsLiteral returns true for tokens corresponding to basic type literals; it
|
||||||
|
// returns false otherwise.
|
||||||
|
func (t Type) IsLiteral() bool { return literal_beg < t && t < literal_end }
|
||||||
|
|
||||||
|
// IsOperator returns true for tokens corresponding to operators and
|
||||||
|
// delimiters; it returns false otherwise.
|
||||||
|
func (t Type) IsOperator() bool { return operator_beg < t && t < operator_end }
|
||||||
|
|
||||||
|
// String returns the token's literal text. Note that this is only
|
||||||
|
// applicable for certain token types, such as token.IDENT,
|
||||||
|
// token.STRING, etc..
|
||||||
|
func (t Token) String() string {
|
||||||
|
return fmt.Sprintf("%s %s %s", t.Pos.String(), t.Type.String(), t.Text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value returns the properly typed value for this token. The type of
|
||||||
|
// the returned interface{} is guaranteed based on the Type field.
|
||||||
|
//
|
||||||
|
// This can only be called for literal types. If it is called for any other
|
||||||
|
// type, this will panic.
|
||||||
|
func (t Token) Value() interface{} {
|
||||||
|
switch t.Type {
|
||||||
|
case BOOL:
|
||||||
|
if t.Text == "true" {
|
||||||
|
return true
|
||||||
|
} else if t.Text == "false" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
panic("unknown bool value: " + t.Text)
|
||||||
|
case FLOAT:
|
||||||
|
v, err := strconv.ParseFloat(t.Text, 64)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return float64(v)
|
||||||
|
case NUMBER:
|
||||||
|
v, err := strconv.ParseInt(t.Text, 0, 64)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int64(v)
|
||||||
|
case IDENT:
|
||||||
|
return t.Text
|
||||||
|
case HEREDOC:
|
||||||
|
return unindentHeredoc(t.Text)
|
||||||
|
case STRING:
|
||||||
|
// Determine the Unquote method to use. If it came from JSON,
|
||||||
|
// then we need to use the built-in unquote since we have to
|
||||||
|
// escape interpolations there.
|
||||||
|
f := hclstrconv.Unquote
|
||||||
|
if t.JSON {
|
||||||
|
f = strconv.Unquote
|
||||||
|
}
|
||||||
|
|
||||||
|
// This case occurs if json null is used
|
||||||
|
if t.Text == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := f(t.Text)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("unquote %s err: %s", t.Text, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return v
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unimplemented Value for type: %s", t.Type))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// unindentHeredoc returns the string content of a HEREDOC if it is started with <<
|
||||||
|
// and the content of a HEREDOC with the hanging indent removed if it is started with
|
||||||
|
// a <<-, and the terminating line is at least as indented as the least indented line.
|
||||||
|
func unindentHeredoc(heredoc string) string {
|
||||||
|
// We need to find the end of the marker
|
||||||
|
idx := strings.IndexByte(heredoc, '\n')
|
||||||
|
if idx == -1 {
|
||||||
|
panic("heredoc doesn't contain newline")
|
||||||
|
}
|
||||||
|
|
||||||
|
unindent := heredoc[2] == '-'
|
||||||
|
|
||||||
|
// We can optimize if the heredoc isn't marked for indentation
|
||||||
|
if !unindent {
|
||||||
|
return string(heredoc[idx+1 : len(heredoc)-idx+1])
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to unindent each line based on the indentation level of the marker
|
||||||
|
lines := strings.Split(string(heredoc[idx+1:len(heredoc)-idx+2]), "\n")
|
||||||
|
whitespacePrefix := lines[len(lines)-1]
|
||||||
|
|
||||||
|
isIndented := true
|
||||||
|
for _, v := range lines {
|
||||||
|
if strings.HasPrefix(v, whitespacePrefix) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
isIndented = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// If all lines are not at least as indented as the terminating mark, return the
|
||||||
|
// heredoc as is, but trim the leading space from the marker on the final line.
|
||||||
|
if !isIndented {
|
||||||
|
return strings.TrimRight(string(heredoc[idx+1:len(heredoc)-idx+1]), " \t")
|
||||||
|
}
|
||||||
|
|
||||||
|
unindentedLines := make([]string, len(lines))
|
||||||
|
for k, v := range lines {
|
||||||
|
if k == len(lines)-1 {
|
||||||
|
unindentedLines[k] = ""
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
unindentedLines[k] = strings.TrimPrefix(v, whitespacePrefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(unindentedLines, "\n")
|
||||||
|
}
|
117
vendor/github.com/hashicorp/hcl/json/parser/flatten.go
generated
vendored
Normal file
117
vendor/github.com/hashicorp/hcl/json/parser/flatten.go
generated
vendored
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
package parser
|
||||||
|
|
||||||
|
import "github.com/hashicorp/hcl/hcl/ast"
|
||||||
|
|
||||||
|
// flattenObjects takes an AST node, walks it, and flattens
|
||||||
|
func flattenObjects(node ast.Node) {
|
||||||
|
ast.Walk(node, func(n ast.Node) (ast.Node, bool) {
|
||||||
|
// We only care about lists, because this is what we modify
|
||||||
|
list, ok := n.(*ast.ObjectList)
|
||||||
|
if !ok {
|
||||||
|
return n, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rebuild the item list
|
||||||
|
items := make([]*ast.ObjectItem, 0, len(list.Items))
|
||||||
|
frontier := make([]*ast.ObjectItem, len(list.Items))
|
||||||
|
copy(frontier, list.Items)
|
||||||
|
for len(frontier) > 0 {
|
||||||
|
// Pop the current item
|
||||||
|
n := len(frontier)
|
||||||
|
item := frontier[n-1]
|
||||||
|
frontier = frontier[:n-1]
|
||||||
|
|
||||||
|
switch v := item.Val.(type) {
|
||||||
|
case *ast.ObjectType:
|
||||||
|
items, frontier = flattenObjectType(v, item, items, frontier)
|
||||||
|
case *ast.ListType:
|
||||||
|
items, frontier = flattenListType(v, item, items, frontier)
|
||||||
|
default:
|
||||||
|
items = append(items, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse the list since the frontier model runs things backwards
|
||||||
|
for i := len(items)/2 - 1; i >= 0; i-- {
|
||||||
|
opp := len(items) - 1 - i
|
||||||
|
items[i], items[opp] = items[opp], items[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Done! Set the original items
|
||||||
|
list.Items = items
|
||||||
|
return n, true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func flattenListType(
|
||||||
|
ot *ast.ListType,
|
||||||
|
item *ast.ObjectItem,
|
||||||
|
items []*ast.ObjectItem,
|
||||||
|
frontier []*ast.ObjectItem) ([]*ast.ObjectItem, []*ast.ObjectItem) {
|
||||||
|
// If the list is empty, keep the original list
|
||||||
|
if len(ot.List) == 0 {
|
||||||
|
items = append(items, item)
|
||||||
|
return items, frontier
|
||||||
|
}
|
||||||
|
|
||||||
|
// All the elements of this object must also be objects!
|
||||||
|
for _, subitem := range ot.List {
|
||||||
|
if _, ok := subitem.(*ast.ObjectType); !ok {
|
||||||
|
items = append(items, item)
|
||||||
|
return items, frontier
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Great! We have a match go through all the items and flatten
|
||||||
|
for _, elem := range ot.List {
|
||||||
|
// Add it to the frontier so that we can recurse
|
||||||
|
frontier = append(frontier, &ast.ObjectItem{
|
||||||
|
Keys: item.Keys,
|
||||||
|
Assign: item.Assign,
|
||||||
|
Val: elem,
|
||||||
|
LeadComment: item.LeadComment,
|
||||||
|
LineComment: item.LineComment,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return items, frontier
|
||||||
|
}
|
||||||
|
|
||||||
|
func flattenObjectType(
|
||||||
|
ot *ast.ObjectType,
|
||||||
|
item *ast.ObjectItem,
|
||||||
|
items []*ast.ObjectItem,
|
||||||
|
frontier []*ast.ObjectItem) ([]*ast.ObjectItem, []*ast.ObjectItem) {
|
||||||
|
// If the list has no items we do not have to flatten anything
|
||||||
|
if ot.List.Items == nil {
|
||||||
|
items = append(items, item)
|
||||||
|
return items, frontier
|
||||||
|
}
|
||||||
|
|
||||||
|
// All the elements of this object must also be objects!
|
||||||
|
for _, subitem := range ot.List.Items {
|
||||||
|
if _, ok := subitem.Val.(*ast.ObjectType); !ok {
|
||||||
|
items = append(items, item)
|
||||||
|
return items, frontier
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Great! We have a match go through all the items and flatten
|
||||||
|
for _, subitem := range ot.List.Items {
|
||||||
|
// Copy the new key
|
||||||
|
keys := make([]*ast.ObjectKey, len(item.Keys)+len(subitem.Keys))
|
||||||
|
copy(keys, item.Keys)
|
||||||
|
copy(keys[len(item.Keys):], subitem.Keys)
|
||||||
|
|
||||||
|
// Add it to the frontier so that we can recurse
|
||||||
|
frontier = append(frontier, &ast.ObjectItem{
|
||||||
|
Keys: keys,
|
||||||
|
Assign: item.Assign,
|
||||||
|
Val: subitem.Val,
|
||||||
|
LeadComment: item.LeadComment,
|
||||||
|
LineComment: item.LineComment,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return items, frontier
|
||||||
|
}
|
313
vendor/github.com/hashicorp/hcl/json/parser/parser.go
generated
vendored
Normal file
313
vendor/github.com/hashicorp/hcl/json/parser/parser.go
generated
vendored
Normal file
|
@ -0,0 +1,313 @@
|
||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/hcl/hcl/ast"
|
||||||
|
hcltoken "github.com/hashicorp/hcl/hcl/token"
|
||||||
|
"github.com/hashicorp/hcl/json/scanner"
|
||||||
|
"github.com/hashicorp/hcl/json/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Parser struct {
|
||||||
|
sc *scanner.Scanner
|
||||||
|
|
||||||
|
// Last read token
|
||||||
|
tok token.Token
|
||||||
|
commaPrev token.Token
|
||||||
|
|
||||||
|
enableTrace bool
|
||||||
|
indent int
|
||||||
|
n int // buffer size (max = 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newParser(src []byte) *Parser {
|
||||||
|
return &Parser{
|
||||||
|
sc: scanner.New(src),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse returns the fully parsed source and returns the abstract syntax tree.
|
||||||
|
func Parse(src []byte) (*ast.File, error) {
|
||||||
|
p := newParser(src)
|
||||||
|
return p.Parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
var errEofToken = errors.New("EOF token found")
|
||||||
|
|
||||||
|
// Parse returns the fully parsed source and returns the abstract syntax tree.
|
||||||
|
func (p *Parser) Parse() (*ast.File, error) {
|
||||||
|
f := &ast.File{}
|
||||||
|
var err, scerr error
|
||||||
|
p.sc.Error = func(pos token.Pos, msg string) {
|
||||||
|
scerr = fmt.Errorf("%s: %s", pos, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The root must be an object in JSON
|
||||||
|
object, err := p.object()
|
||||||
|
if scerr != nil {
|
||||||
|
return nil, scerr
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We make our final node an object list so it is more HCL compatible
|
||||||
|
f.Node = object.List
|
||||||
|
|
||||||
|
// Flatten it, which finds patterns and turns them into more HCL-like
|
||||||
|
// AST trees.
|
||||||
|
flattenObjects(f.Node)
|
||||||
|
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) objectList() (*ast.ObjectList, error) {
|
||||||
|
defer un(trace(p, "ParseObjectList"))
|
||||||
|
node := &ast.ObjectList{}
|
||||||
|
|
||||||
|
for {
|
||||||
|
n, err := p.objectItem()
|
||||||
|
if err == errEofToken {
|
||||||
|
break // we are finished
|
||||||
|
}
|
||||||
|
|
||||||
|
// we don't return a nil node, because might want to use already
|
||||||
|
// collected items.
|
||||||
|
if err != nil {
|
||||||
|
return node, err
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Add(n)
|
||||||
|
|
||||||
|
// Check for a followup comma. If it isn't a comma, then we're done
|
||||||
|
if tok := p.scan(); tok.Type != token.COMMA {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return node, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// objectItem parses a single object item
|
||||||
|
func (p *Parser) objectItem() (*ast.ObjectItem, error) {
|
||||||
|
defer un(trace(p, "ParseObjectItem"))
|
||||||
|
|
||||||
|
keys, err := p.objectKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
o := &ast.ObjectItem{
|
||||||
|
Keys: keys,
|
||||||
|
}
|
||||||
|
|
||||||
|
switch p.tok.Type {
|
||||||
|
case token.COLON:
|
||||||
|
pos := p.tok.Pos
|
||||||
|
o.Assign = hcltoken.Pos{
|
||||||
|
Filename: pos.Filename,
|
||||||
|
Offset: pos.Offset,
|
||||||
|
Line: pos.Line,
|
||||||
|
Column: pos.Column,
|
||||||
|
}
|
||||||
|
|
||||||
|
o.Val, err = p.objectValue()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return o, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// objectKey parses an object key and returns a ObjectKey AST
|
||||||
|
func (p *Parser) objectKey() ([]*ast.ObjectKey, error) {
|
||||||
|
keyCount := 0
|
||||||
|
keys := make([]*ast.ObjectKey, 0)
|
||||||
|
|
||||||
|
for {
|
||||||
|
tok := p.scan()
|
||||||
|
switch tok.Type {
|
||||||
|
case token.EOF:
|
||||||
|
return nil, errEofToken
|
||||||
|
case token.STRING:
|
||||||
|
keyCount++
|
||||||
|
keys = append(keys, &ast.ObjectKey{
|
||||||
|
Token: p.tok.HCLToken(),
|
||||||
|
})
|
||||||
|
case token.COLON:
|
||||||
|
// If we have a zero keycount it means that we never got
|
||||||
|
// an object key, i.e. `{ :`. This is a syntax error.
|
||||||
|
if keyCount == 0 {
|
||||||
|
return nil, fmt.Errorf("expected: STRING got: %s", p.tok.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Done
|
||||||
|
return keys, nil
|
||||||
|
case token.ILLEGAL:
|
||||||
|
return nil, errors.New("illegal")
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("expected: STRING got: %s", p.tok.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// object parses any type of object, such as number, bool, string, object or
|
||||||
|
// list.
|
||||||
|
func (p *Parser) objectValue() (ast.Node, error) {
|
||||||
|
defer un(trace(p, "ParseObjectValue"))
|
||||||
|
tok := p.scan()
|
||||||
|
|
||||||
|
switch tok.Type {
|
||||||
|
case token.NUMBER, token.FLOAT, token.BOOL, token.NULL, token.STRING:
|
||||||
|
return p.literalType()
|
||||||
|
case token.LBRACE:
|
||||||
|
return p.objectType()
|
||||||
|
case token.LBRACK:
|
||||||
|
return p.listType()
|
||||||
|
case token.EOF:
|
||||||
|
return nil, errEofToken
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("Expected object value, got unknown token: %+v", tok)
|
||||||
|
}
|
||||||
|
|
||||||
|
// object parses any type of object, such as number, bool, string, object or
|
||||||
|
// list.
|
||||||
|
func (p *Parser) object() (*ast.ObjectType, error) {
|
||||||
|
defer un(trace(p, "ParseType"))
|
||||||
|
tok := p.scan()
|
||||||
|
|
||||||
|
switch tok.Type {
|
||||||
|
case token.LBRACE:
|
||||||
|
return p.objectType()
|
||||||
|
case token.EOF:
|
||||||
|
return nil, errEofToken
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("Expected object, got unknown token: %+v", tok)
|
||||||
|
}
|
||||||
|
|
||||||
|
// objectType parses an object type and returns a ObjectType AST
|
||||||
|
func (p *Parser) objectType() (*ast.ObjectType, error) {
|
||||||
|
defer un(trace(p, "ParseObjectType"))
|
||||||
|
|
||||||
|
// we assume that the currently scanned token is a LBRACE
|
||||||
|
o := &ast.ObjectType{}
|
||||||
|
|
||||||
|
l, err := p.objectList()
|
||||||
|
|
||||||
|
// if we hit RBRACE, we are good to go (means we parsed all Items), if it's
|
||||||
|
// not a RBRACE, it's an syntax error and we just return it.
|
||||||
|
if err != nil && p.tok.Type != token.RBRACE {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
o.List = l
|
||||||
|
return o, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// listType parses a list type and returns a ListType AST
|
||||||
|
func (p *Parser) listType() (*ast.ListType, error) {
|
||||||
|
defer un(trace(p, "ParseListType"))
|
||||||
|
|
||||||
|
// we assume that the currently scanned token is a LBRACK
|
||||||
|
l := &ast.ListType{}
|
||||||
|
|
||||||
|
for {
|
||||||
|
tok := p.scan()
|
||||||
|
switch tok.Type {
|
||||||
|
case token.NUMBER, token.FLOAT, token.STRING:
|
||||||
|
node, err := p.literalType()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Add(node)
|
||||||
|
case token.COMMA:
|
||||||
|
continue
|
||||||
|
case token.LBRACE:
|
||||||
|
node, err := p.objectType()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Add(node)
|
||||||
|
case token.BOOL:
|
||||||
|
// TODO(arslan) should we support? not supported by HCL yet
|
||||||
|
case token.LBRACK:
|
||||||
|
// TODO(arslan) should we support nested lists? Even though it's
|
||||||
|
// written in README of HCL, it's not a part of the grammar
|
||||||
|
// (not defined in parse.y)
|
||||||
|
case token.RBRACK:
|
||||||
|
// finished
|
||||||
|
return l, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unexpected token while parsing list: %s", tok.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// literalType parses a literal type and returns a LiteralType AST
|
||||||
|
func (p *Parser) literalType() (*ast.LiteralType, error) {
|
||||||
|
defer un(trace(p, "ParseLiteral"))
|
||||||
|
|
||||||
|
return &ast.LiteralType{
|
||||||
|
Token: p.tok.HCLToken(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// scan returns the next token from the underlying scanner. If a token has
|
||||||
|
// been unscanned then read that instead.
|
||||||
|
func (p *Parser) scan() token.Token {
|
||||||
|
// If we have a token on the buffer, then return it.
|
||||||
|
if p.n != 0 {
|
||||||
|
p.n = 0
|
||||||
|
return p.tok
|
||||||
|
}
|
||||||
|
|
||||||
|
p.tok = p.sc.Scan()
|
||||||
|
return p.tok
|
||||||
|
}
|
||||||
|
|
||||||
|
// unscan pushes the previously read token back onto the buffer.
|
||||||
|
func (p *Parser) unscan() {
|
||||||
|
p.n = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Parsing support
|
||||||
|
|
||||||
|
func (p *Parser) printTrace(a ...interface{}) {
|
||||||
|
if !p.enableTrace {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const dots = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . "
|
||||||
|
const n = len(dots)
|
||||||
|
fmt.Printf("%5d:%3d: ", p.tok.Pos.Line, p.tok.Pos.Column)
|
||||||
|
|
||||||
|
i := 2 * p.indent
|
||||||
|
for i > n {
|
||||||
|
fmt.Print(dots)
|
||||||
|
i -= n
|
||||||
|
}
|
||||||
|
// i <= n
|
||||||
|
fmt.Print(dots[0:i])
|
||||||
|
fmt.Println(a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func trace(p *Parser, msg string) *Parser {
|
||||||
|
p.printTrace(msg, "(")
|
||||||
|
p.indent++
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage pattern: defer un(trace(p, "..."))
|
||||||
|
func un(p *Parser) {
|
||||||
|
p.indent--
|
||||||
|
p.printTrace(")")
|
||||||
|
}
|
451
vendor/github.com/hashicorp/hcl/json/scanner/scanner.go
generated
vendored
Normal file
451
vendor/github.com/hashicorp/hcl/json/scanner/scanner.go
generated
vendored
Normal file
|
@ -0,0 +1,451 @@
|
||||||
|
package scanner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/hashicorp/hcl/json/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
// eof represents a marker rune for the end of the reader.
|
||||||
|
const eof = rune(0)
|
||||||
|
|
||||||
|
// Scanner defines a lexical scanner
|
||||||
|
type Scanner struct {
|
||||||
|
buf *bytes.Buffer // Source buffer for advancing and scanning
|
||||||
|
src []byte // Source buffer for immutable access
|
||||||
|
|
||||||
|
// Source Position
|
||||||
|
srcPos token.Pos // current position
|
||||||
|
prevPos token.Pos // previous position, used for peek() method
|
||||||
|
|
||||||
|
lastCharLen int // length of last character in bytes
|
||||||
|
lastLineLen int // length of last line in characters (for correct column reporting)
|
||||||
|
|
||||||
|
tokStart int // token text start position
|
||||||
|
tokEnd int // token text end position
|
||||||
|
|
||||||
|
// Error is called for each error encountered. If no Error
|
||||||
|
// function is set, the error is reported to os.Stderr.
|
||||||
|
Error func(pos token.Pos, msg string)
|
||||||
|
|
||||||
|
// ErrorCount is incremented by one for each error encountered.
|
||||||
|
ErrorCount int
|
||||||
|
|
||||||
|
// tokPos is the start position of most recently scanned token; set by
|
||||||
|
// Scan. The Filename field is always left untouched by the Scanner. If
|
||||||
|
// an error is reported (via Error) and Position is invalid, the scanner is
|
||||||
|
// not inside a token.
|
||||||
|
tokPos token.Pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates and initializes a new instance of Scanner using src as
|
||||||
|
// its source content.
|
||||||
|
func New(src []byte) *Scanner {
|
||||||
|
// even though we accept a src, we read from a io.Reader compatible type
|
||||||
|
// (*bytes.Buffer). So in the future we might easily change it to streaming
|
||||||
|
// read.
|
||||||
|
b := bytes.NewBuffer(src)
|
||||||
|
s := &Scanner{
|
||||||
|
buf: b,
|
||||||
|
src: src,
|
||||||
|
}
|
||||||
|
|
||||||
|
// srcPosition always starts with 1
|
||||||
|
s.srcPos.Line = 1
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// next reads the next rune from the bufferred reader. Returns the rune(0) if
|
||||||
|
// an error occurs (or io.EOF is returned).
|
||||||
|
func (s *Scanner) next() rune {
|
||||||
|
ch, size, err := s.buf.ReadRune()
|
||||||
|
if err != nil {
|
||||||
|
// advance for error reporting
|
||||||
|
s.srcPos.Column++
|
||||||
|
s.srcPos.Offset += size
|
||||||
|
s.lastCharLen = size
|
||||||
|
return eof
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch == utf8.RuneError && size == 1 {
|
||||||
|
s.srcPos.Column++
|
||||||
|
s.srcPos.Offset += size
|
||||||
|
s.lastCharLen = size
|
||||||
|
s.err("illegal UTF-8 encoding")
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
// remember last position
|
||||||
|
s.prevPos = s.srcPos
|
||||||
|
|
||||||
|
s.srcPos.Column++
|
||||||
|
s.lastCharLen = size
|
||||||
|
s.srcPos.Offset += size
|
||||||
|
|
||||||
|
if ch == '\n' {
|
||||||
|
s.srcPos.Line++
|
||||||
|
s.lastLineLen = s.srcPos.Column
|
||||||
|
s.srcPos.Column = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// debug
|
||||||
|
// fmt.Printf("ch: %q, offset:column: %d:%d\n", ch, s.srcPos.Offset, s.srcPos.Column)
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
// unread unreads the previous read Rune and updates the source position
|
||||||
|
func (s *Scanner) unread() {
|
||||||
|
if err := s.buf.UnreadRune(); err != nil {
|
||||||
|
panic(err) // this is user fault, we should catch it
|
||||||
|
}
|
||||||
|
s.srcPos = s.prevPos // put back last position
|
||||||
|
}
|
||||||
|
|
||||||
|
// peek returns the next rune without advancing the reader.
|
||||||
|
func (s *Scanner) peek() rune {
|
||||||
|
peek, _, err := s.buf.ReadRune()
|
||||||
|
if err != nil {
|
||||||
|
return eof
|
||||||
|
}
|
||||||
|
|
||||||
|
s.buf.UnreadRune()
|
||||||
|
return peek
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan scans the next token and returns the token.
|
||||||
|
func (s *Scanner) Scan() token.Token {
|
||||||
|
ch := s.next()
|
||||||
|
|
||||||
|
// skip white space
|
||||||
|
for isWhitespace(ch) {
|
||||||
|
ch = s.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
var tok token.Type
|
||||||
|
|
||||||
|
// token text markings
|
||||||
|
s.tokStart = s.srcPos.Offset - s.lastCharLen
|
||||||
|
|
||||||
|
// token position, initial next() is moving the offset by one(size of rune
|
||||||
|
// actually), though we are interested with the starting point
|
||||||
|
s.tokPos.Offset = s.srcPos.Offset - s.lastCharLen
|
||||||
|
if s.srcPos.Column > 0 {
|
||||||
|
// common case: last character was not a '\n'
|
||||||
|
s.tokPos.Line = s.srcPos.Line
|
||||||
|
s.tokPos.Column = s.srcPos.Column
|
||||||
|
} else {
|
||||||
|
// last character was a '\n'
|
||||||
|
// (we cannot be at the beginning of the source
|
||||||
|
// since we have called next() at least once)
|
||||||
|
s.tokPos.Line = s.srcPos.Line - 1
|
||||||
|
s.tokPos.Column = s.lastLineLen
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case isLetter(ch):
|
||||||
|
lit := s.scanIdentifier()
|
||||||
|
if lit == "true" || lit == "false" {
|
||||||
|
tok = token.BOOL
|
||||||
|
} else if lit == "null" {
|
||||||
|
tok = token.NULL
|
||||||
|
} else {
|
||||||
|
s.err("illegal char")
|
||||||
|
}
|
||||||
|
case isDecimal(ch):
|
||||||
|
tok = s.scanNumber(ch)
|
||||||
|
default:
|
||||||
|
switch ch {
|
||||||
|
case eof:
|
||||||
|
tok = token.EOF
|
||||||
|
case '"':
|
||||||
|
tok = token.STRING
|
||||||
|
s.scanString()
|
||||||
|
case '.':
|
||||||
|
tok = token.PERIOD
|
||||||
|
ch = s.peek()
|
||||||
|
if isDecimal(ch) {
|
||||||
|
tok = token.FLOAT
|
||||||
|
ch = s.scanMantissa(ch)
|
||||||
|
ch = s.scanExponent(ch)
|
||||||
|
}
|
||||||
|
case '[':
|
||||||
|
tok = token.LBRACK
|
||||||
|
case ']':
|
||||||
|
tok = token.RBRACK
|
||||||
|
case '{':
|
||||||
|
tok = token.LBRACE
|
||||||
|
case '}':
|
||||||
|
tok = token.RBRACE
|
||||||
|
case ',':
|
||||||
|
tok = token.COMMA
|
||||||
|
case ':':
|
||||||
|
tok = token.COLON
|
||||||
|
case '-':
|
||||||
|
if isDecimal(s.peek()) {
|
||||||
|
ch := s.next()
|
||||||
|
tok = s.scanNumber(ch)
|
||||||
|
} else {
|
||||||
|
s.err("illegal char")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
s.err("illegal char: " + string(ch))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finish token ending
|
||||||
|
s.tokEnd = s.srcPos.Offset
|
||||||
|
|
||||||
|
// create token literal
|
||||||
|
var tokenText string
|
||||||
|
if s.tokStart >= 0 {
|
||||||
|
tokenText = string(s.src[s.tokStart:s.tokEnd])
|
||||||
|
}
|
||||||
|
s.tokStart = s.tokEnd // ensure idempotency of tokenText() call
|
||||||
|
|
||||||
|
return token.Token{
|
||||||
|
Type: tok,
|
||||||
|
Pos: s.tokPos,
|
||||||
|
Text: tokenText,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanNumber scans a HCL number definition starting with the given rune
|
||||||
|
func (s *Scanner) scanNumber(ch rune) token.Type {
|
||||||
|
zero := ch == '0'
|
||||||
|
pos := s.srcPos
|
||||||
|
|
||||||
|
s.scanMantissa(ch)
|
||||||
|
ch = s.next() // seek forward
|
||||||
|
if ch == 'e' || ch == 'E' {
|
||||||
|
ch = s.scanExponent(ch)
|
||||||
|
return token.FLOAT
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch == '.' {
|
||||||
|
ch = s.scanFraction(ch)
|
||||||
|
if ch == 'e' || ch == 'E' {
|
||||||
|
ch = s.next()
|
||||||
|
ch = s.scanExponent(ch)
|
||||||
|
}
|
||||||
|
return token.FLOAT
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch != eof {
|
||||||
|
s.unread()
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have a larger number and this is zero, error
|
||||||
|
if zero && pos != s.srcPos {
|
||||||
|
s.err("numbers cannot start with 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
return token.NUMBER
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanMantissa scans the mantissa beginning from the rune. It returns the next
|
||||||
|
// non decimal rune. It's used to determine wheter it's a fraction or exponent.
|
||||||
|
func (s *Scanner) scanMantissa(ch rune) rune {
|
||||||
|
scanned := false
|
||||||
|
for isDecimal(ch) {
|
||||||
|
ch = s.next()
|
||||||
|
scanned = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if scanned && ch != eof {
|
||||||
|
s.unread()
|
||||||
|
}
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanFraction scans the fraction after the '.' rune
|
||||||
|
func (s *Scanner) scanFraction(ch rune) rune {
|
||||||
|
if ch == '.' {
|
||||||
|
ch = s.peek() // we peek just to see if we can move forward
|
||||||
|
ch = s.scanMantissa(ch)
|
||||||
|
}
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanExponent scans the remaining parts of an exponent after the 'e' or 'E'
|
||||||
|
// rune.
|
||||||
|
func (s *Scanner) scanExponent(ch rune) rune {
|
||||||
|
if ch == 'e' || ch == 'E' {
|
||||||
|
ch = s.next()
|
||||||
|
if ch == '-' || ch == '+' {
|
||||||
|
ch = s.next()
|
||||||
|
}
|
||||||
|
ch = s.scanMantissa(ch)
|
||||||
|
}
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanString scans a quoted string
|
||||||
|
func (s *Scanner) scanString() {
|
||||||
|
braces := 0
|
||||||
|
for {
|
||||||
|
// '"' opening already consumed
|
||||||
|
// read character after quote
|
||||||
|
ch := s.next()
|
||||||
|
|
||||||
|
if ch == '\n' || ch < 0 || ch == eof {
|
||||||
|
s.err("literal not terminated")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch == '"' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're going into a ${} then we can ignore quotes for awhile
|
||||||
|
if braces == 0 && ch == '$' && s.peek() == '{' {
|
||||||
|
braces++
|
||||||
|
s.next()
|
||||||
|
} else if braces > 0 && ch == '{' {
|
||||||
|
braces++
|
||||||
|
}
|
||||||
|
if braces > 0 && ch == '}' {
|
||||||
|
braces--
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch == '\\' {
|
||||||
|
s.scanEscape()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanEscape scans an escape sequence
|
||||||
|
func (s *Scanner) scanEscape() rune {
|
||||||
|
// http://en.cppreference.com/w/cpp/language/escape
|
||||||
|
ch := s.next() // read character after '/'
|
||||||
|
switch ch {
|
||||||
|
case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', '"':
|
||||||
|
// nothing to do
|
||||||
|
case '0', '1', '2', '3', '4', '5', '6', '7':
|
||||||
|
// octal notation
|
||||||
|
ch = s.scanDigits(ch, 8, 3)
|
||||||
|
case 'x':
|
||||||
|
// hexademical notation
|
||||||
|
ch = s.scanDigits(s.next(), 16, 2)
|
||||||
|
case 'u':
|
||||||
|
// universal character name
|
||||||
|
ch = s.scanDigits(s.next(), 16, 4)
|
||||||
|
case 'U':
|
||||||
|
// universal character name
|
||||||
|
ch = s.scanDigits(s.next(), 16, 8)
|
||||||
|
default:
|
||||||
|
s.err("illegal char escape")
|
||||||
|
}
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanDigits scans a rune with the given base for n times. For example an
|
||||||
|
// octal notation \184 would yield in scanDigits(ch, 8, 3)
|
||||||
|
func (s *Scanner) scanDigits(ch rune, base, n int) rune {
|
||||||
|
for n > 0 && digitVal(ch) < base {
|
||||||
|
ch = s.next()
|
||||||
|
n--
|
||||||
|
}
|
||||||
|
if n > 0 {
|
||||||
|
s.err("illegal char escape")
|
||||||
|
}
|
||||||
|
|
||||||
|
// we scanned all digits, put the last non digit char back
|
||||||
|
s.unread()
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanIdentifier scans an identifier and returns the literal string
|
||||||
|
func (s *Scanner) scanIdentifier() string {
|
||||||
|
offs := s.srcPos.Offset - s.lastCharLen
|
||||||
|
ch := s.next()
|
||||||
|
for isLetter(ch) || isDigit(ch) || ch == '-' {
|
||||||
|
ch = s.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch != eof {
|
||||||
|
s.unread() // we got identifier, put back latest char
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(s.src[offs:s.srcPos.Offset])
|
||||||
|
}
|
||||||
|
|
||||||
|
// recentPosition returns the position of the character immediately after the
|
||||||
|
// character or token returned by the last call to Scan.
|
||||||
|
func (s *Scanner) recentPosition() (pos token.Pos) {
|
||||||
|
pos.Offset = s.srcPos.Offset - s.lastCharLen
|
||||||
|
switch {
|
||||||
|
case s.srcPos.Column > 0:
|
||||||
|
// common case: last character was not a '\n'
|
||||||
|
pos.Line = s.srcPos.Line
|
||||||
|
pos.Column = s.srcPos.Column
|
||||||
|
case s.lastLineLen > 0:
|
||||||
|
// last character was a '\n'
|
||||||
|
// (we cannot be at the beginning of the source
|
||||||
|
// since we have called next() at least once)
|
||||||
|
pos.Line = s.srcPos.Line - 1
|
||||||
|
pos.Column = s.lastLineLen
|
||||||
|
default:
|
||||||
|
// at the beginning of the source
|
||||||
|
pos.Line = 1
|
||||||
|
pos.Column = 1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// err prints the error of any scanning to s.Error function. If the function is
|
||||||
|
// not defined, by default it prints them to os.Stderr
|
||||||
|
func (s *Scanner) err(msg string) {
|
||||||
|
s.ErrorCount++
|
||||||
|
pos := s.recentPosition()
|
||||||
|
|
||||||
|
if s.Error != nil {
|
||||||
|
s.Error(pos, msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "%s: %s\n", pos, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isHexadecimal returns true if the given rune is a letter
|
||||||
|
func isLetter(ch rune) bool {
|
||||||
|
return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= 0x80 && unicode.IsLetter(ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isHexadecimal returns true if the given rune is a decimal digit
|
||||||
|
func isDigit(ch rune) bool {
|
||||||
|
return '0' <= ch && ch <= '9' || ch >= 0x80 && unicode.IsDigit(ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isHexadecimal returns true if the given rune is a decimal number
|
||||||
|
func isDecimal(ch rune) bool {
|
||||||
|
return '0' <= ch && ch <= '9'
|
||||||
|
}
|
||||||
|
|
||||||
|
// isHexadecimal returns true if the given rune is an hexadecimal number
|
||||||
|
func isHexadecimal(ch rune) bool {
|
||||||
|
return '0' <= ch && ch <= '9' || 'a' <= ch && ch <= 'f' || 'A' <= ch && ch <= 'F'
|
||||||
|
}
|
||||||
|
|
||||||
|
// isWhitespace returns true if the rune is a space, tab, newline or carriage return
|
||||||
|
func isWhitespace(ch rune) bool {
|
||||||
|
return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'
|
||||||
|
}
|
||||||
|
|
||||||
|
// digitVal returns the integer value of a given octal,decimal or hexadecimal rune
|
||||||
|
func digitVal(ch rune) int {
|
||||||
|
switch {
|
||||||
|
case '0' <= ch && ch <= '9':
|
||||||
|
return int(ch - '0')
|
||||||
|
case 'a' <= ch && ch <= 'f':
|
||||||
|
return int(ch - 'a' + 10)
|
||||||
|
case 'A' <= ch && ch <= 'F':
|
||||||
|
return int(ch - 'A' + 10)
|
||||||
|
}
|
||||||
|
return 16 // larger than any legal digit val
|
||||||
|
}
|
46
vendor/github.com/hashicorp/hcl/json/token/position.go
generated
vendored
Normal file
46
vendor/github.com/hashicorp/hcl/json/token/position.go
generated
vendored
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
package token
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Pos describes an arbitrary source position
|
||||||
|
// including the file, line, and column location.
|
||||||
|
// A Position is valid if the line number is > 0.
|
||||||
|
type Pos struct {
|
||||||
|
Filename string // filename, if any
|
||||||
|
Offset int // offset, starting at 0
|
||||||
|
Line int // line number, starting at 1
|
||||||
|
Column int // column number, starting at 1 (character count)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValid returns true if the position is valid.
|
||||||
|
func (p *Pos) IsValid() bool { return p.Line > 0 }
|
||||||
|
|
||||||
|
// String returns a string in one of several forms:
|
||||||
|
//
|
||||||
|
// file:line:column valid position with file name
|
||||||
|
// line:column valid position without file name
|
||||||
|
// file invalid position with file name
|
||||||
|
// - invalid position without file name
|
||||||
|
func (p Pos) String() string {
|
||||||
|
s := p.Filename
|
||||||
|
if p.IsValid() {
|
||||||
|
if s != "" {
|
||||||
|
s += ":"
|
||||||
|
}
|
||||||
|
s += fmt.Sprintf("%d:%d", p.Line, p.Column)
|
||||||
|
}
|
||||||
|
if s == "" {
|
||||||
|
s = "-"
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Before reports whether the position p is before u.
|
||||||
|
func (p Pos) Before(u Pos) bool {
|
||||||
|
return u.Offset > p.Offset || u.Line > p.Line
|
||||||
|
}
|
||||||
|
|
||||||
|
// After reports whether the position p is after u.
|
||||||
|
func (p Pos) After(u Pos) bool {
|
||||||
|
return u.Offset < p.Offset || u.Line < p.Line
|
||||||
|
}
|
118
vendor/github.com/hashicorp/hcl/json/token/token.go
generated
vendored
Normal file
118
vendor/github.com/hashicorp/hcl/json/token/token.go
generated
vendored
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
package token
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
hcltoken "github.com/hashicorp/hcl/hcl/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Token defines a single HCL token which can be obtained via the Scanner
|
||||||
|
type Token struct {
|
||||||
|
Type Type
|
||||||
|
Pos Pos
|
||||||
|
Text string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type is the set of lexical tokens of the HCL (HashiCorp Configuration Language)
|
||||||
|
type Type int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Special tokens
|
||||||
|
ILLEGAL Type = iota
|
||||||
|
EOF
|
||||||
|
|
||||||
|
identifier_beg
|
||||||
|
literal_beg
|
||||||
|
NUMBER // 12345
|
||||||
|
FLOAT // 123.45
|
||||||
|
BOOL // true,false
|
||||||
|
STRING // "abc"
|
||||||
|
NULL // null
|
||||||
|
literal_end
|
||||||
|
identifier_end
|
||||||
|
|
||||||
|
operator_beg
|
||||||
|
LBRACK // [
|
||||||
|
LBRACE // {
|
||||||
|
COMMA // ,
|
||||||
|
PERIOD // .
|
||||||
|
COLON // :
|
||||||
|
|
||||||
|
RBRACK // ]
|
||||||
|
RBRACE // }
|
||||||
|
|
||||||
|
operator_end
|
||||||
|
)
|
||||||
|
|
||||||
|
var tokens = [...]string{
|
||||||
|
ILLEGAL: "ILLEGAL",
|
||||||
|
|
||||||
|
EOF: "EOF",
|
||||||
|
|
||||||
|
NUMBER: "NUMBER",
|
||||||
|
FLOAT: "FLOAT",
|
||||||
|
BOOL: "BOOL",
|
||||||
|
STRING: "STRING",
|
||||||
|
NULL: "NULL",
|
||||||
|
|
||||||
|
LBRACK: "LBRACK",
|
||||||
|
LBRACE: "LBRACE",
|
||||||
|
COMMA: "COMMA",
|
||||||
|
PERIOD: "PERIOD",
|
||||||
|
COLON: "COLON",
|
||||||
|
|
||||||
|
RBRACK: "RBRACK",
|
||||||
|
RBRACE: "RBRACE",
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string corresponding to the token tok.
|
||||||
|
func (t Type) String() string {
|
||||||
|
s := ""
|
||||||
|
if 0 <= t && t < Type(len(tokens)) {
|
||||||
|
s = tokens[t]
|
||||||
|
}
|
||||||
|
if s == "" {
|
||||||
|
s = "token(" + strconv.Itoa(int(t)) + ")"
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsIdentifier returns true for tokens corresponding to identifiers and basic
|
||||||
|
// type literals; it returns false otherwise.
|
||||||
|
func (t Type) IsIdentifier() bool { return identifier_beg < t && t < identifier_end }
|
||||||
|
|
||||||
|
// IsLiteral returns true for tokens corresponding to basic type literals; it
|
||||||
|
// returns false otherwise.
|
||||||
|
func (t Type) IsLiteral() bool { return literal_beg < t && t < literal_end }
|
||||||
|
|
||||||
|
// IsOperator returns true for tokens corresponding to operators and
|
||||||
|
// delimiters; it returns false otherwise.
|
||||||
|
func (t Type) IsOperator() bool { return operator_beg < t && t < operator_end }
|
||||||
|
|
||||||
|
// String returns the token's literal text. Note that this is only
|
||||||
|
// applicable for certain token types, such as token.IDENT,
|
||||||
|
// token.STRING, etc..
|
||||||
|
func (t Token) String() string {
|
||||||
|
return fmt.Sprintf("%s %s %s", t.Pos.String(), t.Type.String(), t.Text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HCLToken converts this token to an HCL token.
|
||||||
|
//
|
||||||
|
// The token type must be a literal type or this will panic.
|
||||||
|
func (t Token) HCLToken() hcltoken.Token {
|
||||||
|
switch t.Type {
|
||||||
|
case BOOL:
|
||||||
|
return hcltoken.Token{Type: hcltoken.BOOL, Text: t.Text}
|
||||||
|
case FLOAT:
|
||||||
|
return hcltoken.Token{Type: hcltoken.FLOAT, Text: t.Text}
|
||||||
|
case NULL:
|
||||||
|
return hcltoken.Token{Type: hcltoken.STRING, Text: ""}
|
||||||
|
case NUMBER:
|
||||||
|
return hcltoken.Token{Type: hcltoken.NUMBER, Text: t.Text}
|
||||||
|
case STRING:
|
||||||
|
return hcltoken.Token{Type: hcltoken.STRING, Text: t.Text, JSON: true}
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unimplemented HCLToken for type: %s", t.Type))
|
||||||
|
}
|
||||||
|
}
|
38
vendor/github.com/hashicorp/hcl/lex.go
generated
vendored
Normal file
38
vendor/github.com/hashicorp/hcl/lex.go
generated
vendored
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
package hcl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
type lexModeValue byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
lexModeUnknown lexModeValue = iota
|
||||||
|
lexModeHcl
|
||||||
|
lexModeJson
|
||||||
|
)
|
||||||
|
|
||||||
|
// lexMode returns whether we're going to be parsing in JSON
|
||||||
|
// mode or HCL mode.
|
||||||
|
func lexMode(v []byte) lexModeValue {
|
||||||
|
var (
|
||||||
|
r rune
|
||||||
|
w int
|
||||||
|
offset int
|
||||||
|
)
|
||||||
|
|
||||||
|
for {
|
||||||
|
r, w = utf8.DecodeRune(v[offset:])
|
||||||
|
offset += w
|
||||||
|
if unicode.IsSpace(r) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if r == '{' {
|
||||||
|
return lexModeJson
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return lexModeHcl
|
||||||
|
}
|
39
vendor/github.com/hashicorp/hcl/parse.go
generated
vendored
Normal file
39
vendor/github.com/hashicorp/hcl/parse.go
generated
vendored
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
package hcl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/hcl/hcl/ast"
|
||||||
|
hclParser "github.com/hashicorp/hcl/hcl/parser"
|
||||||
|
jsonParser "github.com/hashicorp/hcl/json/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseBytes accepts as input byte slice and returns ast tree.
|
||||||
|
//
|
||||||
|
// Input can be either JSON or HCL
|
||||||
|
func ParseBytes(in []byte) (*ast.File, error) {
|
||||||
|
return parse(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseString accepts input as a string and returns ast tree.
|
||||||
|
func ParseString(input string) (*ast.File, error) {
|
||||||
|
return parse([]byte(input))
|
||||||
|
}
|
||||||
|
|
||||||
|
func parse(in []byte) (*ast.File, error) {
|
||||||
|
switch lexMode(in) {
|
||||||
|
case lexModeHcl:
|
||||||
|
return hclParser.Parse(in)
|
||||||
|
case lexModeJson:
|
||||||
|
return jsonParser.Parse(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("unknown config format")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses the given input and returns the root object.
|
||||||
|
//
|
||||||
|
// The input format can be either HCL or JSON.
|
||||||
|
func Parse(input string) (*ast.File, error) {
|
||||||
|
return parse([]byte(input))
|
||||||
|
}
|
201
vendor/github.com/inconshreveable/mousetrap/LICENSE
generated
vendored
Normal file
201
vendor/github.com/inconshreveable/mousetrap/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright 2022 Alan Shreve (@inconshreveable)
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
23
vendor/github.com/inconshreveable/mousetrap/README.md
generated
vendored
Normal file
23
vendor/github.com/inconshreveable/mousetrap/README.md
generated
vendored
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# mousetrap
|
||||||
|
|
||||||
|
mousetrap is a tiny library that answers a single question.
|
||||||
|
|
||||||
|
On a Windows machine, was the process invoked by someone double clicking on
|
||||||
|
the executable file while browsing in explorer?
|
||||||
|
|
||||||
|
### Motivation
|
||||||
|
|
||||||
|
Windows developers unfamiliar with command line tools will often "double-click"
|
||||||
|
the executable for a tool. Because most CLI tools print the help and then exit
|
||||||
|
when invoked without arguments, this is often very frustrating for those users.
|
||||||
|
|
||||||
|
mousetrap provides a way to detect these invocations so that you can provide
|
||||||
|
more helpful behavior and instructions on how to run the CLI tool. To see what
|
||||||
|
this looks like, both from an organizational and a technical perspective, see
|
||||||
|
https://inconshreveable.com/09-09-2014/sweat-the-small-stuff/
|
||||||
|
|
||||||
|
### The interface
|
||||||
|
|
||||||
|
The library exposes a single interface:
|
||||||
|
|
||||||
|
func StartedByExplorer() (bool)
|
16
vendor/github.com/inconshreveable/mousetrap/trap_others.go
generated
vendored
Normal file
16
vendor/github.com/inconshreveable/mousetrap/trap_others.go
generated
vendored
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
//go:build !windows
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package mousetrap
|
||||||
|
|
||||||
|
// StartedByExplorer returns true if the program was invoked by the user
|
||||||
|
// double-clicking on the executable from explorer.exe
|
||||||
|
//
|
||||||
|
// It is conservative and returns false if any of the internal calls fail.
|
||||||
|
// It does not guarantee that the program was run from a terminal. It only can tell you
|
||||||
|
// whether it was launched from explorer.exe
|
||||||
|
//
|
||||||
|
// On non-Windows platforms, it always returns false.
|
||||||
|
func StartedByExplorer() bool {
|
||||||
|
return false
|
||||||
|
}
|
42
vendor/github.com/inconshreveable/mousetrap/trap_windows.go
generated
vendored
Normal file
42
vendor/github.com/inconshreveable/mousetrap/trap_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
package mousetrap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getProcessEntry(pid int) (*syscall.ProcessEntry32, error) {
|
||||||
|
snapshot, err := syscall.CreateToolhelp32Snapshot(syscall.TH32CS_SNAPPROCESS, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer syscall.CloseHandle(snapshot)
|
||||||
|
var procEntry syscall.ProcessEntry32
|
||||||
|
procEntry.Size = uint32(unsafe.Sizeof(procEntry))
|
||||||
|
if err = syscall.Process32First(snapshot, &procEntry); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
if procEntry.ProcessID == uint32(pid) {
|
||||||
|
return &procEntry, nil
|
||||||
|
}
|
||||||
|
err = syscall.Process32Next(snapshot, &procEntry)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartedByExplorer returns true if the program was invoked by the user double-clicking
|
||||||
|
// on the executable from explorer.exe
|
||||||
|
//
|
||||||
|
// It is conservative and returns false if any of the internal calls fail.
|
||||||
|
// It does not guarantee that the program was run from a terminal. It only can tell you
|
||||||
|
// whether it was launched from explorer.exe
|
||||||
|
func StartedByExplorer() bool {
|
||||||
|
pe, err := getProcessEntry(syscall.Getppid())
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return "explorer.exe" == syscall.UTF16ToString(pe.ExeFile[:])
|
||||||
|
}
|
6
vendor/github.com/magiconair/properties/.gitignore
generated
vendored
Normal file
6
vendor/github.com/magiconair/properties/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
*.sublime-project
|
||||||
|
*.sublime-workspace
|
||||||
|
*.un~
|
||||||
|
*.swp
|
||||||
|
.idea/
|
||||||
|
*.iml
|
205
vendor/github.com/magiconair/properties/CHANGELOG.md
generated
vendored
Normal file
205
vendor/github.com/magiconair/properties/CHANGELOG.md
generated
vendored
Normal file
|
@ -0,0 +1,205 @@
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
### [1.8.7](https://github.com/magiconair/properties/tree/v1.8.7) - 08 Dec 2022
|
||||||
|
|
||||||
|
* [PR #65](https://github.com/magiconair/properties/pull/65): Speedup Merge
|
||||||
|
|
||||||
|
Thanks to [@AdityaVallabh](https://github.com/AdityaVallabh) for the patch.
|
||||||
|
|
||||||
|
* [PR #66](https://github.com/magiconair/properties/pull/66): use github actions
|
||||||
|
|
||||||
|
### [1.8.6](https://github.com/magiconair/properties/tree/v1.8.6) - 23 Feb 2022
|
||||||
|
|
||||||
|
* [PR #57](https://github.com/magiconair/properties/pull/57):Fix "unreachable code" lint error
|
||||||
|
|
||||||
|
Thanks to [@ellie](https://github.com/ellie) for the patch.
|
||||||
|
|
||||||
|
* [PR #63](https://github.com/magiconair/properties/pull/63): Make TestMustGetParsedDuration backwards compatible
|
||||||
|
|
||||||
|
This patch ensures that the `TestMustGetParsedDuration` still works with `go1.3` to make the
|
||||||
|
author happy until it affects real users.
|
||||||
|
|
||||||
|
Thanks to [@maage](https://github.com/maage) for the patch.
|
||||||
|
|
||||||
|
### [1.8.5](https://github.com/magiconair/properties/tree/v1.8.5) - 24 Mar 2021
|
||||||
|
|
||||||
|
* [PR #55](https://github.com/magiconair/properties/pull/55): Fix: Encoding Bug in Comments
|
||||||
|
|
||||||
|
When reading comments \ are loaded correctly, but when writing they are then
|
||||||
|
replaced by \\. This leads to wrong comments when writing and reading multiple times.
|
||||||
|
|
||||||
|
Thanks to [@doxsch](https://github.com/doxsch) for the patch.
|
||||||
|
|
||||||
|
### [1.8.4](https://github.com/magiconair/properties/tree/v1.8.4) - 23 Sep 2020
|
||||||
|
|
||||||
|
* [PR #50](https://github.com/magiconair/properties/pull/50): enhance error message for circular references
|
||||||
|
|
||||||
|
Thanks to [@sriv](https://github.com/sriv) for the patch.
|
||||||
|
|
||||||
|
### [1.8.3](https://github.com/magiconair/properties/tree/v1.8.3) - 14 Sep 2020
|
||||||
|
|
||||||
|
* [PR #49](https://github.com/magiconair/properties/pull/49): Include the key in error message causing the circular reference
|
||||||
|
|
||||||
|
The change is include the key in the error message which is causing the circular
|
||||||
|
reference when parsing/loading the properties files.
|
||||||
|
|
||||||
|
Thanks to [@haroon-sheikh](https://github.com/haroon-sheikh) for the patch.
|
||||||
|
|
||||||
|
### [1.8.2](https://github.com/magiconair/properties/tree/v1.8.2) - 25 Aug 2020
|
||||||
|
|
||||||
|
* [PR #36](https://github.com/magiconair/properties/pull/36): Escape backslash on write
|
||||||
|
|
||||||
|
This patch ensures that backslashes are escaped on write. Existing applications which
|
||||||
|
rely on the old behavior may need to be updated.
|
||||||
|
|
||||||
|
Thanks to [@apesternikov](https://github.com/apesternikov) for the patch.
|
||||||
|
|
||||||
|
* [PR #42](https://github.com/magiconair/properties/pull/42): Made Content-Type check whitespace agnostic in LoadURL()
|
||||||
|
|
||||||
|
Thanks to [@aliras1](https://github.com/aliras1) for the patch.
|
||||||
|
|
||||||
|
* [PR #41](https://github.com/magiconair/properties/pull/41): Make key/value separator configurable on Write()
|
||||||
|
|
||||||
|
Thanks to [@mkjor](https://github.com/mkjor) for the patch.
|
||||||
|
|
||||||
|
* [PR #40](https://github.com/magiconair/properties/pull/40): Add method to return a sorted list of keys
|
||||||
|
|
||||||
|
Thanks to [@mkjor](https://github.com/mkjor) for the patch.
|
||||||
|
|
||||||
|
### [1.8.1](https://github.com/magiconair/properties/tree/v1.8.1) - 10 May 2019
|
||||||
|
|
||||||
|
* [PR #35](https://github.com/magiconair/properties/pull/35): Close body always after request
|
||||||
|
|
||||||
|
This patch ensures that in `LoadURL` the response body is always closed.
|
||||||
|
|
||||||
|
Thanks to [@liubog2008](https://github.com/liubog2008) for the patch.
|
||||||
|
|
||||||
|
### [1.8](https://github.com/magiconair/properties/tree/v1.8) - 15 May 2018
|
||||||
|
|
||||||
|
* [PR #26](https://github.com/magiconair/properties/pull/26): Disable expansion during loading
|
||||||
|
|
||||||
|
This adds the option to disable property expansion during loading.
|
||||||
|
|
||||||
|
Thanks to [@kmala](https://github.com/kmala) for the patch.
|
||||||
|
|
||||||
|
### [1.7.6](https://github.com/magiconair/properties/tree/v1.7.6) - 14 Feb 2018
|
||||||
|
|
||||||
|
* [PR #29](https://github.com/magiconair/properties/pull/29): Reworked expansion logic to handle more complex cases.
|
||||||
|
|
||||||
|
See PR for an example.
|
||||||
|
|
||||||
|
Thanks to [@yobert](https://github.com/yobert) for the fix.
|
||||||
|
|
||||||
|
### [1.7.5](https://github.com/magiconair/properties/tree/v1.7.5) - 13 Feb 2018
|
||||||
|
|
||||||
|
* [PR #28](https://github.com/magiconair/properties/pull/28): Support duplicate expansions in the same value
|
||||||
|
|
||||||
|
Values which expand the same key multiple times (e.g. `key=${a} ${a}`) will no longer fail
|
||||||
|
with a `circular reference error`.
|
||||||
|
|
||||||
|
Thanks to [@yobert](https://github.com/yobert) for the fix.
|
||||||
|
|
||||||
|
### [1.7.4](https://github.com/magiconair/properties/tree/v1.7.4) - 31 Oct 2017
|
||||||
|
|
||||||
|
* [Issue #23](https://github.com/magiconair/properties/issues/23): Ignore blank lines with whitespaces
|
||||||
|
|
||||||
|
* [PR #24](https://github.com/magiconair/properties/pull/24): Update keys when DisableExpansion is enabled
|
||||||
|
|
||||||
|
Thanks to [@mgurov](https://github.com/mgurov) for the fix.
|
||||||
|
|
||||||
|
### [1.7.3](https://github.com/magiconair/properties/tree/v1.7.3) - 10 Jul 2017
|
||||||
|
|
||||||
|
* [Issue #17](https://github.com/magiconair/properties/issues/17): Add [SetValue()](http://godoc.org/github.com/magiconair/properties#Properties.SetValue) method to set values generically
|
||||||
|
* [Issue #22](https://github.com/magiconair/properties/issues/22): Add [LoadMap()](http://godoc.org/github.com/magiconair/properties#LoadMap) function to load properties from a string map
|
||||||
|
|
||||||
|
### [1.7.2](https://github.com/magiconair/properties/tree/v1.7.2) - 20 Mar 2017
|
||||||
|
|
||||||
|
* [Issue #15](https://github.com/magiconair/properties/issues/15): Drop gocheck dependency
|
||||||
|
* [PR #21](https://github.com/magiconair/properties/pull/21): Add [Map()](http://godoc.org/github.com/magiconair/properties#Properties.Map) and [FilterFunc()](http://godoc.org/github.com/magiconair/properties#Properties.FilterFunc)
|
||||||
|
|
||||||
|
### [1.7.1](https://github.com/magiconair/properties/tree/v1.7.1) - 13 Jan 2017
|
||||||
|
|
||||||
|
* [Issue #14](https://github.com/magiconair/properties/issues/14): Decouple TestLoadExpandedFile from `$USER`
|
||||||
|
* [PR #12](https://github.com/magiconair/properties/pull/12): Load from files and URLs
|
||||||
|
* [PR #16](https://github.com/magiconair/properties/pull/16): Keep gofmt happy
|
||||||
|
* [PR #18](https://github.com/magiconair/properties/pull/18): Fix Delete() function
|
||||||
|
|
||||||
|
### [1.7.0](https://github.com/magiconair/properties/tree/v1.7.0) - 20 Mar 2016
|
||||||
|
|
||||||
|
* [Issue #10](https://github.com/magiconair/properties/issues/10): Add [LoadURL,LoadURLs,MustLoadURL,MustLoadURLs](http://godoc.org/github.com/magiconair/properties#LoadURL) method to load properties from a URL.
|
||||||
|
* [Issue #11](https://github.com/magiconair/properties/issues/11): Add [LoadString,MustLoadString](http://godoc.org/github.com/magiconair/properties#LoadString) method to load properties from an UTF8 string.
|
||||||
|
* [PR #8](https://github.com/magiconair/properties/pull/8): Add [MustFlag](http://godoc.org/github.com/magiconair/properties#Properties.MustFlag) method to provide overrides via command line flags. (@pascaldekloe)
|
||||||
|
|
||||||
|
### [1.6.0](https://github.com/magiconair/properties/tree/v1.6.0) - 11 Dec 2015
|
||||||
|
|
||||||
|
* Add [Decode](http://godoc.org/github.com/magiconair/properties#Properties.Decode) method to populate struct from properties via tags.
|
||||||
|
|
||||||
|
### [1.5.6](https://github.com/magiconair/properties/tree/v1.5.6) - 18 Oct 2015
|
||||||
|
|
||||||
|
* Vendored in gopkg.in/check.v1
|
||||||
|
|
||||||
|
### [1.5.5](https://github.com/magiconair/properties/tree/v1.5.5) - 31 Jul 2015
|
||||||
|
|
||||||
|
* [PR #6](https://github.com/magiconair/properties/pull/6): Add [Delete](http://godoc.org/github.com/magiconair/properties#Properties.Delete) method to remove keys including comments. (@gerbenjacobs)
|
||||||
|
|
||||||
|
### [1.5.4](https://github.com/magiconair/properties/tree/v1.5.4) - 23 Jun 2015
|
||||||
|
|
||||||
|
* [Issue #5](https://github.com/magiconair/properties/issues/5): Allow disabling of property expansion [DisableExpansion](http://godoc.org/github.com/magiconair/properties#Properties.DisableExpansion). When property expansion is disabled Properties become a simple key/value store and don't check for circular references.
|
||||||
|
|
||||||
|
### [1.5.3](https://github.com/magiconair/properties/tree/v1.5.3) - 02 Jun 2015
|
||||||
|
|
||||||
|
* [Issue #4](https://github.com/magiconair/properties/issues/4): Maintain key order in [Filter()](http://godoc.org/github.com/magiconair/properties#Properties.Filter), [FilterPrefix()](http://godoc.org/github.com/magiconair/properties#Properties.FilterPrefix) and [FilterRegexp()](http://godoc.org/github.com/magiconair/properties#Properties.FilterRegexp)
|
||||||
|
|
||||||
|
### [1.5.2](https://github.com/magiconair/properties/tree/v1.5.2) - 10 Apr 2015
|
||||||
|
|
||||||
|
* [Issue #3](https://github.com/magiconair/properties/issues/3): Don't print comments in [WriteComment()](http://godoc.org/github.com/magiconair/properties#Properties.WriteComment) if they are all empty
|
||||||
|
* Add clickable links to README
|
||||||
|
|
||||||
|
### [1.5.1](https://github.com/magiconair/properties/tree/v1.5.1) - 08 Dec 2014
|
||||||
|
|
||||||
|
* Added [GetParsedDuration()](http://godoc.org/github.com/magiconair/properties#Properties.GetParsedDuration) and [MustGetParsedDuration()](http://godoc.org/github.com/magiconair/properties#Properties.MustGetParsedDuration) for values specified compatible with
|
||||||
|
[time.ParseDuration()](http://golang.org/pkg/time/#ParseDuration).
|
||||||
|
|
||||||
|
### [1.5.0](https://github.com/magiconair/properties/tree/v1.5.0) - 18 Nov 2014
|
||||||
|
|
||||||
|
* Added support for single and multi-line comments (reading, writing and updating)
|
||||||
|
* The order of keys is now preserved
|
||||||
|
* Calling [Set()](http://godoc.org/github.com/magiconair/properties#Properties.Set) with an empty key now silently ignores the call and does not create a new entry
|
||||||
|
* Added a [MustSet()](http://godoc.org/github.com/magiconair/properties#Properties.MustSet) method
|
||||||
|
* Migrated test library from launchpad.net/gocheck to [gopkg.in/check.v1](http://gopkg.in/check.v1)
|
||||||
|
|
||||||
|
### [1.4.2](https://github.com/magiconair/properties/tree/v1.4.2) - 15 Nov 2014
|
||||||
|
|
||||||
|
* [Issue #2](https://github.com/magiconair/properties/issues/2): Fixed goroutine leak in parser which created two lexers but cleaned up only one
|
||||||
|
|
||||||
|
### [1.4.1](https://github.com/magiconair/properties/tree/v1.4.1) - 13 Nov 2014
|
||||||
|
|
||||||
|
* [Issue #1](https://github.com/magiconair/properties/issues/1): Fixed bug in Keys() method which returned an empty string
|
||||||
|
|
||||||
|
### [1.4.0](https://github.com/magiconair/properties/tree/v1.4.0) - 23 Sep 2014
|
||||||
|
|
||||||
|
* Added [Keys()](http://godoc.org/github.com/magiconair/properties#Properties.Keys) to get the keys
|
||||||
|
* Added [Filter()](http://godoc.org/github.com/magiconair/properties#Properties.Filter), [FilterRegexp()](http://godoc.org/github.com/magiconair/properties#Properties.FilterRegexp) and [FilterPrefix()](http://godoc.org/github.com/magiconair/properties#Properties.FilterPrefix) to get a subset of the properties
|
||||||
|
|
||||||
|
### [1.3.0](https://github.com/magiconair/properties/tree/v1.3.0) - 18 Mar 2014
|
||||||
|
|
||||||
|
* Added support for time.Duration
|
||||||
|
* Made MustXXX() failure beha[ior configurable (log.Fatal, panic](https://github.com/magiconair/properties/tree/vior configurable (log.Fatal, panic) - custom)
|
||||||
|
* Changed default of MustXXX() failure from panic to log.Fatal
|
||||||
|
|
||||||
|
### [1.2.0](https://github.com/magiconair/properties/tree/v1.2.0) - 05 Mar 2014
|
||||||
|
|
||||||
|
* Added MustGet... functions
|
||||||
|
* Added support for int and uint with range checks on 32 bit platforms
|
||||||
|
|
||||||
|
### [1.1.0](https://github.com/magiconair/properties/tree/v1.1.0) - 20 Jan 2014
|
||||||
|
|
||||||
|
* Renamed from goproperties to properties
|
||||||
|
* Added support for expansion of environment vars in
|
||||||
|
filenames and value expressions
|
||||||
|
* Fixed bug where value expressions were not at the
|
||||||
|
start of the string
|
||||||
|
|
||||||
|
### [1.0.0](https://github.com/magiconair/properties/tree/v1.0.0) - 7 Jan 2014
|
||||||
|
|
||||||
|
* Initial release
|
24
vendor/github.com/magiconair/properties/LICENSE.md
generated
vendored
Normal file
24
vendor/github.com/magiconair/properties/LICENSE.md
generated
vendored
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
Copyright (c) 2013-2020, Frank Schroeder
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
128
vendor/github.com/magiconair/properties/README.md
generated
vendored
Normal file
128
vendor/github.com/magiconair/properties/README.md
generated
vendored
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
[![](https://img.shields.io/github/tag/magiconair/properties.svg?style=flat-square&label=release)](https://github.com/magiconair/properties/releases)
|
||||||
|
[![Travis CI Status](https://img.shields.io/travis/magiconair/properties.svg?branch=master&style=flat-square&label=travis)](https://travis-ci.org/magiconair/properties)
|
||||||
|
[![License](https://img.shields.io/badge/License-BSD%202--Clause-orange.svg?style=flat-square)](https://raw.githubusercontent.com/magiconair/properties/master/LICENSE)
|
||||||
|
[![GoDoc](http://img.shields.io/badge/godoc-reference-5272B4.svg?style=flat-square)](http://godoc.org/github.com/magiconair/properties)
|
||||||
|
|
||||||
|
# Overview
|
||||||
|
|
||||||
|
#### Please run `git pull --tags` to update the tags. See [below](#updated-git-tags) why.
|
||||||
|
|
||||||
|
properties is a Go library for reading and writing properties files.
|
||||||
|
|
||||||
|
It supports reading from multiple files or URLs and Spring style recursive
|
||||||
|
property expansion of expressions like `${key}` to their corresponding value.
|
||||||
|
Value expressions can refer to other keys like in `${key}` or to environment
|
||||||
|
variables like in `${USER}`. Filenames can also contain environment variables
|
||||||
|
like in `/home/${USER}/myapp.properties`.
|
||||||
|
|
||||||
|
Properties can be decoded into structs, maps, arrays and values through
|
||||||
|
struct tags.
|
||||||
|
|
||||||
|
Comments and the order of keys are preserved. Comments can be modified
|
||||||
|
and can be written to the output.
|
||||||
|
|
||||||
|
The properties library supports both ISO-8859-1 and UTF-8 encoded data.
|
||||||
|
|
||||||
|
Starting from version 1.3.0 the behavior of the MustXXX() functions is
|
||||||
|
configurable by providing a custom `ErrorHandler` function. The default has
|
||||||
|
changed from `panic` to `log.Fatal` but this is configurable and custom
|
||||||
|
error handling functions can be provided. See the package documentation for
|
||||||
|
details.
|
||||||
|
|
||||||
|
Read the full documentation on [![GoDoc](http://img.shields.io/badge/godoc-reference-5272B4.svg?style=flat-square)](http://godoc.org/github.com/magiconair/properties)
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"github.com/magiconair/properties"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// init from a file
|
||||||
|
p := properties.MustLoadFile("${HOME}/config.properties", properties.UTF8)
|
||||||
|
|
||||||
|
// or multiple files
|
||||||
|
p = properties.MustLoadFiles([]string{
|
||||||
|
"${HOME}/config.properties",
|
||||||
|
"${HOME}/config-${USER}.properties",
|
||||||
|
}, properties.UTF8, true)
|
||||||
|
|
||||||
|
// or from a map
|
||||||
|
p = properties.LoadMap(map[string]string{"key": "value", "abc": "def"})
|
||||||
|
|
||||||
|
// or from a string
|
||||||
|
p = properties.MustLoadString("key=value\nabc=def")
|
||||||
|
|
||||||
|
// or from a URL
|
||||||
|
p = properties.MustLoadURL("http://host/path")
|
||||||
|
|
||||||
|
// or from multiple URLs
|
||||||
|
p = properties.MustLoadURL([]string{
|
||||||
|
"http://host/config",
|
||||||
|
"http://host/config-${USER}",
|
||||||
|
}, true)
|
||||||
|
|
||||||
|
// or from flags
|
||||||
|
p.MustFlag(flag.CommandLine)
|
||||||
|
|
||||||
|
// get values through getters
|
||||||
|
host := p.MustGetString("host")
|
||||||
|
port := p.GetInt("port", 8080)
|
||||||
|
|
||||||
|
// or through Decode
|
||||||
|
type Config struct {
|
||||||
|
Host string `properties:"host"`
|
||||||
|
Port int `properties:"port,default=9000"`
|
||||||
|
Accept []string `properties:"accept,default=image/png;image;gif"`
|
||||||
|
Timeout time.Duration `properties:"timeout,default=5s"`
|
||||||
|
}
|
||||||
|
var cfg Config
|
||||||
|
if err := p.Decode(&cfg); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installation and Upgrade
|
||||||
|
|
||||||
|
```
|
||||||
|
$ go get -u github.com/magiconair/properties
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
2 clause BSD license. See [LICENSE](https://github.com/magiconair/properties/blob/master/LICENSE) file for details.
|
||||||
|
|
||||||
|
## ToDo
|
||||||
|
|
||||||
|
* Dump contents with passwords and secrets obscured
|
||||||
|
|
||||||
|
## Updated Git tags
|
||||||
|
|
||||||
|
#### 13 Feb 2018
|
||||||
|
|
||||||
|
I realized that all of the git tags I had pushed before v1.7.5 were lightweight tags
|
||||||
|
and I've only recently learned that this doesn't play well with `git describe` 😞
|
||||||
|
|
||||||
|
I have replaced all lightweight tags with signed tags using this script which should
|
||||||
|
retain the commit date, name and email address. Please run `git pull --tags` to update them.
|
||||||
|
|
||||||
|
Worst case you have to reclone the repo.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
#!/bin/bash
|
||||||
|
tag=$1
|
||||||
|
echo "Updating $tag"
|
||||||
|
date=$(git show ${tag}^0 --format=%aD | head -1)
|
||||||
|
email=$(git show ${tag}^0 --format=%aE | head -1)
|
||||||
|
name=$(git show ${tag}^0 --format=%aN | head -1)
|
||||||
|
GIT_COMMITTER_DATE="$date" GIT_COMMITTER_NAME="$name" GIT_COMMITTER_EMAIL="$email" git tag -s -f ${tag} ${tag}^0 -m ${tag}
|
||||||
|
```
|
||||||
|
|
||||||
|
I apologize for the inconvenience.
|
||||||
|
|
||||||
|
Frank
|
||||||
|
|
289
vendor/github.com/magiconair/properties/decode.go
generated
vendored
Normal file
289
vendor/github.com/magiconair/properties/decode.go
generated
vendored
Normal file
|
@ -0,0 +1,289 @@
|
||||||
|
// Copyright 2013-2022 Frank Schroeder. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package properties
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Decode assigns property values to exported fields of a struct.
|
||||||
|
//
|
||||||
|
// Decode traverses v recursively and returns an error if a value cannot be
|
||||||
|
// converted to the field type or a required value is missing for a field.
|
||||||
|
//
|
||||||
|
// The following type dependent decodings are used:
|
||||||
|
//
|
||||||
|
// String, boolean, numeric fields have the value of the property key assigned.
|
||||||
|
// The property key name is the name of the field. A different key and a default
|
||||||
|
// value can be set in the field's tag. Fields without default value are
|
||||||
|
// required. If the value cannot be converted to the field type an error is
|
||||||
|
// returned.
|
||||||
|
//
|
||||||
|
// time.Duration fields have the result of time.ParseDuration() assigned.
|
||||||
|
//
|
||||||
|
// time.Time fields have the vaule of time.Parse() assigned. The default layout
|
||||||
|
// is time.RFC3339 but can be set in the field's tag.
|
||||||
|
//
|
||||||
|
// Arrays and slices of string, boolean, numeric, time.Duration and time.Time
|
||||||
|
// fields have the value interpreted as a comma separated list of values. The
|
||||||
|
// individual values are trimmed of whitespace and empty values are ignored. A
|
||||||
|
// default value can be provided as a semicolon separated list in the field's
|
||||||
|
// tag.
|
||||||
|
//
|
||||||
|
// Struct fields are decoded recursively using the field name plus "." as
|
||||||
|
// prefix. The prefix (without dot) can be overridden in the field's tag.
|
||||||
|
// Default values are not supported in the field's tag. Specify them on the
|
||||||
|
// fields of the inner struct instead.
|
||||||
|
//
|
||||||
|
// Map fields must have a key of type string and are decoded recursively by
|
||||||
|
// using the field's name plus ".' as prefix and the next element of the key
|
||||||
|
// name as map key. The prefix (without dot) can be overridden in the field's
|
||||||
|
// tag. Default values are not supported.
|
||||||
|
//
|
||||||
|
// Examples:
|
||||||
|
//
|
||||||
|
// // Field is ignored.
|
||||||
|
// Field int `properties:"-"`
|
||||||
|
//
|
||||||
|
// // Field is assigned value of 'Field'.
|
||||||
|
// Field int
|
||||||
|
//
|
||||||
|
// // Field is assigned value of 'myName'.
|
||||||
|
// Field int `properties:"myName"`
|
||||||
|
//
|
||||||
|
// // Field is assigned value of key 'myName' and has a default
|
||||||
|
// // value 15 if the key does not exist.
|
||||||
|
// Field int `properties:"myName,default=15"`
|
||||||
|
//
|
||||||
|
// // Field is assigned value of key 'Field' and has a default
|
||||||
|
// // value 15 if the key does not exist.
|
||||||
|
// Field int `properties:",default=15"`
|
||||||
|
//
|
||||||
|
// // Field is assigned value of key 'date' and the date
|
||||||
|
// // is in format 2006-01-02
|
||||||
|
// Field time.Time `properties:"date,layout=2006-01-02"`
|
||||||
|
//
|
||||||
|
// // Field is assigned the non-empty and whitespace trimmed
|
||||||
|
// // values of key 'Field' split by commas.
|
||||||
|
// Field []string
|
||||||
|
//
|
||||||
|
// // Field is assigned the non-empty and whitespace trimmed
|
||||||
|
// // values of key 'Field' split by commas and has a default
|
||||||
|
// // value ["a", "b", "c"] if the key does not exist.
|
||||||
|
// Field []string `properties:",default=a;b;c"`
|
||||||
|
//
|
||||||
|
// // Field is decoded recursively with "Field." as key prefix.
|
||||||
|
// Field SomeStruct
|
||||||
|
//
|
||||||
|
// // Field is decoded recursively with "myName." as key prefix.
|
||||||
|
// Field SomeStruct `properties:"myName"`
|
||||||
|
//
|
||||||
|
// // Field is decoded recursively with "Field." as key prefix
|
||||||
|
// // and the next dotted element of the key as map key.
|
||||||
|
// Field map[string]string
|
||||||
|
//
|
||||||
|
// // Field is decoded recursively with "myName." as key prefix
|
||||||
|
// // and the next dotted element of the key as map key.
|
||||||
|
// Field map[string]string `properties:"myName"`
|
||||||
|
func (p *Properties) Decode(x interface{}) error {
|
||||||
|
t, v := reflect.TypeOf(x), reflect.ValueOf(x)
|
||||||
|
if t.Kind() != reflect.Ptr || v.Elem().Type().Kind() != reflect.Struct {
|
||||||
|
return fmt.Errorf("not a pointer to struct: %s", t)
|
||||||
|
}
|
||||||
|
if err := dec(p, "", nil, nil, v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func dec(p *Properties, key string, def *string, opts map[string]string, v reflect.Value) error {
|
||||||
|
t := v.Type()
|
||||||
|
|
||||||
|
// value returns the property value for key or the default if provided.
|
||||||
|
value := func() (string, error) {
|
||||||
|
if val, ok := p.Get(key); ok {
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
if def != nil {
|
||||||
|
return *def, nil
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("missing required key %s", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// conv converts a string to a value of the given type.
|
||||||
|
conv := func(s string, t reflect.Type) (val reflect.Value, err error) {
|
||||||
|
var v interface{}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case isDuration(t):
|
||||||
|
v, err = time.ParseDuration(s)
|
||||||
|
|
||||||
|
case isTime(t):
|
||||||
|
layout := opts["layout"]
|
||||||
|
if layout == "" {
|
||||||
|
layout = time.RFC3339
|
||||||
|
}
|
||||||
|
v, err = time.Parse(layout, s)
|
||||||
|
|
||||||
|
case isBool(t):
|
||||||
|
v, err = boolVal(s), nil
|
||||||
|
|
||||||
|
case isString(t):
|
||||||
|
v, err = s, nil
|
||||||
|
|
||||||
|
case isFloat(t):
|
||||||
|
v, err = strconv.ParseFloat(s, 64)
|
||||||
|
|
||||||
|
case isInt(t):
|
||||||
|
v, err = strconv.ParseInt(s, 10, 64)
|
||||||
|
|
||||||
|
case isUint(t):
|
||||||
|
v, err = strconv.ParseUint(s, 10, 64)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return reflect.Zero(t), fmt.Errorf("unsupported type %s", t)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return reflect.Zero(t), err
|
||||||
|
}
|
||||||
|
return reflect.ValueOf(v).Convert(t), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// keydef returns the property key and the default value based on the
|
||||||
|
// name of the struct field and the options in the tag.
|
||||||
|
keydef := func(f reflect.StructField) (string, *string, map[string]string) {
|
||||||
|
_key, _opts := parseTag(f.Tag.Get("properties"))
|
||||||
|
|
||||||
|
var _def *string
|
||||||
|
if d, ok := _opts["default"]; ok {
|
||||||
|
_def = &d
|
||||||
|
}
|
||||||
|
if _key != "" {
|
||||||
|
return _key, _def, _opts
|
||||||
|
}
|
||||||
|
return f.Name, _def, _opts
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case isDuration(t) || isTime(t) || isBool(t) || isString(t) || isFloat(t) || isInt(t) || isUint(t):
|
||||||
|
s, err := value()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
val, err := conv(s, t)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Set(val)
|
||||||
|
|
||||||
|
case isPtr(t):
|
||||||
|
return dec(p, key, def, opts, v.Elem())
|
||||||
|
|
||||||
|
case isStruct(t):
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
fv := v.Field(i)
|
||||||
|
fk, def, opts := keydef(t.Field(i))
|
||||||
|
if !fv.CanSet() {
|
||||||
|
return fmt.Errorf("cannot set %s", t.Field(i).Name)
|
||||||
|
}
|
||||||
|
if fk == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if key != "" {
|
||||||
|
fk = key + "." + fk
|
||||||
|
}
|
||||||
|
if err := dec(p, fk, def, opts, fv); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case isArray(t):
|
||||||
|
val, err := value()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
vals := split(val, ";")
|
||||||
|
a := reflect.MakeSlice(t, 0, len(vals))
|
||||||
|
for _, s := range vals {
|
||||||
|
val, err := conv(s, t.Elem())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
a = reflect.Append(a, val)
|
||||||
|
}
|
||||||
|
v.Set(a)
|
||||||
|
|
||||||
|
case isMap(t):
|
||||||
|
valT := t.Elem()
|
||||||
|
m := reflect.MakeMap(t)
|
||||||
|
for postfix := range p.FilterStripPrefix(key + ".").m {
|
||||||
|
pp := strings.SplitN(postfix, ".", 2)
|
||||||
|
mk, mv := pp[0], reflect.New(valT)
|
||||||
|
if err := dec(p, key+"."+mk, nil, nil, mv); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.SetMapIndex(reflect.ValueOf(mk), mv.Elem())
|
||||||
|
}
|
||||||
|
v.Set(m)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported type %s", t)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// split splits a string on sep, trims whitespace of elements
|
||||||
|
// and omits empty elements
|
||||||
|
func split(s string, sep string) []string {
|
||||||
|
var a []string
|
||||||
|
for _, v := range strings.Split(s, sep) {
|
||||||
|
if v = strings.TrimSpace(v); v != "" {
|
||||||
|
a = append(a, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseTag parses a "key,k=v,k=v,..."
|
||||||
|
func parseTag(tag string) (key string, opts map[string]string) {
|
||||||
|
opts = map[string]string{}
|
||||||
|
for i, s := range strings.Split(tag, ",") {
|
||||||
|
if i == 0 {
|
||||||
|
key = s
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pp := strings.SplitN(s, "=", 2)
|
||||||
|
if len(pp) == 1 {
|
||||||
|
opts[pp[0]] = ""
|
||||||
|
} else {
|
||||||
|
opts[pp[0]] = pp[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return key, opts
|
||||||
|
}
|
||||||
|
|
||||||
|
func isArray(t reflect.Type) bool { return t.Kind() == reflect.Array || t.Kind() == reflect.Slice }
|
||||||
|
func isBool(t reflect.Type) bool { return t.Kind() == reflect.Bool }
|
||||||
|
func isDuration(t reflect.Type) bool { return t == reflect.TypeOf(time.Second) }
|
||||||
|
func isMap(t reflect.Type) bool { return t.Kind() == reflect.Map }
|
||||||
|
func isPtr(t reflect.Type) bool { return t.Kind() == reflect.Ptr }
|
||||||
|
func isString(t reflect.Type) bool { return t.Kind() == reflect.String }
|
||||||
|
func isStruct(t reflect.Type) bool { return t.Kind() == reflect.Struct }
|
||||||
|
func isTime(t reflect.Type) bool { return t == reflect.TypeOf(time.Time{}) }
|
||||||
|
func isFloat(t reflect.Type) bool {
|
||||||
|
return t.Kind() == reflect.Float32 || t.Kind() == reflect.Float64
|
||||||
|
}
|
||||||
|
func isInt(t reflect.Type) bool {
|
||||||
|
return t.Kind() == reflect.Int || t.Kind() == reflect.Int8 || t.Kind() == reflect.Int16 || t.Kind() == reflect.Int32 || t.Kind() == reflect.Int64
|
||||||
|
}
|
||||||
|
func isUint(t reflect.Type) bool {
|
||||||
|
return t.Kind() == reflect.Uint || t.Kind() == reflect.Uint8 || t.Kind() == reflect.Uint16 || t.Kind() == reflect.Uint32 || t.Kind() == reflect.Uint64
|
||||||
|
}
|
155
vendor/github.com/magiconair/properties/doc.go
generated
vendored
Normal file
155
vendor/github.com/magiconair/properties/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
// Copyright 2013-2022 Frank Schroeder. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package properties provides functions for reading and writing
|
||||||
|
// ISO-8859-1 and UTF-8 encoded .properties files and has
|
||||||
|
// support for recursive property expansion.
|
||||||
|
//
|
||||||
|
// Java properties files are ISO-8859-1 encoded and use Unicode
|
||||||
|
// literals for characters outside the ISO character set. Unicode
|
||||||
|
// literals can be used in UTF-8 encoded properties files but
|
||||||
|
// aren't necessary.
|
||||||
|
//
|
||||||
|
// To load a single properties file use MustLoadFile():
|
||||||
|
//
|
||||||
|
// p := properties.MustLoadFile(filename, properties.UTF8)
|
||||||
|
//
|
||||||
|
// To load multiple properties files use MustLoadFiles()
|
||||||
|
// which loads the files in the given order and merges the
|
||||||
|
// result. Missing properties files can be ignored if the
|
||||||
|
// 'ignoreMissing' flag is set to true.
|
||||||
|
//
|
||||||
|
// Filenames can contain environment variables which are expanded
|
||||||
|
// before loading.
|
||||||
|
//
|
||||||
|
// f1 := "/etc/myapp/myapp.conf"
|
||||||
|
// f2 := "/home/${USER}/myapp.conf"
|
||||||
|
// p := MustLoadFiles([]string{f1, f2}, properties.UTF8, true)
|
||||||
|
//
|
||||||
|
// All of the different key/value delimiters ' ', ':' and '=' are
|
||||||
|
// supported as well as the comment characters '!' and '#' and
|
||||||
|
// multi-line values.
|
||||||
|
//
|
||||||
|
// ! this is a comment
|
||||||
|
// # and so is this
|
||||||
|
//
|
||||||
|
// # the following expressions are equal
|
||||||
|
// key value
|
||||||
|
// key=value
|
||||||
|
// key:value
|
||||||
|
// key = value
|
||||||
|
// key : value
|
||||||
|
// key = val\
|
||||||
|
// ue
|
||||||
|
//
|
||||||
|
// Properties stores all comments preceding a key and provides
|
||||||
|
// GetComments() and SetComments() methods to retrieve and
|
||||||
|
// update them. The convenience functions GetComment() and
|
||||||
|
// SetComment() allow access to the last comment. The
|
||||||
|
// WriteComment() method writes properties files including
|
||||||
|
// the comments and with the keys in the original order.
|
||||||
|
// This can be used for sanitizing properties files.
|
||||||
|
//
|
||||||
|
// Property expansion is recursive and circular references
|
||||||
|
// and malformed expressions are not allowed and cause an
|
||||||
|
// error. Expansion of environment variables is supported.
|
||||||
|
//
|
||||||
|
// # standard property
|
||||||
|
// key = value
|
||||||
|
//
|
||||||
|
// # property expansion: key2 = value
|
||||||
|
// key2 = ${key}
|
||||||
|
//
|
||||||
|
// # recursive expansion: key3 = value
|
||||||
|
// key3 = ${key2}
|
||||||
|
//
|
||||||
|
// # circular reference (error)
|
||||||
|
// key = ${key}
|
||||||
|
//
|
||||||
|
// # malformed expression (error)
|
||||||
|
// key = ${ke
|
||||||
|
//
|
||||||
|
// # refers to the users' home dir
|
||||||
|
// home = ${HOME}
|
||||||
|
//
|
||||||
|
// # local key takes precedence over env var: u = foo
|
||||||
|
// USER = foo
|
||||||
|
// u = ${USER}
|
||||||
|
//
|
||||||
|
// The default property expansion format is ${key} but can be
|
||||||
|
// changed by setting different pre- and postfix values on the
|
||||||
|
// Properties object.
|
||||||
|
//
|
||||||
|
// p := properties.NewProperties()
|
||||||
|
// p.Prefix = "#["
|
||||||
|
// p.Postfix = "]#"
|
||||||
|
//
|
||||||
|
// Properties provides convenience functions for getting typed
|
||||||
|
// values with default values if the key does not exist or the
|
||||||
|
// type conversion failed.
|
||||||
|
//
|
||||||
|
// # Returns true if the value is either "1", "on", "yes" or "true"
|
||||||
|
// # Returns false for every other value and the default value if
|
||||||
|
// # the key does not exist.
|
||||||
|
// v = p.GetBool("key", false)
|
||||||
|
//
|
||||||
|
// # Returns the value if the key exists and the format conversion
|
||||||
|
// # was successful. Otherwise, the default value is returned.
|
||||||
|
// v = p.GetInt64("key", 999)
|
||||||
|
// v = p.GetUint64("key", 999)
|
||||||
|
// v = p.GetFloat64("key", 123.0)
|
||||||
|
// v = p.GetString("key", "def")
|
||||||
|
// v = p.GetDuration("key", 999)
|
||||||
|
//
|
||||||
|
// As an alternative properties may be applied with the standard
|
||||||
|
// library's flag implementation at any time.
|
||||||
|
//
|
||||||
|
// # Standard configuration
|
||||||
|
// v = flag.Int("key", 999, "help message")
|
||||||
|
// flag.Parse()
|
||||||
|
//
|
||||||
|
// # Merge p into the flag set
|
||||||
|
// p.MustFlag(flag.CommandLine)
|
||||||
|
//
|
||||||
|
// Properties provides several MustXXX() convenience functions
|
||||||
|
// which will terminate the app if an error occurs. The behavior
|
||||||
|
// of the failure is configurable and the default is to call
|
||||||
|
// log.Fatal(err). To have the MustXXX() functions panic instead
|
||||||
|
// of logging the error set a different ErrorHandler before
|
||||||
|
// you use the Properties package.
|
||||||
|
//
|
||||||
|
// properties.ErrorHandler = properties.PanicHandler
|
||||||
|
//
|
||||||
|
// # Will panic instead of logging an error
|
||||||
|
// p := properties.MustLoadFile("config.properties")
|
||||||
|
//
|
||||||
|
// You can also provide your own ErrorHandler function. The only requirement
|
||||||
|
// is that the error handler function must exit after handling the error.
|
||||||
|
//
|
||||||
|
// properties.ErrorHandler = func(err error) {
|
||||||
|
// fmt.Println(err)
|
||||||
|
// os.Exit(1)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// # Will write to stdout and then exit
|
||||||
|
// p := properties.MustLoadFile("config.properties")
|
||||||
|
//
|
||||||
|
// Properties can also be loaded into a struct via the `Decode`
|
||||||
|
// method, e.g.
|
||||||
|
//
|
||||||
|
// type S struct {
|
||||||
|
// A string `properties:"a,default=foo"`
|
||||||
|
// D time.Duration `properties:"timeout,default=5s"`
|
||||||
|
// E time.Time `properties:"expires,layout=2006-01-02,default=2015-01-01"`
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// See `Decode()` method for the full documentation.
|
||||||
|
//
|
||||||
|
// The following documents provide a description of the properties
|
||||||
|
// file format.
|
||||||
|
//
|
||||||
|
// http://en.wikipedia.org/wiki/.properties
|
||||||
|
//
|
||||||
|
// http://docs.oracle.com/javase/7/docs/api/java/util/Properties.html#load%28java.io.Reader%29
|
||||||
|
package properties
|
35
vendor/github.com/magiconair/properties/integrate.go
generated
vendored
Normal file
35
vendor/github.com/magiconair/properties/integrate.go
generated
vendored
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
// Copyright 2013-2022 Frank Schroeder. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package properties
|
||||||
|
|
||||||
|
import "flag"
|
||||||
|
|
||||||
|
// MustFlag sets flags that are skipped by dst.Parse when p contains
|
||||||
|
// the respective key for flag.Flag.Name.
|
||||||
|
//
|
||||||
|
// It's use is recommended with command line arguments as in:
|
||||||
|
//
|
||||||
|
// flag.Parse()
|
||||||
|
// p.MustFlag(flag.CommandLine)
|
||||||
|
func (p *Properties) MustFlag(dst *flag.FlagSet) {
|
||||||
|
m := make(map[string]*flag.Flag)
|
||||||
|
dst.VisitAll(func(f *flag.Flag) {
|
||||||
|
m[f.Name] = f
|
||||||
|
})
|
||||||
|
dst.Visit(func(f *flag.Flag) {
|
||||||
|
delete(m, f.Name) // overridden
|
||||||
|
})
|
||||||
|
|
||||||
|
for name, f := range m {
|
||||||
|
v, ok := p.Get(name)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := f.Value.Set(v); err != nil {
|
||||||
|
ErrorHandler(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
395
vendor/github.com/magiconair/properties/lex.go
generated
vendored
Normal file
395
vendor/github.com/magiconair/properties/lex.go
generated
vendored
Normal file
|
@ -0,0 +1,395 @@
|
||||||
|
// Copyright 2013-2022 Frank Schroeder. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
//
|
||||||
|
// Parts of the lexer are from the template/text/parser package
|
||||||
|
// For these parts the following applies:
|
||||||
|
//
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file of the go 1.2
|
||||||
|
// distribution.
|
||||||
|
|
||||||
|
package properties
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// item represents a token or text string returned from the scanner.
|
||||||
|
type item struct {
|
||||||
|
typ itemType // The type of this item.
|
||||||
|
pos int // The starting position, in bytes, of this item in the input string.
|
||||||
|
val string // The value of this item.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i item) String() string {
|
||||||
|
switch {
|
||||||
|
case i.typ == itemEOF:
|
||||||
|
return "EOF"
|
||||||
|
case i.typ == itemError:
|
||||||
|
return i.val
|
||||||
|
case len(i.val) > 10:
|
||||||
|
return fmt.Sprintf("%.10q...", i.val)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%q", i.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// itemType identifies the type of lex items.
|
||||||
|
type itemType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
itemError itemType = iota // error occurred; value is text of error
|
||||||
|
itemEOF
|
||||||
|
itemKey // a key
|
||||||
|
itemValue // a value
|
||||||
|
itemComment // a comment
|
||||||
|
)
|
||||||
|
|
||||||
|
// defines a constant for EOF
|
||||||
|
const eof = -1
|
||||||
|
|
||||||
|
// permitted whitespace characters space, FF and TAB
|
||||||
|
const whitespace = " \f\t"
|
||||||
|
|
||||||
|
// stateFn represents the state of the scanner as a function that returns the next state.
|
||||||
|
type stateFn func(*lexer) stateFn
|
||||||
|
|
||||||
|
// lexer holds the state of the scanner.
|
||||||
|
type lexer struct {
|
||||||
|
input string // the string being scanned
|
||||||
|
state stateFn // the next lexing function to enter
|
||||||
|
pos int // current position in the input
|
||||||
|
start int // start position of this item
|
||||||
|
width int // width of last rune read from input
|
||||||
|
lastPos int // position of most recent item returned by nextItem
|
||||||
|
runes []rune // scanned runes for this item
|
||||||
|
items chan item // channel of scanned items
|
||||||
|
}
|
||||||
|
|
||||||
|
// next returns the next rune in the input.
|
||||||
|
func (l *lexer) next() rune {
|
||||||
|
if l.pos >= len(l.input) {
|
||||||
|
l.width = 0
|
||||||
|
return eof
|
||||||
|
}
|
||||||
|
r, w := utf8.DecodeRuneInString(l.input[l.pos:])
|
||||||
|
l.width = w
|
||||||
|
l.pos += l.width
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// peek returns but does not consume the next rune in the input.
|
||||||
|
func (l *lexer) peek() rune {
|
||||||
|
r := l.next()
|
||||||
|
l.backup()
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// backup steps back one rune. Can only be called once per call of next.
|
||||||
|
func (l *lexer) backup() {
|
||||||
|
l.pos -= l.width
|
||||||
|
}
|
||||||
|
|
||||||
|
// emit passes an item back to the client.
|
||||||
|
func (l *lexer) emit(t itemType) {
|
||||||
|
i := item{t, l.start, string(l.runes)}
|
||||||
|
l.items <- i
|
||||||
|
l.start = l.pos
|
||||||
|
l.runes = l.runes[:0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore skips over the pending input before this point.
|
||||||
|
func (l *lexer) ignore() {
|
||||||
|
l.start = l.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// appends the rune to the current value
|
||||||
|
func (l *lexer) appendRune(r rune) {
|
||||||
|
l.runes = append(l.runes, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// accept consumes the next rune if it's from the valid set.
|
||||||
|
func (l *lexer) accept(valid string) bool {
|
||||||
|
if strings.ContainsRune(valid, l.next()) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
l.backup()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// acceptRun consumes a run of runes from the valid set.
|
||||||
|
func (l *lexer) acceptRun(valid string) {
|
||||||
|
for strings.ContainsRune(valid, l.next()) {
|
||||||
|
}
|
||||||
|
l.backup()
|
||||||
|
}
|
||||||
|
|
||||||
|
// lineNumber reports which line we're on, based on the position of
|
||||||
|
// the previous item returned by nextItem. Doing it this way
|
||||||
|
// means we don't have to worry about peek double counting.
|
||||||
|
func (l *lexer) lineNumber() int {
|
||||||
|
return 1 + strings.Count(l.input[:l.lastPos], "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// errorf returns an error token and terminates the scan by passing
|
||||||
|
// back a nil pointer that will be the next state, terminating l.nextItem.
|
||||||
|
func (l *lexer) errorf(format string, args ...interface{}) stateFn {
|
||||||
|
l.items <- item{itemError, l.start, fmt.Sprintf(format, args...)}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// nextItem returns the next item from the input.
|
||||||
|
func (l *lexer) nextItem() item {
|
||||||
|
i := <-l.items
|
||||||
|
l.lastPos = i.pos
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// lex creates a new scanner for the input string.
|
||||||
|
func lex(input string) *lexer {
|
||||||
|
l := &lexer{
|
||||||
|
input: input,
|
||||||
|
items: make(chan item),
|
||||||
|
runes: make([]rune, 0, 32),
|
||||||
|
}
|
||||||
|
go l.run()
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// run runs the state machine for the lexer.
|
||||||
|
func (l *lexer) run() {
|
||||||
|
for l.state = lexBeforeKey(l); l.state != nil; {
|
||||||
|
l.state = l.state(l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// state functions
|
||||||
|
|
||||||
|
// lexBeforeKey scans until a key begins.
|
||||||
|
func lexBeforeKey(l *lexer) stateFn {
|
||||||
|
switch r := l.next(); {
|
||||||
|
case isEOF(r):
|
||||||
|
l.emit(itemEOF)
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case isEOL(r):
|
||||||
|
l.ignore()
|
||||||
|
return lexBeforeKey
|
||||||
|
|
||||||
|
case isComment(r):
|
||||||
|
return lexComment
|
||||||
|
|
||||||
|
case isWhitespace(r):
|
||||||
|
l.ignore()
|
||||||
|
return lexBeforeKey
|
||||||
|
|
||||||
|
default:
|
||||||
|
l.backup()
|
||||||
|
return lexKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexComment scans a comment line. The comment character has already been scanned.
|
||||||
|
func lexComment(l *lexer) stateFn {
|
||||||
|
l.acceptRun(whitespace)
|
||||||
|
l.ignore()
|
||||||
|
for {
|
||||||
|
switch r := l.next(); {
|
||||||
|
case isEOF(r):
|
||||||
|
l.ignore()
|
||||||
|
l.emit(itemEOF)
|
||||||
|
return nil
|
||||||
|
case isEOL(r):
|
||||||
|
l.emit(itemComment)
|
||||||
|
return lexBeforeKey
|
||||||
|
default:
|
||||||
|
l.appendRune(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexKey scans the key up to a delimiter
|
||||||
|
func lexKey(l *lexer) stateFn {
|
||||||
|
var r rune
|
||||||
|
|
||||||
|
Loop:
|
||||||
|
for {
|
||||||
|
switch r = l.next(); {
|
||||||
|
|
||||||
|
case isEscape(r):
|
||||||
|
err := l.scanEscapeSequence()
|
||||||
|
if err != nil {
|
||||||
|
return l.errorf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
case isEndOfKey(r):
|
||||||
|
l.backup()
|
||||||
|
break Loop
|
||||||
|
|
||||||
|
case isEOF(r):
|
||||||
|
break Loop
|
||||||
|
|
||||||
|
default:
|
||||||
|
l.appendRune(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(l.runes) > 0 {
|
||||||
|
l.emit(itemKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isEOF(r) {
|
||||||
|
l.emit(itemEOF)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return lexBeforeValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexBeforeValue scans the delimiter between key and value.
|
||||||
|
// Leading and trailing whitespace is ignored.
|
||||||
|
// We expect to be just after the key.
|
||||||
|
func lexBeforeValue(l *lexer) stateFn {
|
||||||
|
l.acceptRun(whitespace)
|
||||||
|
l.accept(":=")
|
||||||
|
l.acceptRun(whitespace)
|
||||||
|
l.ignore()
|
||||||
|
return lexValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexValue scans text until the end of the line. We expect to be just after the delimiter.
|
||||||
|
func lexValue(l *lexer) stateFn {
|
||||||
|
for {
|
||||||
|
switch r := l.next(); {
|
||||||
|
case isEscape(r):
|
||||||
|
if isEOL(l.peek()) {
|
||||||
|
l.next()
|
||||||
|
l.acceptRun(whitespace)
|
||||||
|
} else {
|
||||||
|
err := l.scanEscapeSequence()
|
||||||
|
if err != nil {
|
||||||
|
return l.errorf(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case isEOL(r):
|
||||||
|
l.emit(itemValue)
|
||||||
|
l.ignore()
|
||||||
|
return lexBeforeKey
|
||||||
|
|
||||||
|
case isEOF(r):
|
||||||
|
l.emit(itemValue)
|
||||||
|
l.emit(itemEOF)
|
||||||
|
return nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
l.appendRune(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanEscapeSequence scans either one of the escaped characters
|
||||||
|
// or a unicode literal. We expect to be after the escape character.
|
||||||
|
func (l *lexer) scanEscapeSequence() error {
|
||||||
|
switch r := l.next(); {
|
||||||
|
|
||||||
|
case isEscapedCharacter(r):
|
||||||
|
l.appendRune(decodeEscapedCharacter(r))
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case atUnicodeLiteral(r):
|
||||||
|
return l.scanUnicodeLiteral()
|
||||||
|
|
||||||
|
case isEOF(r):
|
||||||
|
return fmt.Errorf("premature EOF")
|
||||||
|
|
||||||
|
// silently drop the escape character and append the rune as is
|
||||||
|
default:
|
||||||
|
l.appendRune(r)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// scans a unicode literal in the form \uXXXX. We expect to be after the \u.
|
||||||
|
func (l *lexer) scanUnicodeLiteral() error {
|
||||||
|
// scan the digits
|
||||||
|
d := make([]rune, 4)
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
d[i] = l.next()
|
||||||
|
if d[i] == eof || !strings.ContainsRune("0123456789abcdefABCDEF", d[i]) {
|
||||||
|
return fmt.Errorf("invalid unicode literal")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode the digits into a rune
|
||||||
|
r, err := strconv.ParseInt(string(d), 16, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
l.appendRune(rune(r))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeEscapedCharacter returns the unescaped rune. We expect to be after the escape character.
|
||||||
|
func decodeEscapedCharacter(r rune) rune {
|
||||||
|
switch r {
|
||||||
|
case 'f':
|
||||||
|
return '\f'
|
||||||
|
case 'n':
|
||||||
|
return '\n'
|
||||||
|
case 'r':
|
||||||
|
return '\r'
|
||||||
|
case 't':
|
||||||
|
return '\t'
|
||||||
|
default:
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// atUnicodeLiteral reports whether we are at a unicode literal.
|
||||||
|
// The escape character has already been consumed.
|
||||||
|
func atUnicodeLiteral(r rune) bool {
|
||||||
|
return r == 'u'
|
||||||
|
}
|
||||||
|
|
||||||
|
// isComment reports whether we are at the start of a comment.
|
||||||
|
func isComment(r rune) bool {
|
||||||
|
return r == '#' || r == '!'
|
||||||
|
}
|
||||||
|
|
||||||
|
// isEndOfKey reports whether the rune terminates the current key.
|
||||||
|
func isEndOfKey(r rune) bool {
|
||||||
|
return strings.ContainsRune(" \f\t\r\n:=", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isEOF reports whether we are at EOF.
|
||||||
|
func isEOF(r rune) bool {
|
||||||
|
return r == eof
|
||||||
|
}
|
||||||
|
|
||||||
|
// isEOL reports whether we are at a new line character.
|
||||||
|
func isEOL(r rune) bool {
|
||||||
|
return r == '\n' || r == '\r'
|
||||||
|
}
|
||||||
|
|
||||||
|
// isEscape reports whether the rune is the escape character which
|
||||||
|
// prefixes unicode literals and other escaped characters.
|
||||||
|
func isEscape(r rune) bool {
|
||||||
|
return r == '\\'
|
||||||
|
}
|
||||||
|
|
||||||
|
// isEscapedCharacter reports whether we are at one of the characters that need escaping.
|
||||||
|
// The escape character has already been consumed.
|
||||||
|
func isEscapedCharacter(r rune) bool {
|
||||||
|
return strings.ContainsRune(" :=fnrt", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isWhitespace reports whether the rune is a whitespace character.
|
||||||
|
func isWhitespace(r rune) bool {
|
||||||
|
return strings.ContainsRune(whitespace, r)
|
||||||
|
}
|
293
vendor/github.com/magiconair/properties/load.go
generated
vendored
Normal file
293
vendor/github.com/magiconair/properties/load.go
generated
vendored
Normal file
|
@ -0,0 +1,293 @@
|
||||||
|
// Copyright 2013-2022 Frank Schroeder. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package properties
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Encoding specifies encoding of the input data.
|
||||||
|
type Encoding uint
|
||||||
|
|
||||||
|
const (
|
||||||
|
// utf8Default is a private placeholder for the zero value of Encoding to
|
||||||
|
// ensure that it has the correct meaning. UTF8 is the default encoding but
|
||||||
|
// was assigned a non-zero value which cannot be changed without breaking
|
||||||
|
// existing code. Clients should continue to use the public constants.
|
||||||
|
utf8Default Encoding = iota
|
||||||
|
|
||||||
|
// UTF8 interprets the input data as UTF-8.
|
||||||
|
UTF8
|
||||||
|
|
||||||
|
// ISO_8859_1 interprets the input data as ISO-8859-1.
|
||||||
|
ISO_8859_1
|
||||||
|
)
|
||||||
|
|
||||||
|
type Loader struct {
|
||||||
|
// Encoding determines how the data from files and byte buffers
|
||||||
|
// is interpreted. For URLs the Content-Type header is used
|
||||||
|
// to determine the encoding of the data.
|
||||||
|
Encoding Encoding
|
||||||
|
|
||||||
|
// DisableExpansion configures the property expansion of the
|
||||||
|
// returned property object. When set to true, the property values
|
||||||
|
// will not be expanded and the Property object will not be checked
|
||||||
|
// for invalid expansion expressions.
|
||||||
|
DisableExpansion bool
|
||||||
|
|
||||||
|
// IgnoreMissing configures whether missing files or URLs which return
|
||||||
|
// 404 are reported as errors. When set to true, missing files and 404
|
||||||
|
// status codes are not reported as errors.
|
||||||
|
IgnoreMissing bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load reads a buffer into a Properties struct.
|
||||||
|
func (l *Loader) LoadBytes(buf []byte) (*Properties, error) {
|
||||||
|
return l.loadBytes(buf, l.Encoding)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadAll reads the content of multiple URLs or files in the given order into
|
||||||
|
// a Properties struct. If IgnoreMissing is true then a 404 status code or
|
||||||
|
// missing file will not be reported as error. Encoding sets the encoding for
|
||||||
|
// files. For the URLs see LoadURL for the Content-Type header and the
|
||||||
|
// encoding.
|
||||||
|
func (l *Loader) LoadAll(names []string) (*Properties, error) {
|
||||||
|
all := NewProperties()
|
||||||
|
for _, name := range names {
|
||||||
|
n, err := expandName(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var p *Properties
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(n, "http://"):
|
||||||
|
p, err = l.LoadURL(n)
|
||||||
|
case strings.HasPrefix(n, "https://"):
|
||||||
|
p, err = l.LoadURL(n)
|
||||||
|
default:
|
||||||
|
p, err = l.LoadFile(n)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
all.Merge(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
all.DisableExpansion = l.DisableExpansion
|
||||||
|
if all.DisableExpansion {
|
||||||
|
return all, nil
|
||||||
|
}
|
||||||
|
return all, all.check()
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadFile reads a file into a Properties struct.
|
||||||
|
// If IgnoreMissing is true then a missing file will not be
|
||||||
|
// reported as error.
|
||||||
|
func (l *Loader) LoadFile(filename string) (*Properties, error) {
|
||||||
|
data, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
if l.IgnoreMissing && os.IsNotExist(err) {
|
||||||
|
LogPrintf("properties: %s not found. skipping", filename)
|
||||||
|
return NewProperties(), nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return l.loadBytes(data, l.Encoding)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadURL reads the content of the URL into a Properties struct.
|
||||||
|
//
|
||||||
|
// The encoding is determined via the Content-Type header which
|
||||||
|
// should be set to 'text/plain'. If the 'charset' parameter is
|
||||||
|
// missing, 'iso-8859-1' or 'latin1' the encoding is set to
|
||||||
|
// ISO-8859-1. If the 'charset' parameter is set to 'utf-8' the
|
||||||
|
// encoding is set to UTF-8. A missing content type header is
|
||||||
|
// interpreted as 'text/plain; charset=utf-8'.
|
||||||
|
func (l *Loader) LoadURL(url string) (*Properties, error) {
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("properties: error fetching %q. %s", url, err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode == 404 && l.IgnoreMissing {
|
||||||
|
LogPrintf("properties: %s returned %d. skipping", url, resp.StatusCode)
|
||||||
|
return NewProperties(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
return nil, fmt.Errorf("properties: %s returned %d", url, resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("properties: %s error reading response. %s", url, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ct := resp.Header.Get("Content-Type")
|
||||||
|
ct = strings.Join(strings.Fields(ct), "")
|
||||||
|
var enc Encoding
|
||||||
|
switch strings.ToLower(ct) {
|
||||||
|
case "text/plain", "text/plain;charset=iso-8859-1", "text/plain;charset=latin1":
|
||||||
|
enc = ISO_8859_1
|
||||||
|
case "", "text/plain;charset=utf-8":
|
||||||
|
enc = UTF8
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("properties: invalid content type %s", ct)
|
||||||
|
}
|
||||||
|
|
||||||
|
return l.loadBytes(body, enc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Loader) loadBytes(buf []byte, enc Encoding) (*Properties, error) {
|
||||||
|
p, err := parse(convert(buf, enc))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p.DisableExpansion = l.DisableExpansion
|
||||||
|
if p.DisableExpansion {
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
return p, p.check()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load reads a buffer into a Properties struct.
|
||||||
|
func Load(buf []byte, enc Encoding) (*Properties, error) {
|
||||||
|
l := &Loader{Encoding: enc}
|
||||||
|
return l.LoadBytes(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadString reads an UTF8 string into a properties struct.
|
||||||
|
func LoadString(s string) (*Properties, error) {
|
||||||
|
l := &Loader{Encoding: UTF8}
|
||||||
|
return l.LoadBytes([]byte(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadMap creates a new Properties struct from a string map.
|
||||||
|
func LoadMap(m map[string]string) *Properties {
|
||||||
|
p := NewProperties()
|
||||||
|
for k, v := range m {
|
||||||
|
p.Set(k, v)
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadFile reads a file into a Properties struct.
|
||||||
|
func LoadFile(filename string, enc Encoding) (*Properties, error) {
|
||||||
|
l := &Loader{Encoding: enc}
|
||||||
|
return l.LoadAll([]string{filename})
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadFiles reads multiple files in the given order into
|
||||||
|
// a Properties struct. If 'ignoreMissing' is true then
|
||||||
|
// non-existent files will not be reported as error.
|
||||||
|
func LoadFiles(filenames []string, enc Encoding, ignoreMissing bool) (*Properties, error) {
|
||||||
|
l := &Loader{Encoding: enc, IgnoreMissing: ignoreMissing}
|
||||||
|
return l.LoadAll(filenames)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadURL reads the content of the URL into a Properties struct.
|
||||||
|
// See Loader#LoadURL for details.
|
||||||
|
func LoadURL(url string) (*Properties, error) {
|
||||||
|
l := &Loader{Encoding: UTF8}
|
||||||
|
return l.LoadAll([]string{url})
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadURLs reads the content of multiple URLs in the given order into a
|
||||||
|
// Properties struct. If IgnoreMissing is true then a 404 status code will
|
||||||
|
// not be reported as error. See Loader#LoadURL for the Content-Type header
|
||||||
|
// and the encoding.
|
||||||
|
func LoadURLs(urls []string, ignoreMissing bool) (*Properties, error) {
|
||||||
|
l := &Loader{Encoding: UTF8, IgnoreMissing: ignoreMissing}
|
||||||
|
return l.LoadAll(urls)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadAll reads the content of multiple URLs or files in the given order into a
|
||||||
|
// Properties struct. If 'ignoreMissing' is true then a 404 status code or missing file will
|
||||||
|
// not be reported as error. Encoding sets the encoding for files. For the URLs please see
|
||||||
|
// LoadURL for the Content-Type header and the encoding.
|
||||||
|
func LoadAll(names []string, enc Encoding, ignoreMissing bool) (*Properties, error) {
|
||||||
|
l := &Loader{Encoding: enc, IgnoreMissing: ignoreMissing}
|
||||||
|
return l.LoadAll(names)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustLoadString reads an UTF8 string into a Properties struct and
|
||||||
|
// panics on error.
|
||||||
|
func MustLoadString(s string) *Properties {
|
||||||
|
return must(LoadString(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustLoadFile reads a file into a Properties struct and
|
||||||
|
// panics on error.
|
||||||
|
func MustLoadFile(filename string, enc Encoding) *Properties {
|
||||||
|
return must(LoadFile(filename, enc))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustLoadFiles reads multiple files in the given order into
|
||||||
|
// a Properties struct and panics on error. If 'ignoreMissing'
|
||||||
|
// is true then non-existent files will not be reported as error.
|
||||||
|
func MustLoadFiles(filenames []string, enc Encoding, ignoreMissing bool) *Properties {
|
||||||
|
return must(LoadFiles(filenames, enc, ignoreMissing))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustLoadURL reads the content of a URL into a Properties struct and
|
||||||
|
// panics on error.
|
||||||
|
func MustLoadURL(url string) *Properties {
|
||||||
|
return must(LoadURL(url))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustLoadURLs reads the content of multiple URLs in the given order into a
|
||||||
|
// Properties struct and panics on error. If 'ignoreMissing' is true then a 404
|
||||||
|
// status code will not be reported as error.
|
||||||
|
func MustLoadURLs(urls []string, ignoreMissing bool) *Properties {
|
||||||
|
return must(LoadURLs(urls, ignoreMissing))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustLoadAll reads the content of multiple URLs or files in the given order into a
|
||||||
|
// Properties struct. If 'ignoreMissing' is true then a 404 status code or missing file will
|
||||||
|
// not be reported as error. Encoding sets the encoding for files. For the URLs please see
|
||||||
|
// LoadURL for the Content-Type header and the encoding. It panics on error.
|
||||||
|
func MustLoadAll(names []string, enc Encoding, ignoreMissing bool) *Properties {
|
||||||
|
return must(LoadAll(names, enc, ignoreMissing))
|
||||||
|
}
|
||||||
|
|
||||||
|
func must(p *Properties, err error) *Properties {
|
||||||
|
if err != nil {
|
||||||
|
ErrorHandler(err)
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// expandName expands ${ENV_VAR} expressions in a name.
|
||||||
|
// If the environment variable does not exist then it will be replaced
|
||||||
|
// with an empty string. Malformed expressions like "${ENV_VAR" will
|
||||||
|
// be reported as error.
|
||||||
|
func expandName(name string) (string, error) {
|
||||||
|
return expand(name, []string{}, "${", "}", make(map[string]string))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interprets a byte buffer either as an ISO-8859-1 or UTF-8 encoded string.
|
||||||
|
// For ISO-8859-1 we can convert each byte straight into a rune since the
|
||||||
|
// first 256 unicode code points cover ISO-8859-1.
|
||||||
|
func convert(buf []byte, enc Encoding) string {
|
||||||
|
switch enc {
|
||||||
|
case utf8Default, UTF8:
|
||||||
|
return string(buf)
|
||||||
|
case ISO_8859_1:
|
||||||
|
runes := make([]rune, len(buf))
|
||||||
|
for i, b := range buf {
|
||||||
|
runes[i] = rune(b)
|
||||||
|
}
|
||||||
|
return string(runes)
|
||||||
|
default:
|
||||||
|
ErrorHandler(fmt.Errorf("unsupported encoding %v", enc))
|
||||||
|
}
|
||||||
|
panic("ErrorHandler should exit")
|
||||||
|
}
|
86
vendor/github.com/magiconair/properties/parser.go
generated
vendored
Normal file
86
vendor/github.com/magiconair/properties/parser.go
generated
vendored
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
// Copyright 2013-2022 Frank Schroeder. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package properties
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
type parser struct {
|
||||||
|
lex *lexer
|
||||||
|
}
|
||||||
|
|
||||||
|
func parse(input string) (properties *Properties, err error) {
|
||||||
|
p := &parser{lex: lex(input)}
|
||||||
|
defer p.recover(&err)
|
||||||
|
|
||||||
|
properties = NewProperties()
|
||||||
|
key := ""
|
||||||
|
comments := []string{}
|
||||||
|
|
||||||
|
for {
|
||||||
|
token := p.expectOneOf(itemComment, itemKey, itemEOF)
|
||||||
|
switch token.typ {
|
||||||
|
case itemEOF:
|
||||||
|
goto done
|
||||||
|
case itemComment:
|
||||||
|
comments = append(comments, token.val)
|
||||||
|
continue
|
||||||
|
case itemKey:
|
||||||
|
key = token.val
|
||||||
|
if _, ok := properties.m[key]; !ok {
|
||||||
|
properties.k = append(properties.k, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
token = p.expectOneOf(itemValue, itemEOF)
|
||||||
|
if len(comments) > 0 {
|
||||||
|
properties.c[key] = comments
|
||||||
|
comments = []string{}
|
||||||
|
}
|
||||||
|
switch token.typ {
|
||||||
|
case itemEOF:
|
||||||
|
properties.m[key] = ""
|
||||||
|
goto done
|
||||||
|
case itemValue:
|
||||||
|
properties.m[key] = token.val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
done:
|
||||||
|
return properties, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) errorf(format string, args ...interface{}) {
|
||||||
|
format = fmt.Sprintf("properties: Line %d: %s", p.lex.lineNumber(), format)
|
||||||
|
panic(fmt.Errorf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) expectOneOf(expected ...itemType) (token item) {
|
||||||
|
token = p.lex.nextItem()
|
||||||
|
for _, v := range expected {
|
||||||
|
if token.typ == v {
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.unexpected(token)
|
||||||
|
panic("unexpected token")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) unexpected(token item) {
|
||||||
|
p.errorf(token.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// recover is the handler that turns panics into returns from the top level of Parse.
|
||||||
|
func (p *parser) recover(errp *error) {
|
||||||
|
e := recover()
|
||||||
|
if e != nil {
|
||||||
|
if _, ok := e.(runtime.Error); ok {
|
||||||
|
panic(e)
|
||||||
|
}
|
||||||
|
*errp = e.(error)
|
||||||
|
}
|
||||||
|
}
|
848
vendor/github.com/magiconair/properties/properties.go
generated
vendored
Normal file
848
vendor/github.com/magiconair/properties/properties.go
generated
vendored
Normal file
|
@ -0,0 +1,848 @@
|
||||||
|
// Copyright 2013-2022 Frank Schroeder. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package properties
|
||||||
|
|
||||||
|
// BUG(frank): Set() does not check for invalid unicode literals since this is currently handled by the lexer.
|
||||||
|
// BUG(frank): Write() does not allow to configure the newline character. Therefore, on Windows LF is used.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
const maxExpansionDepth = 64
|
||||||
|
|
||||||
|
// ErrorHandlerFunc defines the type of function which handles failures
|
||||||
|
// of the MustXXX() functions. An error handler function must exit
|
||||||
|
// the application after handling the error.
|
||||||
|
type ErrorHandlerFunc func(error)
|
||||||
|
|
||||||
|
// ErrorHandler is the function which handles failures of the MustXXX()
|
||||||
|
// functions. The default is LogFatalHandler.
|
||||||
|
var ErrorHandler ErrorHandlerFunc = LogFatalHandler
|
||||||
|
|
||||||
|
// LogHandlerFunc defines the function prototype for logging errors.
|
||||||
|
type LogHandlerFunc func(fmt string, args ...interface{})
|
||||||
|
|
||||||
|
// LogPrintf defines a log handler which uses log.Printf.
|
||||||
|
var LogPrintf LogHandlerFunc = log.Printf
|
||||||
|
|
||||||
|
// LogFatalHandler handles the error by logging a fatal error and exiting.
|
||||||
|
func LogFatalHandler(err error) {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PanicHandler handles the error by panicking.
|
||||||
|
func PanicHandler(err error) {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// A Properties contains the key/value pairs from the properties input.
|
||||||
|
// All values are stored in unexpanded form and are expanded at runtime
|
||||||
|
type Properties struct {
|
||||||
|
// Pre-/Postfix for property expansion.
|
||||||
|
Prefix string
|
||||||
|
Postfix string
|
||||||
|
|
||||||
|
// DisableExpansion controls the expansion of properties on Get()
|
||||||
|
// and the check for circular references on Set(). When set to
|
||||||
|
// true Properties behaves like a simple key/value store and does
|
||||||
|
// not check for circular references on Get() or on Set().
|
||||||
|
DisableExpansion bool
|
||||||
|
|
||||||
|
// Stores the key/value pairs
|
||||||
|
m map[string]string
|
||||||
|
|
||||||
|
// Stores the comments per key.
|
||||||
|
c map[string][]string
|
||||||
|
|
||||||
|
// Stores the keys in order of appearance.
|
||||||
|
k []string
|
||||||
|
|
||||||
|
// WriteSeparator specifies the separator of key and value while writing the properties.
|
||||||
|
WriteSeparator string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewProperties creates a new Properties struct with the default
|
||||||
|
// configuration for "${key}" expressions.
|
||||||
|
func NewProperties() *Properties {
|
||||||
|
return &Properties{
|
||||||
|
Prefix: "${",
|
||||||
|
Postfix: "}",
|
||||||
|
m: map[string]string{},
|
||||||
|
c: map[string][]string{},
|
||||||
|
k: []string{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load reads a buffer into the given Properties struct.
|
||||||
|
func (p *Properties) Load(buf []byte, enc Encoding) error {
|
||||||
|
l := &Loader{Encoding: enc, DisableExpansion: p.DisableExpansion}
|
||||||
|
newProperties, err := l.LoadBytes(buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.Merge(newProperties)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the expanded value for the given key if exists.
|
||||||
|
// Otherwise, ok is false.
|
||||||
|
func (p *Properties) Get(key string) (value string, ok bool) {
|
||||||
|
v, ok := p.m[key]
|
||||||
|
if p.DisableExpansion {
|
||||||
|
return v, ok
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
expanded, err := p.expand(key, v)
|
||||||
|
|
||||||
|
// we guarantee that the expanded value is free of
|
||||||
|
// circular references and malformed expressions
|
||||||
|
// so we panic if we still get an error here.
|
||||||
|
if err != nil {
|
||||||
|
ErrorHandler(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return expanded, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustGet returns the expanded value for the given key if exists.
|
||||||
|
// Otherwise, it panics.
|
||||||
|
func (p *Properties) MustGet(key string) string {
|
||||||
|
if v, ok := p.Get(key); ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
ErrorHandler(invalidKeyError(key))
|
||||||
|
panic("ErrorHandler should exit")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// ClearComments removes the comments for all keys.
|
||||||
|
func (p *Properties) ClearComments() {
|
||||||
|
p.c = map[string][]string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// GetComment returns the last comment before the given key or an empty string.
|
||||||
|
func (p *Properties) GetComment(key string) string {
|
||||||
|
comments, ok := p.c[key]
|
||||||
|
if !ok || len(comments) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return comments[len(comments)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// GetComments returns all comments that appeared before the given key or nil.
|
||||||
|
func (p *Properties) GetComments(key string) []string {
|
||||||
|
if comments, ok := p.c[key]; ok {
|
||||||
|
return comments
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// SetComment sets the comment for the key.
|
||||||
|
func (p *Properties) SetComment(key, comment string) {
|
||||||
|
p.c[key] = []string{comment}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// SetComments sets the comments for the key. If the comments are nil then
|
||||||
|
// all comments for this key are deleted.
|
||||||
|
func (p *Properties) SetComments(key string, comments []string) {
|
||||||
|
if comments == nil {
|
||||||
|
delete(p.c, key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.c[key] = comments
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// GetBool checks if the expanded value is one of '1', 'yes',
|
||||||
|
// 'true' or 'on' if the key exists. The comparison is case-insensitive.
|
||||||
|
// If the key does not exist the default value is returned.
|
||||||
|
func (p *Properties) GetBool(key string, def bool) bool {
|
||||||
|
v, err := p.getBool(key)
|
||||||
|
if err != nil {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustGetBool checks if the expanded value is one of '1', 'yes',
|
||||||
|
// 'true' or 'on' if the key exists. The comparison is case-insensitive.
|
||||||
|
// If the key does not exist the function panics.
|
||||||
|
func (p *Properties) MustGetBool(key string) bool {
|
||||||
|
v, err := p.getBool(key)
|
||||||
|
if err != nil {
|
||||||
|
ErrorHandler(err)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Properties) getBool(key string) (value bool, err error) {
|
||||||
|
if v, ok := p.Get(key); ok {
|
||||||
|
return boolVal(v), nil
|
||||||
|
}
|
||||||
|
return false, invalidKeyError(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func boolVal(v string) bool {
|
||||||
|
v = strings.ToLower(v)
|
||||||
|
return v == "1" || v == "true" || v == "yes" || v == "on"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// GetDuration parses the expanded value as an time.Duration (in ns) if the
|
||||||
|
// key exists. If key does not exist or the value cannot be parsed the default
|
||||||
|
// value is returned. In almost all cases you want to use GetParsedDuration().
|
||||||
|
func (p *Properties) GetDuration(key string, def time.Duration) time.Duration {
|
||||||
|
v, err := p.getInt64(key)
|
||||||
|
if err != nil {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
return time.Duration(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustGetDuration parses the expanded value as an time.Duration (in ns) if
|
||||||
|
// the key exists. If key does not exist or the value cannot be parsed the
|
||||||
|
// function panics. In almost all cases you want to use MustGetParsedDuration().
|
||||||
|
func (p *Properties) MustGetDuration(key string) time.Duration {
|
||||||
|
v, err := p.getInt64(key)
|
||||||
|
if err != nil {
|
||||||
|
ErrorHandler(err)
|
||||||
|
}
|
||||||
|
return time.Duration(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// GetParsedDuration parses the expanded value with time.ParseDuration() if the key exists.
|
||||||
|
// If key does not exist or the value cannot be parsed the default
|
||||||
|
// value is returned.
|
||||||
|
func (p *Properties) GetParsedDuration(key string, def time.Duration) time.Duration {
|
||||||
|
s, ok := p.Get(key)
|
||||||
|
if !ok {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
v, err := time.ParseDuration(s)
|
||||||
|
if err != nil {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustGetParsedDuration parses the expanded value with time.ParseDuration() if the key exists.
|
||||||
|
// If key does not exist or the value cannot be parsed the function panics.
|
||||||
|
func (p *Properties) MustGetParsedDuration(key string) time.Duration {
|
||||||
|
s, ok := p.Get(key)
|
||||||
|
if !ok {
|
||||||
|
ErrorHandler(invalidKeyError(key))
|
||||||
|
}
|
||||||
|
v, err := time.ParseDuration(s)
|
||||||
|
if err != nil {
|
||||||
|
ErrorHandler(err)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// GetFloat64 parses the expanded value as a float64 if the key exists.
|
||||||
|
// If key does not exist or the value cannot be parsed the default
|
||||||
|
// value is returned.
|
||||||
|
func (p *Properties) GetFloat64(key string, def float64) float64 {
|
||||||
|
v, err := p.getFloat64(key)
|
||||||
|
if err != nil {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustGetFloat64 parses the expanded value as a float64 if the key exists.
|
||||||
|
// If key does not exist or the value cannot be parsed the function panics.
|
||||||
|
func (p *Properties) MustGetFloat64(key string) float64 {
|
||||||
|
v, err := p.getFloat64(key)
|
||||||
|
if err != nil {
|
||||||
|
ErrorHandler(err)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Properties) getFloat64(key string) (value float64, err error) {
|
||||||
|
if v, ok := p.Get(key); ok {
|
||||||
|
value, err = strconv.ParseFloat(v, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
return 0, invalidKeyError(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// GetInt parses the expanded value as an int if the key exists.
|
||||||
|
// If key does not exist or the value cannot be parsed the default
|
||||||
|
// value is returned. If the value does not fit into an int the
|
||||||
|
// function panics with an out of range error.
|
||||||
|
func (p *Properties) GetInt(key string, def int) int {
|
||||||
|
v, err := p.getInt64(key)
|
||||||
|
if err != nil {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
return intRangeCheck(key, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustGetInt parses the expanded value as an int if the key exists.
|
||||||
|
// If key does not exist or the value cannot be parsed the function panics.
|
||||||
|
// If the value does not fit into an int the function panics with
|
||||||
|
// an out of range error.
|
||||||
|
func (p *Properties) MustGetInt(key string) int {
|
||||||
|
v, err := p.getInt64(key)
|
||||||
|
if err != nil {
|
||||||
|
ErrorHandler(err)
|
||||||
|
}
|
||||||
|
return intRangeCheck(key, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// GetInt64 parses the expanded value as an int64 if the key exists.
|
||||||
|
// If key does not exist or the value cannot be parsed the default
|
||||||
|
// value is returned.
|
||||||
|
func (p *Properties) GetInt64(key string, def int64) int64 {
|
||||||
|
v, err := p.getInt64(key)
|
||||||
|
if err != nil {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustGetInt64 parses the expanded value as an int if the key exists.
|
||||||
|
// If key does not exist or the value cannot be parsed the function panics.
|
||||||
|
func (p *Properties) MustGetInt64(key string) int64 {
|
||||||
|
v, err := p.getInt64(key)
|
||||||
|
if err != nil {
|
||||||
|
ErrorHandler(err)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Properties) getInt64(key string) (value int64, err error) {
|
||||||
|
if v, ok := p.Get(key); ok {
|
||||||
|
value, err = strconv.ParseInt(v, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
return 0, invalidKeyError(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// GetUint parses the expanded value as an uint if the key exists.
|
||||||
|
// If key does not exist or the value cannot be parsed the default
|
||||||
|
// value is returned. If the value does not fit into an int the
|
||||||
|
// function panics with an out of range error.
|
||||||
|
func (p *Properties) GetUint(key string, def uint) uint {
|
||||||
|
v, err := p.getUint64(key)
|
||||||
|
if err != nil {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
return uintRangeCheck(key, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustGetUint parses the expanded value as an int if the key exists.
|
||||||
|
// If key does not exist or the value cannot be parsed the function panics.
|
||||||
|
// If the value does not fit into an int the function panics with
|
||||||
|
// an out of range error.
|
||||||
|
func (p *Properties) MustGetUint(key string) uint {
|
||||||
|
v, err := p.getUint64(key)
|
||||||
|
if err != nil {
|
||||||
|
ErrorHandler(err)
|
||||||
|
}
|
||||||
|
return uintRangeCheck(key, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// GetUint64 parses the expanded value as an uint64 if the key exists.
|
||||||
|
// If key does not exist or the value cannot be parsed the default
|
||||||
|
// value is returned.
|
||||||
|
func (p *Properties) GetUint64(key string, def uint64) uint64 {
|
||||||
|
v, err := p.getUint64(key)
|
||||||
|
if err != nil {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustGetUint64 parses the expanded value as an int if the key exists.
|
||||||
|
// If key does not exist or the value cannot be parsed the function panics.
|
||||||
|
func (p *Properties) MustGetUint64(key string) uint64 {
|
||||||
|
v, err := p.getUint64(key)
|
||||||
|
if err != nil {
|
||||||
|
ErrorHandler(err)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Properties) getUint64(key string) (value uint64, err error) {
|
||||||
|
if v, ok := p.Get(key); ok {
|
||||||
|
value, err = strconv.ParseUint(v, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
return 0, invalidKeyError(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// GetString returns the expanded value for the given key if exists or
|
||||||
|
// the default value otherwise.
|
||||||
|
func (p *Properties) GetString(key, def string) string {
|
||||||
|
if v, ok := p.Get(key); ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustGetString returns the expanded value for the given key if exists or
|
||||||
|
// panics otherwise.
|
||||||
|
func (p *Properties) MustGetString(key string) string {
|
||||||
|
if v, ok := p.Get(key); ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
ErrorHandler(invalidKeyError(key))
|
||||||
|
panic("ErrorHandler should exit")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Filter returns a new properties object which contains all properties
|
||||||
|
// for which the key matches the pattern.
|
||||||
|
func (p *Properties) Filter(pattern string) (*Properties, error) {
|
||||||
|
re, err := regexp.Compile(pattern)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.FilterRegexp(re), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilterRegexp returns a new properties object which contains all properties
|
||||||
|
// for which the key matches the regular expression.
|
||||||
|
func (p *Properties) FilterRegexp(re *regexp.Regexp) *Properties {
|
||||||
|
pp := NewProperties()
|
||||||
|
for _, k := range p.k {
|
||||||
|
if re.MatchString(k) {
|
||||||
|
// TODO(fs): we are ignoring the error which flags a circular reference.
|
||||||
|
// TODO(fs): since we are just copying a subset of keys this cannot happen (fingers crossed)
|
||||||
|
pp.Set(k, p.m[k])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pp
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilterPrefix returns a new properties object with a subset of all keys
|
||||||
|
// with the given prefix.
|
||||||
|
func (p *Properties) FilterPrefix(prefix string) *Properties {
|
||||||
|
pp := NewProperties()
|
||||||
|
for _, k := range p.k {
|
||||||
|
if strings.HasPrefix(k, prefix) {
|
||||||
|
// TODO(fs): we are ignoring the error which flags a circular reference.
|
||||||
|
// TODO(fs): since we are just copying a subset of keys this cannot happen (fingers crossed)
|
||||||
|
pp.Set(k, p.m[k])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pp
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilterStripPrefix returns a new properties object with a subset of all keys
|
||||||
|
// with the given prefix and the prefix removed from the keys.
|
||||||
|
func (p *Properties) FilterStripPrefix(prefix string) *Properties {
|
||||||
|
pp := NewProperties()
|
||||||
|
n := len(prefix)
|
||||||
|
for _, k := range p.k {
|
||||||
|
if len(k) > len(prefix) && strings.HasPrefix(k, prefix) {
|
||||||
|
// TODO(fs): we are ignoring the error which flags a circular reference.
|
||||||
|
// TODO(fs): since we are modifying keys I am not entirely sure whether we can create a circular reference
|
||||||
|
// TODO(fs): this function should probably return an error but the signature is fixed
|
||||||
|
pp.Set(k[n:], p.m[k])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the number of keys.
|
||||||
|
func (p *Properties) Len() int {
|
||||||
|
return len(p.m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keys returns all keys in the same order as in the input.
|
||||||
|
func (p *Properties) Keys() []string {
|
||||||
|
keys := make([]string, len(p.k))
|
||||||
|
copy(keys, p.k)
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets the property key to the corresponding value.
|
||||||
|
// If a value for key existed before then ok is true and prev
|
||||||
|
// contains the previous value. If the value contains a
|
||||||
|
// circular reference or a malformed expression then
|
||||||
|
// an error is returned.
|
||||||
|
// An empty key is silently ignored.
|
||||||
|
func (p *Properties) Set(key, value string) (prev string, ok bool, err error) {
|
||||||
|
if key == "" {
|
||||||
|
return "", false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// if expansion is disabled we allow circular references
|
||||||
|
if p.DisableExpansion {
|
||||||
|
prev, ok = p.Get(key)
|
||||||
|
p.m[key] = value
|
||||||
|
if !ok {
|
||||||
|
p.k = append(p.k, key)
|
||||||
|
}
|
||||||
|
return prev, ok, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// to check for a circular reference we temporarily need
|
||||||
|
// to set the new value. If there is an error then revert
|
||||||
|
// to the previous state. Only if all tests are successful
|
||||||
|
// then we add the key to the p.k list.
|
||||||
|
prev, ok = p.Get(key)
|
||||||
|
p.m[key] = value
|
||||||
|
|
||||||
|
// now check for a circular reference
|
||||||
|
_, err = p.expand(key, value)
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
// revert to the previous state
|
||||||
|
if ok {
|
||||||
|
p.m[key] = prev
|
||||||
|
} else {
|
||||||
|
delete(p.m, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
p.k = append(p.k, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return prev, ok, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetValue sets property key to the default string value
|
||||||
|
// as defined by fmt.Sprintf("%v").
|
||||||
|
func (p *Properties) SetValue(key string, value interface{}) error {
|
||||||
|
_, _, err := p.Set(key, fmt.Sprintf("%v", value))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustSet sets the property key to the corresponding value.
|
||||||
|
// If a value for key existed before then ok is true and prev
|
||||||
|
// contains the previous value. An empty key is silently ignored.
|
||||||
|
func (p *Properties) MustSet(key, value string) (prev string, ok bool) {
|
||||||
|
prev, ok, err := p.Set(key, value)
|
||||||
|
if err != nil {
|
||||||
|
ErrorHandler(err)
|
||||||
|
}
|
||||||
|
return prev, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string of all expanded 'key = value' pairs.
|
||||||
|
func (p *Properties) String() string {
|
||||||
|
var s string
|
||||||
|
for _, key := range p.k {
|
||||||
|
value, _ := p.Get(key)
|
||||||
|
s = fmt.Sprintf("%s%s = %s\n", s, key, value)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort sorts the properties keys in alphabetical order.
|
||||||
|
// This is helpfully before writing the properties.
|
||||||
|
func (p *Properties) Sort() {
|
||||||
|
sort.Strings(p.k)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes all unexpanded 'key = value' pairs to the given writer.
|
||||||
|
// Write returns the number of bytes written and any write error encountered.
|
||||||
|
func (p *Properties) Write(w io.Writer, enc Encoding) (n int, err error) {
|
||||||
|
return p.WriteComment(w, "", enc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteComment writes all unexpanced 'key = value' pairs to the given writer.
|
||||||
|
// If prefix is not empty then comments are written with a blank line and the
|
||||||
|
// given prefix. The prefix should be either "# " or "! " to be compatible with
|
||||||
|
// the properties file format. Otherwise, the properties parser will not be
|
||||||
|
// able to read the file back in. It returns the number of bytes written and
|
||||||
|
// any write error encountered.
|
||||||
|
func (p *Properties) WriteComment(w io.Writer, prefix string, enc Encoding) (n int, err error) {
|
||||||
|
var x int
|
||||||
|
|
||||||
|
for _, key := range p.k {
|
||||||
|
value := p.m[key]
|
||||||
|
|
||||||
|
if prefix != "" {
|
||||||
|
if comments, ok := p.c[key]; ok {
|
||||||
|
// don't print comments if they are all empty
|
||||||
|
allEmpty := true
|
||||||
|
for _, c := range comments {
|
||||||
|
if c != "" {
|
||||||
|
allEmpty = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !allEmpty {
|
||||||
|
// add a blank line between entries but not at the top
|
||||||
|
if len(comments) > 0 && n > 0 {
|
||||||
|
x, err = fmt.Fprintln(w)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n += x
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range comments {
|
||||||
|
x, err = fmt.Fprintf(w, "%s%s\n", prefix, c)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n += x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sep := " = "
|
||||||
|
if p.WriteSeparator != "" {
|
||||||
|
sep = p.WriteSeparator
|
||||||
|
}
|
||||||
|
x, err = fmt.Fprintf(w, "%s%s%s\n", encode(key, " :", enc), sep, encode(value, "", enc))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n += x
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map returns a copy of the properties as a map.
|
||||||
|
func (p *Properties) Map() map[string]string {
|
||||||
|
m := make(map[string]string)
|
||||||
|
for k, v := range p.m {
|
||||||
|
m[k] = v
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilterFunc returns a copy of the properties which includes the values which passed all filters.
|
||||||
|
func (p *Properties) FilterFunc(filters ...func(k, v string) bool) *Properties {
|
||||||
|
pp := NewProperties()
|
||||||
|
outer:
|
||||||
|
for k, v := range p.m {
|
||||||
|
for _, f := range filters {
|
||||||
|
if !f(k, v) {
|
||||||
|
continue outer
|
||||||
|
}
|
||||||
|
pp.Set(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pp
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Delete removes the key and its comments.
|
||||||
|
func (p *Properties) Delete(key string) {
|
||||||
|
delete(p.m, key)
|
||||||
|
delete(p.c, key)
|
||||||
|
newKeys := []string{}
|
||||||
|
for _, k := range p.k {
|
||||||
|
if k != key {
|
||||||
|
newKeys = append(newKeys, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.k = newKeys
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge merges properties, comments and keys from other *Properties into p
|
||||||
|
func (p *Properties) Merge(other *Properties) {
|
||||||
|
for _, k := range other.k {
|
||||||
|
if _, ok := p.m[k]; !ok {
|
||||||
|
p.k = append(p.k, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for k, v := range other.m {
|
||||||
|
p.m[k] = v
|
||||||
|
}
|
||||||
|
for k, v := range other.c {
|
||||||
|
p.c[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// check expands all values and returns an error if a circular reference or
|
||||||
|
// a malformed expression was found.
|
||||||
|
func (p *Properties) check() error {
|
||||||
|
for key, value := range p.m {
|
||||||
|
if _, err := p.expand(key, value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Properties) expand(key, input string) (string, error) {
|
||||||
|
// no pre/postfix -> nothing to expand
|
||||||
|
if p.Prefix == "" && p.Postfix == "" {
|
||||||
|
return input, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return expand(input, []string{key}, p.Prefix, p.Postfix, p.m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// expand recursively expands expressions of '(prefix)key(postfix)' to their corresponding values.
|
||||||
|
// The function keeps track of the keys that were already expanded and stops if it
|
||||||
|
// detects a circular reference or a malformed expression of the form '(prefix)key'.
|
||||||
|
func expand(s string, keys []string, prefix, postfix string, values map[string]string) (string, error) {
|
||||||
|
if len(keys) > maxExpansionDepth {
|
||||||
|
return "", fmt.Errorf("expansion too deep")
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
start := strings.Index(s, prefix)
|
||||||
|
if start == -1 {
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
keyStart := start + len(prefix)
|
||||||
|
keyLen := strings.Index(s[keyStart:], postfix)
|
||||||
|
if keyLen == -1 {
|
||||||
|
return "", fmt.Errorf("malformed expression")
|
||||||
|
}
|
||||||
|
|
||||||
|
end := keyStart + keyLen + len(postfix) - 1
|
||||||
|
key := s[keyStart : keyStart+keyLen]
|
||||||
|
|
||||||
|
// fmt.Printf("s:%q pp:%q start:%d end:%d keyStart:%d keyLen:%d key:%q\n", s, prefix + "..." + postfix, start, end, keyStart, keyLen, key)
|
||||||
|
|
||||||
|
for _, k := range keys {
|
||||||
|
if key == k {
|
||||||
|
var b bytes.Buffer
|
||||||
|
b.WriteString("circular reference in:\n")
|
||||||
|
for _, k1 := range keys {
|
||||||
|
fmt.Fprintf(&b, "%s=%s\n", k1, values[k1])
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf(b.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val, ok := values[key]
|
||||||
|
if !ok {
|
||||||
|
val = os.Getenv(key)
|
||||||
|
}
|
||||||
|
new_val, err := expand(val, append(keys, key), prefix, postfix, values)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
s = s[:start] + new_val + s[end+1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// encode encodes a UTF-8 string to ISO-8859-1 and escapes some characters.
|
||||||
|
func encode(s string, special string, enc Encoding) string {
|
||||||
|
switch enc {
|
||||||
|
case UTF8:
|
||||||
|
return encodeUtf8(s, special)
|
||||||
|
case ISO_8859_1:
|
||||||
|
return encodeIso(s, special)
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unsupported encoding %v", enc))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeUtf8(s string, special string) string {
|
||||||
|
v := ""
|
||||||
|
for pos := 0; pos < len(s); {
|
||||||
|
r, w := utf8.DecodeRuneInString(s[pos:])
|
||||||
|
pos += w
|
||||||
|
v += escape(r, special)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeIso(s string, special string) string {
|
||||||
|
var r rune
|
||||||
|
var w int
|
||||||
|
var v string
|
||||||
|
for pos := 0; pos < len(s); {
|
||||||
|
switch r, w = utf8.DecodeRuneInString(s[pos:]); {
|
||||||
|
case r < 1<<8: // single byte rune -> escape special chars only
|
||||||
|
v += escape(r, special)
|
||||||
|
case r < 1<<16: // two byte rune -> unicode literal
|
||||||
|
v += fmt.Sprintf("\\u%04x", r)
|
||||||
|
default: // more than two bytes per rune -> can't encode
|
||||||
|
v += "?"
|
||||||
|
}
|
||||||
|
pos += w
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func escape(r rune, special string) string {
|
||||||
|
switch r {
|
||||||
|
case '\f':
|
||||||
|
return "\\f"
|
||||||
|
case '\n':
|
||||||
|
return "\\n"
|
||||||
|
case '\r':
|
||||||
|
return "\\r"
|
||||||
|
case '\t':
|
||||||
|
return "\\t"
|
||||||
|
case '\\':
|
||||||
|
return "\\\\"
|
||||||
|
default:
|
||||||
|
if strings.ContainsRune(special, r) {
|
||||||
|
return "\\" + string(r)
|
||||||
|
}
|
||||||
|
return string(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func invalidKeyError(key string) error {
|
||||||
|
return fmt.Errorf("unknown property: %s", key)
|
||||||
|
}
|
31
vendor/github.com/magiconair/properties/rangecheck.go
generated
vendored
Normal file
31
vendor/github.com/magiconair/properties/rangecheck.go
generated
vendored
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright 2013-2022 Frank Schroeder. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package properties
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// make this a var to overwrite it in a test
|
||||||
|
var is32Bit = ^uint(0) == math.MaxUint32
|
||||||
|
|
||||||
|
// intRangeCheck checks if the value fits into the int type and
|
||||||
|
// panics if it does not.
|
||||||
|
func intRangeCheck(key string, v int64) int {
|
||||||
|
if is32Bit && (v < math.MinInt32 || v > math.MaxInt32) {
|
||||||
|
panic(fmt.Sprintf("Value %d for key %s out of range", v, key))
|
||||||
|
}
|
||||||
|
return int(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// uintRangeCheck checks if the value fits into the uint type and
|
||||||
|
// panics if it does not.
|
||||||
|
func uintRangeCheck(key string, v uint64) uint {
|
||||||
|
if is32Bit && v > math.MaxUint32 {
|
||||||
|
panic(fmt.Sprintf("Value %d for key %s out of range", v, key))
|
||||||
|
}
|
||||||
|
return uint(v)
|
||||||
|
}
|
201
vendor/github.com/matttproud/golang_protobuf_extensions/LICENSE
generated
vendored
Normal file
201
vendor/github.com/matttproud/golang_protobuf_extensions/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright {yyyy} {name of copyright owner}
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
1
vendor/github.com/matttproud/golang_protobuf_extensions/NOTICE
generated
vendored
Normal file
1
vendor/github.com/matttproud/golang_protobuf_extensions/NOTICE
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Copyright 2012 Matt T. Proud (matt.proud@gmail.com)
|
1
vendor/github.com/matttproud/golang_protobuf_extensions/pbutil/.gitignore
generated
vendored
Normal file
1
vendor/github.com/matttproud/golang_protobuf_extensions/pbutil/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
cover.dat
|
7
vendor/github.com/matttproud/golang_protobuf_extensions/pbutil/Makefile
generated
vendored
Normal file
7
vendor/github.com/matttproud/golang_protobuf_extensions/pbutil/Makefile
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
all:
|
||||||
|
|
||||||
|
cover:
|
||||||
|
go test -cover -v -coverprofile=cover.dat ./...
|
||||||
|
go tool cover -func cover.dat
|
||||||
|
|
||||||
|
.PHONY: cover
|
75
vendor/github.com/matttproud/golang_protobuf_extensions/pbutil/decode.go
generated
vendored
Normal file
75
vendor/github.com/matttproud/golang_protobuf_extensions/pbutil/decode.go
generated
vendored
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
// Copyright 2013 Matt T. Proud
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package pbutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
var errInvalidVarint = errors.New("invalid varint32 encountered")
|
||||||
|
|
||||||
|
// ReadDelimited decodes a message from the provided length-delimited stream,
|
||||||
|
// where the length is encoded as 32-bit varint prefix to the message body.
|
||||||
|
// It returns the total number of bytes read and any applicable error. This is
|
||||||
|
// roughly equivalent to the companion Java API's
|
||||||
|
// MessageLite#parseDelimitedFrom. As per the reader contract, this function
|
||||||
|
// calls r.Read repeatedly as required until exactly one message including its
|
||||||
|
// prefix is read and decoded (or an error has occurred). The function never
|
||||||
|
// reads more bytes from the stream than required. The function never returns
|
||||||
|
// an error if a message has been read and decoded correctly, even if the end
|
||||||
|
// of the stream has been reached in doing so. In that case, any subsequent
|
||||||
|
// calls return (0, io.EOF).
|
||||||
|
func ReadDelimited(r io.Reader, m proto.Message) (n int, err error) {
|
||||||
|
// Per AbstractParser#parsePartialDelimitedFrom with
|
||||||
|
// CodedInputStream#readRawVarint32.
|
||||||
|
var headerBuf [binary.MaxVarintLen32]byte
|
||||||
|
var bytesRead, varIntBytes int
|
||||||
|
var messageLength uint64
|
||||||
|
for varIntBytes == 0 { // i.e. no varint has been decoded yet.
|
||||||
|
if bytesRead >= len(headerBuf) {
|
||||||
|
return bytesRead, errInvalidVarint
|
||||||
|
}
|
||||||
|
// We have to read byte by byte here to avoid reading more bytes
|
||||||
|
// than required. Each read byte is appended to what we have
|
||||||
|
// read before.
|
||||||
|
newBytesRead, err := r.Read(headerBuf[bytesRead : bytesRead+1])
|
||||||
|
if newBytesRead == 0 {
|
||||||
|
if err != nil {
|
||||||
|
return bytesRead, err
|
||||||
|
}
|
||||||
|
// A Reader should not return (0, nil), but if it does,
|
||||||
|
// it should be treated as no-op (according to the
|
||||||
|
// Reader contract). So let's go on...
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
bytesRead += newBytesRead
|
||||||
|
// Now present everything read so far to the varint decoder and
|
||||||
|
// see if a varint can be decoded already.
|
||||||
|
messageLength, varIntBytes = proto.DecodeVarint(headerBuf[:bytesRead])
|
||||||
|
}
|
||||||
|
|
||||||
|
messageBuf := make([]byte, messageLength)
|
||||||
|
newBytesRead, err := io.ReadFull(r, messageBuf)
|
||||||
|
bytesRead += newBytesRead
|
||||||
|
if err != nil {
|
||||||
|
return bytesRead, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytesRead, proto.Unmarshal(messageBuf, m)
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue