vendor: move vendored sources in-tree
This should make it easier to see changes instead of just a blob
This commit is contained in:
27
vendor/github.com/tailscale/hujson/LICENSE
generated
vendored
Normal file
27
vendor/github.com/tailscale/hujson/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2019 Tailscale Inc. 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.
|
||||
36
vendor/github.com/tailscale/hujson/README.md
generated
vendored
Normal file
36
vendor/github.com/tailscale/hujson/README.md
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
# HuJSON - "Human JSON" ([JWCC](https://nigeltao.github.io/blog/2021/json-with-commas-comments.html))
|
||||
|
||||
[](https://pkg.go.dev/github.com/tailscale/hujson)
|
||||
|
||||
The `github.com/tailscale/hujson` package implements
|
||||
the [JWCC](https://nigeltao.github.io/blog/2021/json-with-commas-comments.html) extension
|
||||
of [standard JSON](https://datatracker.ietf.org/doc/html/rfc8259).
|
||||
|
||||
The `JWCC` format permits two things over standard JSON:
|
||||
|
||||
1. C-style line comments and block comments intermixed with whitespace,
|
||||
2. allows trailing commas after the last member/element in an object/array.
|
||||
|
||||
All JSON is valid JWCC.
|
||||
|
||||
For details, see the JWCC docs at:
|
||||
|
||||
https://nigeltao.github.io/blog/2021/json-with-commas-comments.html
|
||||
|
||||
## Visual Studio Code association
|
||||
|
||||
Visual Studio Code supports a similar `jsonc` (JSON with comments) format. To
|
||||
treat all `*.hujson` files as `jsonc` with trailing commas allowed, you can add
|
||||
the following snippet to your Visual Studio Code configuration:
|
||||
|
||||
```json
|
||||
"files.associations": {
|
||||
"*.hujson": "jsonc"
|
||||
},
|
||||
"json.schemas": [{
|
||||
"fileMatch": ["*.hujson"],
|
||||
"schema": {
|
||||
"allowTrailingCommas": true
|
||||
}
|
||||
}]
|
||||
```
|
||||
106
vendor/github.com/tailscale/hujson/find.go
generated
vendored
Normal file
106
vendor/github.com/tailscale/hujson/find.go
generated
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
// Copyright (c) 2021 Tailscale Inc & 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 hujson
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var errNotFound = fmt.Errorf("value not found")
|
||||
|
||||
// Find locates the value specified by the JSON pointer (see RFC 6901).
|
||||
// It returns nil if the value does not exist or the pointer is invalid.
|
||||
// If a JSON object has multiple members matching a given name,
|
||||
// the first is returned. Object names are matched exactly,
|
||||
// rather than with a case-insensitive match.
|
||||
func (v *Value) Find(ptr string) *Value {
|
||||
if s, err := v.find(findState{pointer: ptr}); err == nil {
|
||||
return s.value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type findState struct {
|
||||
pointer string // pointer[:offset] is the current value, pointer[offset:] is the remainder
|
||||
offset int
|
||||
|
||||
parent composite // nil for root pointer
|
||||
name string // name into parent to obtain current value
|
||||
idx int // idx into parent to obtain current value
|
||||
value *Value // the current value
|
||||
}
|
||||
|
||||
func (v *Value) find(s findState) (findState, error) {
|
||||
// An empty pointer denotes the value itself.
|
||||
s.value = v
|
||||
if s.pointer[s.offset:] == "" {
|
||||
return s, nil
|
||||
}
|
||||
comp, ok := v.Value.(composite)
|
||||
if !ok {
|
||||
return s, fmt.Errorf("invalid pointer: cannot index into literal at %v", s.pointer[:s.offset])
|
||||
}
|
||||
|
||||
// There must be one or more fragments.
|
||||
s.parent, s.idx, s.name = nil, 0, ""
|
||||
if !strings.HasPrefix(s.pointer[s.offset:], "/") {
|
||||
return s, fmt.Errorf("invalid pointer: lacks a forward slash prefix")
|
||||
}
|
||||
n := len("/")
|
||||
if i := strings.IndexByte(s.pointer[s.offset+n:], '/'); i >= 0 {
|
||||
n += i
|
||||
} else {
|
||||
n = len(s.pointer) - s.offset
|
||||
}
|
||||
s.offset += n
|
||||
|
||||
// Unescape the name if necessary (section 4).
|
||||
name := s.pointer[s.offset-n : s.offset]
|
||||
if strings.IndexByte(name, '~') >= 0 {
|
||||
name = strings.ReplaceAll(name, "~1", "/")
|
||||
name = strings.ReplaceAll(name, "~0", "~")
|
||||
}
|
||||
name = name[len("/"):]
|
||||
|
||||
// Index into the object or array.
|
||||
s.parent, s.name, s.idx = comp, name, comp.length()
|
||||
switch comp := v.Value.(type) {
|
||||
case *Object:
|
||||
for i, m := range comp.Members {
|
||||
if m.Name.Value.(Literal).equalString(name) {
|
||||
s.idx = i
|
||||
return comp.Members[i].Value.find(s)
|
||||
}
|
||||
}
|
||||
case *Array:
|
||||
if name == "-" {
|
||||
return s, errNotFound
|
||||
}
|
||||
i, err := strconv.ParseUint(name, 10, 0)
|
||||
if err != nil || (i == 0 && name != "0") {
|
||||
return s, fmt.Errorf("invalid array index: %s", name)
|
||||
}
|
||||
if i < uint64(len(comp.Elements)) {
|
||||
s.idx = int(i)
|
||||
return comp.Elements[i].find(s)
|
||||
}
|
||||
}
|
||||
return s, errNotFound
|
||||
}
|
||||
|
||||
func (b Literal) equalString(s string) bool {
|
||||
// Fast-path: Assume there are no escape characters.
|
||||
if len(b) >= 2 && b[0] == '"' && b[len(b)-1] == '"' && bytes.IndexByte(b, '\\') < 0 {
|
||||
return string(b[len(`"`):len(b)-len(`"`)]) == s
|
||||
}
|
||||
// Slow-path: Unescape the string and then compare it.
|
||||
// TODO(dsnet): Implement allocation-free comparison.
|
||||
var s2 string
|
||||
return json.Unmarshal(b, &s2) == nil && s == s2
|
||||
}
|
||||
602
vendor/github.com/tailscale/hujson/format.go
generated
vendored
Normal file
602
vendor/github.com/tailscale/hujson/format.go
generated
vendored
Normal file
@@ -0,0 +1,602 @@
|
||||
// Copyright (c) 2021 Tailscale Inc & 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 hujson
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// Standardize strips any features specific to HuJSON from b,
|
||||
// making it compliant with standard JSON per RFC 8259.
|
||||
// All comments and trailing commas are replaced with a space character
|
||||
// in order to preserve the original line numbers and byte offsets.
|
||||
// If an error is encountered, then b is returned as is along with the error.
|
||||
func Standardize(b []byte) ([]byte, error) {
|
||||
ast, err := Parse(b)
|
||||
if err != nil {
|
||||
return b, err
|
||||
}
|
||||
ast.Standardize()
|
||||
return ast.Pack(), nil
|
||||
}
|
||||
|
||||
// Minimize removes all whitespace, comments, and trailing commas from b,
|
||||
// making it compliant with standard JSON per RFC 8259.
|
||||
// If an error is encountered, then b is returned as is along with the error.
|
||||
func Minimize(b []byte) ([]byte, error) {
|
||||
ast, err := Parse(b)
|
||||
if err != nil {
|
||||
return b, err
|
||||
}
|
||||
ast.Minimize()
|
||||
return ast.Pack(), nil
|
||||
}
|
||||
|
||||
// Format formats b according to some opinionated heuristics for
|
||||
// how HuJSON should look. The exact output may change over time.
|
||||
// It is the equivalent of `go fmt` but for HuJSON.
|
||||
//
|
||||
// If the input is standard JSON, then the output will remain standard.
|
||||
// Format is idempotent such that formatting already formatted HuJSON
|
||||
// results in no changes.
|
||||
// If an error is encountered, then b is returned as is along with the error.
|
||||
func Format(b []byte) ([]byte, error) {
|
||||
ast, err := Parse(b)
|
||||
if err != nil {
|
||||
return b, err
|
||||
}
|
||||
ast.Format()
|
||||
return ast.Pack(), nil
|
||||
}
|
||||
|
||||
const punchCardWidth = 80
|
||||
|
||||
var (
|
||||
newline = []byte("\n")
|
||||
twoNewlines = []byte("\n\n")
|
||||
endlineWindows = []byte("\r\n")
|
||||
endlineMacOSX = []byte("\n\r")
|
||||
carriageReturn = []byte("\r")
|
||||
space = []byte(" ")
|
||||
)
|
||||
|
||||
// Format formats the value according to some opinionated heuristics for
|
||||
// how HuJSON should look. The exact output may change over time.
|
||||
// It is the equivalent of `go fmt` but for HuJSON.
|
||||
//
|
||||
// If the input is standard JSON, then the output will remain standard.
|
||||
// Format is idempotent such that formatting already formatted HuJSON
|
||||
// results in no changes.
|
||||
func (v *Value) Format() {
|
||||
// Format leading extra.
|
||||
v.BeforeExtra.format(0, formatOptions{})
|
||||
v.BeforeExtra = v.BeforeExtra[consumeWhitespace(v.BeforeExtra):] // never has leading whitespace
|
||||
// Format the value.
|
||||
needExpand := make(map[composite]bool)
|
||||
isStandard := v.IsStandard()
|
||||
v.normalize()
|
||||
v.expandComposites(needExpand)
|
||||
v.formatWhitespace(0, needExpand, isStandard)
|
||||
v.alignObjectValues()
|
||||
// Format trailing extra.
|
||||
v.AfterExtra.format(0, formatOptions{})
|
||||
v.AfterExtra = append(bytes.TrimRightFunc(v.AfterExtra, unicode.IsSpace), '\n') // always has exactly one trailing newline
|
||||
|
||||
v.UpdateOffsets()
|
||||
}
|
||||
|
||||
// normalize performs simple normalization changes. In particular, it:
|
||||
// - normalizes strings,
|
||||
// - normalizes empty objects and arrays as simply {} or [],
|
||||
// - normalizes whitespace between names and colons,
|
||||
// - normalizes whitespace between values and commas.
|
||||
//
|
||||
// It always returns true to be compatible with composite.rangeValues.
|
||||
func (v *Value) normalize() bool {
|
||||
switch v2 := v.Value.(type) {
|
||||
case Literal:
|
||||
// Normalize string if there are escape characters.
|
||||
if v2.Kind() == '"' && bytes.IndexByte(v2, '\\') >= 0 {
|
||||
v.Value = String(v2.String())
|
||||
}
|
||||
case composite:
|
||||
// Cleanup for empty objects and arrays.
|
||||
if v2.length() == 0 {
|
||||
// If there is only whitespace, then remove the whitespace.
|
||||
if !v2.afterExtra().hasComment() {
|
||||
*v2.afterExtra() = nil
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// If there is only whitespace between the name and colon,
|
||||
// or between the value and comma, then remove the whitespace.
|
||||
for v3 := range v2.allValues() {
|
||||
if !v3.AfterExtra.hasComment() {
|
||||
v3.AfterExtra = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize all sub-values.
|
||||
for v3 := range v2.allValues() {
|
||||
v3.normalize()
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// lineStats carries statistics about a sequence of lines.
|
||||
type lineStats struct {
|
||||
firstLength int
|
||||
lastLength int
|
||||
multiline bool // false implies firstLength == lastLength
|
||||
}
|
||||
|
||||
// expandComposites populates needExpand with the set of composite values
|
||||
// that need to be expanded (i.e., print each member/element on a new line).
|
||||
// This method is pure and does not mutate the AST.
|
||||
func (v *Value) expandComposites(needExpand map[composite]bool) (stats lineStats) {
|
||||
switch v2 := v.Value.(type) {
|
||||
case Literal:
|
||||
stats = lineStats{len(v2), len(v2), false}
|
||||
case composite:
|
||||
// Every object or array is either fully inlined or fully expanded.
|
||||
// This simplifies machine-modification of HuJSON so that the mutation
|
||||
// can easily determine which mode it is currently in.
|
||||
//
|
||||
// If any whitespace after a '{', '[', or ',' or before a '}' or ']'
|
||||
// contains a newline, then we always expand the object or array.
|
||||
var expand bool
|
||||
|
||||
// Keep track of line lengths.
|
||||
var lineLength int
|
||||
var lineLengths []int
|
||||
updateStats := func(s lineStats) {
|
||||
lineLength += s.firstLength
|
||||
if s.multiline {
|
||||
lineLengths = append(lineLengths, lineLength)
|
||||
lineLength = s.lastLength
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate through all members/elements in an object/array.
|
||||
switch v2 := v2.(type) {
|
||||
case *Object:
|
||||
lineLength += len("{")
|
||||
for i := range v2.Members {
|
||||
name := &v2.Members[i].Name
|
||||
value := &v2.Members[i].Value
|
||||
expand = expand || name.BeforeExtra.hasNewline()
|
||||
updateStats(name.BeforeExtra.lineStats())
|
||||
updateStats(name.expandComposites(needExpand))
|
||||
updateStats(name.AfterExtra.lineStats())
|
||||
lineLength += len(": ")
|
||||
updateStats(value.BeforeExtra.lineStats())
|
||||
updateStats(value.expandComposites(needExpand))
|
||||
updateStats(value.AfterExtra.lineStats())
|
||||
lineLength += len(", ")
|
||||
}
|
||||
lineLength += len("}")
|
||||
|
||||
// Always expand multiline objects with more than 1 member.
|
||||
expand = expand || v2.length() > 1 && stats.multiline
|
||||
case *Array:
|
||||
lineLength += len("[")
|
||||
for i := range v2.Elements {
|
||||
value := &v2.Elements[i]
|
||||
expand = expand || value.BeforeExtra.hasNewline()
|
||||
updateStats(value.BeforeExtra.lineStats())
|
||||
updateStats(value.expandComposites(needExpand))
|
||||
updateStats(value.AfterExtra.lineStats())
|
||||
lineLength += len(", ")
|
||||
}
|
||||
lineLength += len("]")
|
||||
}
|
||||
if last := v2.lastValue(); last != nil {
|
||||
expand = expand || last.AfterExtra.hasNewline()
|
||||
}
|
||||
expand = expand || v2.afterExtra().hasNewline()
|
||||
|
||||
// Update the block statistics.
|
||||
lineLengths = append(lineLengths, lineLength)
|
||||
stats = lineStats{
|
||||
firstLength: lineLengths[0],
|
||||
lastLength: lineLengths[len(lineLengths)-1],
|
||||
multiline: len(lineLengths) > 1,
|
||||
}
|
||||
for i := 0; !expand && i < len(lineLengths); i++ {
|
||||
expand = lineLengths[i] > punchCardWidth
|
||||
}
|
||||
|
||||
if expand {
|
||||
stats = lineStats{len("{"), len("}"), true}
|
||||
stats.firstLength += v2.beforeExtraAt(0).lineStats().firstLength
|
||||
needExpand[v2] = expand
|
||||
}
|
||||
}
|
||||
return stats
|
||||
}
|
||||
|
||||
func (b Extra) lineStats() (stats lineStats) {
|
||||
// length is the approximate length of the comments.
|
||||
length := func(b []byte) (n int) {
|
||||
for {
|
||||
b = b[consumeWhitespace(b):]
|
||||
switch {
|
||||
case bytes.HasPrefix(b, lineCommentStart):
|
||||
return n + len(" ") + len(b) // line comment must go to the end
|
||||
case bytes.HasPrefix(b, blockCommentStart):
|
||||
nc := consumeComment(b)
|
||||
if nc <= 0 {
|
||||
return n + len(" ") + len(b) // truncated block comment must go to the end
|
||||
}
|
||||
n += len(" ") + nc
|
||||
b = b[nc:]
|
||||
continue
|
||||
default:
|
||||
if n > 0 {
|
||||
n += len(" ") // account for padding space after block comment
|
||||
}
|
||||
return n
|
||||
}
|
||||
}
|
||||
}
|
||||
if !bytes.Contains(b, newline) {
|
||||
n := length(b)
|
||||
return lineStats{n, n, false}
|
||||
} else {
|
||||
first := b[:bytes.IndexByte(b, '\n')]
|
||||
last := b[bytes.LastIndexByte(b, '\n')+len("\n"):]
|
||||
return lineStats{length(first), length(last), true}
|
||||
}
|
||||
}
|
||||
|
||||
// formatWhitespace mutates the AST and formats whitespace to ensure
|
||||
// consistent indentation and expansion of objects and arrays.
|
||||
func (v *Value) formatWhitespace(depth int, needExpand map[composite]bool, standardize bool) {
|
||||
if comp, ok := v.Value.(composite); ok {
|
||||
expand := needExpand[comp]
|
||||
|
||||
// Format all members/elements in an object/array.
|
||||
switch comp := comp.(type) {
|
||||
case *Object:
|
||||
for i := range comp.Members {
|
||||
name := &comp.Members[i].Name
|
||||
value := &comp.Members[i].Value
|
||||
|
||||
// Format extra before name.
|
||||
name.BeforeExtra.format(depth+1, formatOptions{
|
||||
ensureLeadingNewline: expand,
|
||||
removeLeadingEmptyLines: i == 0,
|
||||
appendSpaceIfEmpty: i != 0,
|
||||
})
|
||||
// Format the name.
|
||||
name.formatWhitespace(depth+1, needExpand, standardize)
|
||||
// Format extra after name and before colon.
|
||||
name.AfterExtra.format(depth+2, formatOptions{
|
||||
removeLeadingEmptyLines: true,
|
||||
removeTrailingEmptyLines: true,
|
||||
})
|
||||
// Format extra after colon and before value.
|
||||
value.BeforeExtra.format(depth+2, formatOptions{
|
||||
removeLeadingEmptyLines: true,
|
||||
removeTrailingEmptyLines: true,
|
||||
appendSpaceIfEmpty: true,
|
||||
})
|
||||
// Format the value.
|
||||
depthOffset := 0
|
||||
if expand {
|
||||
depthOffset++
|
||||
}
|
||||
if name.AfterExtra.hasNewline() || value.BeforeExtra.hasNewline() {
|
||||
depthOffset++
|
||||
}
|
||||
value.formatWhitespace(depth+depthOffset, needExpand, standardize)
|
||||
// Format extra after value and before comma.
|
||||
value.AfterExtra.format(depth+2, formatOptions{
|
||||
removeLeadingEmptyLines: true,
|
||||
removeTrailingEmptyLines: true,
|
||||
})
|
||||
}
|
||||
case *Array:
|
||||
for i := range comp.Elements {
|
||||
value := &comp.Elements[i]
|
||||
|
||||
// Format extra before value.
|
||||
value.BeforeExtra.format(depth+1, formatOptions{
|
||||
ensureLeadingNewline: expand,
|
||||
removeLeadingEmptyLines: i == 0,
|
||||
appendSpaceIfEmpty: i != 0,
|
||||
})
|
||||
// Format the value.
|
||||
depthOffset := 0
|
||||
if expand {
|
||||
depthOffset++
|
||||
}
|
||||
value.formatWhitespace(depth+depthOffset, needExpand, standardize)
|
||||
// Format extra after value and before comma.
|
||||
value.AfterExtra.format(depth+2, formatOptions{
|
||||
removeLeadingEmptyLines: true,
|
||||
removeTrailingEmptyLines: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Format the extra before the closing '}' or ']'.
|
||||
comp.afterExtra().format(depth+1, formatOptions{
|
||||
ensureTrailingNewline: expand,
|
||||
removeLeadingEmptyLines: comp.length() == 0,
|
||||
removeTrailingEmptyLines: true,
|
||||
unindentLastLine: true,
|
||||
})
|
||||
|
||||
// Normalize presence of trailing comma.
|
||||
surroundedComma := comp.lastValue() != nil && len(comp.lastValue().AfterExtra) > 0 && len(*comp.afterExtra()) > 0
|
||||
switch {
|
||||
// Avoid a trailing comma for a non-expanded object or array.
|
||||
case !expand && !surroundedComma:
|
||||
setTrailingComma(comp, false)
|
||||
// Otherwise, emit a trailing comma (unless this need to be standard).
|
||||
case expand && !standardize:
|
||||
setTrailingComma(comp, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type formatOptions struct {
|
||||
ensureLeadingNewline bool
|
||||
ensureTrailingNewline bool
|
||||
removeLeadingEmptyLines bool
|
||||
removeTrailingEmptyLines bool
|
||||
unindentLastLine bool
|
||||
appendSpaceIfEmpty bool
|
||||
}
|
||||
|
||||
func (b *Extra) format(depth int, opts formatOptions) {
|
||||
// Remove carriage returns to normalize output across operating systems.
|
||||
if bytes.IndexByte(*b, '\r') >= 0 {
|
||||
*b = bytes.ReplaceAll(*b, endlineWindows, newline)
|
||||
*b = bytes.ReplaceAll(*b, endlineMacOSX, newline)
|
||||
*b = bytes.ReplaceAll(*b, carriageReturn, space)
|
||||
}
|
||||
|
||||
in := *b
|
||||
var out []byte // TODO(dsnet): Cache this in sync.Pool?
|
||||
|
||||
// Inject a leading newline if not present in the input.
|
||||
if opts.ensureLeadingNewline && !in.hasNewline() {
|
||||
out = append(out, '\n')
|
||||
}
|
||||
|
||||
// Iterate over every paragraph in the comment.
|
||||
for len(in) > 0 {
|
||||
// Handle whitespace.
|
||||
if n := consumeWhitespace(in); n > 0 {
|
||||
nl := bytes.Count(in[:n], newline)
|
||||
if nl > 2 {
|
||||
nl = 2 // never allow more than one blank line
|
||||
}
|
||||
for i := 0; i < nl; i++ {
|
||||
out = append(out, '\n')
|
||||
}
|
||||
in = in[n:]
|
||||
continue
|
||||
}
|
||||
|
||||
// Handle comments.
|
||||
n := consumeComment(in)
|
||||
if n <= 0 {
|
||||
return // invalid comment
|
||||
}
|
||||
|
||||
// Emit leading whitespace.
|
||||
if bytes.HasSuffix(out, newline) {
|
||||
out = appendIndent(out, depth)
|
||||
} else {
|
||||
out = append(out, ' ')
|
||||
}
|
||||
|
||||
// Copy single-line comment to the output verbatim.
|
||||
comment := in[:n]
|
||||
if bytes.HasPrefix(comment, lineCommentStart) || !comment.hasNewline() {
|
||||
comment = bytes.TrimRightFunc(comment, unicode.IsSpace) // trim trailing whitespace
|
||||
if bytes.HasPrefix(comment, lineCommentStart) {
|
||||
n-- // leave newline for next iteration of comment
|
||||
}
|
||||
out = append(out, comment...) // single-line comments preserved verbatim
|
||||
in = in[n:]
|
||||
continue
|
||||
}
|
||||
|
||||
// Format multi-line block comments and copy to the output.
|
||||
lines := bytes.Split(comment, newline) // len(lines) >= 2 since at least one '\n' exists
|
||||
var firstLine []byte // first non-empty line after blockCommentStart
|
||||
var hasEmptyLine bool
|
||||
for i, line := range lines {
|
||||
line = bytes.TrimRightFunc(line, unicode.IsSpace) // trim trailing whitespace
|
||||
if len(firstLine) == 0 && len(line) > 0 && i > 0 {
|
||||
firstLine = line
|
||||
}
|
||||
hasEmptyLine = hasEmptyLine || len(line) == 0
|
||||
lines[i] = line
|
||||
}
|
||||
|
||||
// Compute the longest common prefix
|
||||
commonPrefix := firstLine
|
||||
for i, line := range lines[1:] {
|
||||
if len(line) == 0 {
|
||||
continue // ignore empty lines
|
||||
}
|
||||
|
||||
// If the last line is just "*/" with preceding whitespace, then
|
||||
// ignore any whitespace as part of the common prefix.
|
||||
// Instead, copy the whitespace from the common prefix.
|
||||
isLast := bytes.HasSuffix(line, blockCommentEnd)
|
||||
if isLast && consumeWhitespace(line)+len(blockCommentEnd) == len(line) {
|
||||
prefixLen := consumeWhitespace(commonPrefix)
|
||||
lines[i+1] = append(commonPrefix[:prefixLen:prefixLen], blockCommentEnd...)
|
||||
break
|
||||
}
|
||||
|
||||
// Check for longest common prefix.
|
||||
for i := 0; i < len(line) && i < len(commonPrefix); i++ {
|
||||
if line[i] != commonPrefix[i] {
|
||||
commonPrefix = commonPrefix[:i]
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Indent every line and copy to output.
|
||||
prefixLen := consumeWhitespace(commonPrefix)
|
||||
starAligned := !hasEmptyLine && len(commonPrefix) > prefixLen && commonPrefix[prefixLen] == '*'
|
||||
out = append(out, lines[0]...)
|
||||
out = append(out, '\n')
|
||||
for _, line := range lines[1:] {
|
||||
if len(line) > 0 {
|
||||
out = appendIndent(out, depth)
|
||||
if starAligned {
|
||||
out = append(out, ' ')
|
||||
}
|
||||
out = append(out, line[prefixLen:]...)
|
||||
}
|
||||
out = append(out, '\n')
|
||||
}
|
||||
out = bytes.TrimRight(out, "\n")
|
||||
in = in[n:]
|
||||
}
|
||||
|
||||
// Inject a trailing newline if not present in the input.
|
||||
if opts.ensureTrailingNewline && !bytes.HasSuffix(out, newline) {
|
||||
out = append(out, '\n')
|
||||
}
|
||||
// Remove all leading empty lines.
|
||||
for opts.removeLeadingEmptyLines && bytes.HasPrefix(out, twoNewlines) {
|
||||
out = out[1:]
|
||||
}
|
||||
// Remove all trailing empty lines.
|
||||
for opts.removeTrailingEmptyLines && bytes.HasSuffix(out, twoNewlines) {
|
||||
out = out[:len(out)-1]
|
||||
}
|
||||
// If the whitespace ends on a newline, append the necessary indentation.
|
||||
// Otherwise, emit a space if we did not end on a new line.
|
||||
if bytes.HasSuffix(out, newline) {
|
||||
if opts.unindentLastLine {
|
||||
depth--
|
||||
}
|
||||
out = appendIndent(out, depth)
|
||||
} else if len(out) > 0 {
|
||||
out = append(out, ' ')
|
||||
}
|
||||
// Emit a space if the output is empty.
|
||||
if opts.appendSpaceIfEmpty && len(out) == 0 {
|
||||
out = append(out, ' ')
|
||||
}
|
||||
|
||||
// Copy intermediate output to the receiver.
|
||||
if !bytes.Equal(*b, out) {
|
||||
*b = append((*b)[:0], out...)
|
||||
}
|
||||
}
|
||||
|
||||
// alignObjectValues aligns object values by inserting spaces after the name
|
||||
// so that the values are aligned to the same column.
|
||||
//
|
||||
// It always returns true to be compatible with composite.rangeValues.
|
||||
func (v *Value) alignObjectValues() bool {
|
||||
// TODO(dsnet): This is broken for non-monospace, non-narrow characters.
|
||||
// This is hard to fix as even `go fmt` suffers from this problem.
|
||||
// See https://golang.org/issue/8273.
|
||||
if obj, ok := v.Value.(*Object); ok {
|
||||
type row struct {
|
||||
extra *Extra // pointer to extra after colon and before value
|
||||
length int // length from start of name to end of extra
|
||||
}
|
||||
var rows []row
|
||||
alignRows := func() {
|
||||
// TODO(dsnet): Should we break apart rows if the number of spaces
|
||||
// to insert exceeds some threshold?
|
||||
|
||||
// Compute the maximum width.
|
||||
var max int
|
||||
for _, row := range rows {
|
||||
if max < row.length {
|
||||
max = row.length
|
||||
}
|
||||
}
|
||||
// Align every row up to that width.
|
||||
for _, row := range rows {
|
||||
for n := max - row.length; n > 0; n-- {
|
||||
*row.extra = append(*row.extra, ' ')
|
||||
}
|
||||
}
|
||||
// Reset the sequence of rows.
|
||||
rows = rows[:0]
|
||||
}
|
||||
var indentSuffix []byte
|
||||
for i := range obj.Members {
|
||||
name := &obj.Members[i].Name
|
||||
value := &obj.Members[i].Value
|
||||
|
||||
// Whitespace right before name must have a newline and
|
||||
// everything after the name until the comma cannot have newlines.
|
||||
if !name.BeforeExtra.hasNewline() ||
|
||||
name.hasNewline(false) ||
|
||||
name.AfterExtra.hasNewline() ||
|
||||
value.BeforeExtra.hasNewline() ||
|
||||
value.hasNewline(false) ||
|
||||
value.AfterExtra.hasNewline() {
|
||||
alignRows()
|
||||
continue
|
||||
}
|
||||
|
||||
// If there are multiple newlines or the indentSuffix mismatches,
|
||||
// then this is the start of a new block or rows to align.
|
||||
if bytes.Count(name.BeforeExtra, newline) > 1 || !bytes.HasSuffix(name.BeforeExtra, indentSuffix) {
|
||||
alignRows() // flush the current block or rows
|
||||
}
|
||||
|
||||
rows = append(rows, row{
|
||||
extra: &value.BeforeExtra,
|
||||
length: len(name.Value.(Literal)) + len(name.AfterExtra) + len(":") + len(value.BeforeExtra),
|
||||
})
|
||||
}
|
||||
alignRows()
|
||||
}
|
||||
|
||||
// Recursively align all sub-objects.
|
||||
if comp, ok := v.Value.(composite); ok {
|
||||
for v2 := range comp.allValues() {
|
||||
v2.alignObjectValues()
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (v Value) hasNewline(checkTopLevelExtra bool) bool {
|
||||
if checkTopLevelExtra && (v.BeforeExtra.hasNewline() || v.AfterExtra.hasNewline()) {
|
||||
return true
|
||||
}
|
||||
if comp, ok := v.Value.(composite); ok {
|
||||
for v := range comp.allValues() {
|
||||
if v.hasNewline(true) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (b Extra) hasNewline() bool {
|
||||
return bytes.IndexByte(b, '\n') >= 0
|
||||
}
|
||||
|
||||
func appendIndent(b []byte, n int) []byte {
|
||||
for i := 0; i < n; i++ {
|
||||
b = append(b, '\t')
|
||||
}
|
||||
return b
|
||||
}
|
||||
6
vendor/github.com/tailscale/hujson/go.work
generated
vendored
Normal file
6
vendor/github.com/tailscale/hujson/go.work
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
go 1.23
|
||||
|
||||
use (
|
||||
.
|
||||
./cmd/hujsonfmt
|
||||
)
|
||||
92
vendor/github.com/tailscale/hujson/pack.go
generated
vendored
Normal file
92
vendor/github.com/tailscale/hujson/pack.go
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
// Copyright (c) 2021 Tailscale Inc & 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 hujson
|
||||
|
||||
// UpdateOffsets iterates through v and updates all
|
||||
// Value.StartOffset and Value.EndOffset fields so that they are accurate.
|
||||
func (v *Value) UpdateOffsets() {
|
||||
v.updateOffsets(0)
|
||||
}
|
||||
func (v *Value) updateOffsets(n int) int {
|
||||
n += len(v.BeforeExtra)
|
||||
v.StartOffset = n
|
||||
switch v2 := v.Value.(type) {
|
||||
case Literal:
|
||||
n += len(v2)
|
||||
case *Object:
|
||||
n += len("{")
|
||||
for i := range v2.Members {
|
||||
n = v2.Members[i].Name.updateOffsets(n)
|
||||
n += len(":")
|
||||
n = v2.Members[i].Value.updateOffsets(n)
|
||||
n += len(",")
|
||||
}
|
||||
if v2.length() > 0 && !hasTrailingComma(v2) {
|
||||
n -= len(",")
|
||||
}
|
||||
n += len(v2.AfterExtra)
|
||||
n += len("}")
|
||||
case *Array:
|
||||
n += len("[")
|
||||
for i := range v2.Elements {
|
||||
n = v2.Elements[i].updateOffsets(n)
|
||||
n += len(",")
|
||||
}
|
||||
if v2.length() > 0 && !hasTrailingComma(v2) {
|
||||
n -= len(",")
|
||||
}
|
||||
n += len(v2.AfterExtra)
|
||||
n += len("]")
|
||||
}
|
||||
v.EndOffset = n
|
||||
n += len(v.AfterExtra)
|
||||
return n
|
||||
}
|
||||
|
||||
// Pack serializes the value as HuJSON.
|
||||
// The output is valid so long as every Extra and Literal in the Value is valid.
|
||||
// The output does not alias the memory of any buffers referenced by v.
|
||||
func (v Value) Pack() []byte {
|
||||
return v.append(nil)
|
||||
}
|
||||
|
||||
// String is a string representation of v.
|
||||
func (v Value) String() string {
|
||||
return string(v.append(nil))
|
||||
}
|
||||
|
||||
func (v Value) append(b []byte) []byte {
|
||||
b = append(b, v.BeforeExtra...)
|
||||
switch v2 := v.Value.(type) {
|
||||
case Literal:
|
||||
b = append(b, v2...)
|
||||
case *Object:
|
||||
b = append(b, '{')
|
||||
for _, m := range v2.Members {
|
||||
b = m.Name.append(b)
|
||||
b = append(b, ':')
|
||||
b = m.Value.append(b)
|
||||
b = append(b, ',')
|
||||
}
|
||||
if v2.length() > 0 && !hasTrailingComma(v2) {
|
||||
b = b[:len(b)-1]
|
||||
}
|
||||
b = append(b, v2.AfterExtra...)
|
||||
b = append(b, '}')
|
||||
case *Array:
|
||||
b = append(b, '[')
|
||||
for _, e := range v2.Elements {
|
||||
b = e.append(b)
|
||||
b = append(b, ',')
|
||||
}
|
||||
if v2.length() > 0 && !hasTrailingComma(v2) {
|
||||
b = b[:len(b)-1]
|
||||
}
|
||||
b = append(b, v2.AfterExtra...)
|
||||
b = append(b, ']')
|
||||
}
|
||||
b = append(b, v.AfterExtra...)
|
||||
return b
|
||||
}
|
||||
282
vendor/github.com/tailscale/hujson/parse.go
generated
vendored
Normal file
282
vendor/github.com/tailscale/hujson/parse.go
generated
vendored
Normal file
@@ -0,0 +1,282 @@
|
||||
// Copyright (c) 2021 Tailscale Inc & 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 hujson
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
func lineColumn(b []byte, n int) (line, column int) {
|
||||
line = 1 + bytes.Count(b[:n], []byte("\n"))
|
||||
column = 1 + n - (bytes.LastIndexByte(b[:n], '\n') + len("\n"))
|
||||
return line, column
|
||||
}
|
||||
|
||||
// Parse parses a HuJSON value as a Value.
|
||||
// Extra and Literal values in v will alias the provided input buffer.
|
||||
func Parse(b []byte) (Value, error) {
|
||||
v, n, err := parseNext(0, b)
|
||||
if err == nil && n < len(b) {
|
||||
err = newInvalidCharacterError(b[n:], "after top-level value")
|
||||
}
|
||||
if err != nil {
|
||||
line, column := lineColumn(b, n)
|
||||
err = fmt.Errorf("hujson: line %d, column %d: %w", line, column, err)
|
||||
return v, err
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// parseNext parses the next value with surrounding whitespace and comments.
|
||||
func parseNext(n int, b []byte) (v Value, _ int, err error) {
|
||||
n0 := n
|
||||
|
||||
// Consume leading whitespace and comments.
|
||||
if n, err = consumeExtra(n, b); err != nil {
|
||||
return v, n, err
|
||||
}
|
||||
if n > n0 {
|
||||
v.BeforeExtra = b[n0:n:n]
|
||||
}
|
||||
|
||||
// Parse the next value.
|
||||
v.StartOffset = n
|
||||
if v.Value, n, err = parseNextTrimmed(n, b); err != nil {
|
||||
return v, n, err
|
||||
}
|
||||
v.EndOffset = n
|
||||
|
||||
// Consume trailing whitespace and comments.
|
||||
if n, err = consumeExtra(n, b); err != nil {
|
||||
return v, n, err
|
||||
}
|
||||
if n > v.EndOffset {
|
||||
v.AfterExtra = b[v.EndOffset:n:n]
|
||||
}
|
||||
|
||||
return v, n, nil
|
||||
}
|
||||
|
||||
var (
|
||||
errInvalidObjectEnd = errors.New("invalid character '}' at start of value")
|
||||
errInvalidArrayEnd = errors.New("invalid character ']' at start of value")
|
||||
)
|
||||
|
||||
// parseNextTrimmed parses the next value without surrounding whitespace and comments.
|
||||
func parseNextTrimmed(n int, b []byte) (ValueTrimmed, int, error) {
|
||||
if len(b) == n {
|
||||
return nil, n, fmt.Errorf("parsing value: %w", io.ErrUnexpectedEOF)
|
||||
}
|
||||
switch b[n] {
|
||||
// Parse objects.
|
||||
case '{':
|
||||
n++
|
||||
var obj Object
|
||||
for {
|
||||
var vk, vv Value
|
||||
var err error
|
||||
|
||||
// Parse the name.
|
||||
if vk, n, err = parseNext(n, b); err != nil {
|
||||
if err == errInvalidObjectEnd && vk.Value == nil {
|
||||
setTrailingComma(&obj, len(obj.Members) > 0)
|
||||
obj.AfterExtra = vk.BeforeExtra
|
||||
return &obj, n + len(`}`), nil
|
||||
}
|
||||
return &obj, n, err
|
||||
}
|
||||
if vk.Value.Kind() != '"' {
|
||||
return &obj, vk.StartOffset, newInvalidCharacterError(b[vk.StartOffset:], "at start of object name")
|
||||
}
|
||||
|
||||
// Parse the colon.
|
||||
switch {
|
||||
case len(b) == n:
|
||||
return &obj, n, fmt.Errorf("parsing object after name: %w", io.ErrUnexpectedEOF)
|
||||
case b[n] != ':':
|
||||
return &obj, n, newInvalidCharacterError(b[n:], "after object name")
|
||||
}
|
||||
n++
|
||||
|
||||
// Parse the value.
|
||||
if vv, n, err = parseNext(n, b); err != nil {
|
||||
return &obj, n, err
|
||||
}
|
||||
|
||||
obj.Members = append(obj.Members, ObjectMember{vk, vv})
|
||||
switch {
|
||||
case len(b) == n:
|
||||
return &obj, n, fmt.Errorf("parsing object after value: %w", io.ErrUnexpectedEOF)
|
||||
case b[n] == ',':
|
||||
n++
|
||||
case b[n] == '}':
|
||||
// Move AfterExtra from last value to AfterExtra of the object.
|
||||
obj.AfterExtra = obj.Members[len(obj.Members)-1].Value.AfterExtra
|
||||
obj.Members[len(obj.Members)-1].Value.AfterExtra = nil
|
||||
return &obj, n + len(`}`), nil
|
||||
default:
|
||||
return &obj, n, newInvalidCharacterError(b[n:], "after object value (expecting ',' or '}')")
|
||||
}
|
||||
}
|
||||
case '}':
|
||||
return nil, n, errInvalidObjectEnd
|
||||
|
||||
// Parse arrays.
|
||||
case '[':
|
||||
n++
|
||||
var arr Array
|
||||
for {
|
||||
var v Value
|
||||
var err error
|
||||
if v, n, err = parseNext(n, b); err != nil {
|
||||
if err == errInvalidArrayEnd && v.Value == nil {
|
||||
setTrailingComma(&arr, len(arr.Elements) > 0)
|
||||
arr.AfterExtra = v.BeforeExtra
|
||||
return &arr, n + len(`]`), nil
|
||||
}
|
||||
return &arr, n, err
|
||||
}
|
||||
arr.Elements = append(arr.Elements, v)
|
||||
switch {
|
||||
case len(b) == n:
|
||||
return &arr, n, fmt.Errorf("parsing array after value: %w", io.ErrUnexpectedEOF)
|
||||
case b[n] == ',':
|
||||
n++
|
||||
case b[n] == ']':
|
||||
// Move AfterExtra from last value to AfterExtra of the array.
|
||||
arr.AfterExtra = arr.Elements[len(arr.Elements)-1].AfterExtra
|
||||
arr.Elements[len(arr.Elements)-1].AfterExtra = nil
|
||||
return &arr, n + len(`]`), nil
|
||||
default:
|
||||
return &arr, n, newInvalidCharacterError(b[n:], "after array value (expecting ',' or ']')")
|
||||
}
|
||||
}
|
||||
case ']':
|
||||
return nil, n, errInvalidArrayEnd
|
||||
|
||||
// Parse strings.
|
||||
case '"':
|
||||
n0 := n
|
||||
n++
|
||||
var inEscape bool
|
||||
for {
|
||||
switch {
|
||||
case len(b) == n:
|
||||
return nil, n, fmt.Errorf("parsing string: %w", io.ErrUnexpectedEOF)
|
||||
case inEscape:
|
||||
inEscape = false
|
||||
case b[n] == '\\':
|
||||
inEscape = true
|
||||
case b[n] == '"':
|
||||
n++
|
||||
lit := Literal(b[n0:n:n])
|
||||
if !lit.IsValid() {
|
||||
return nil, n0, fmt.Errorf("invalid literal: %s", lit)
|
||||
}
|
||||
return lit, n, nil
|
||||
}
|
||||
n++
|
||||
}
|
||||
|
||||
// Parse null, booleans, and numbers.
|
||||
default:
|
||||
n0 := n
|
||||
for len(b) > n && (b[n] == '-' || b[n] == '+' || b[n] == '.' ||
|
||||
('a' <= b[n] && b[n] <= 'z') ||
|
||||
('A' <= b[n] && b[n] <= 'Z') ||
|
||||
('0' <= b[n] && b[n] <= '9')) {
|
||||
n++
|
||||
}
|
||||
switch lit := Literal(b[n0:n:n]); {
|
||||
case len(lit) == 0:
|
||||
return nil, n0, newInvalidCharacterError(b[n0:], "at start of value")
|
||||
case !lit.IsValid():
|
||||
return nil, n0, fmt.Errorf("invalid literal: %s", lit)
|
||||
default:
|
||||
return lit, n, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
lineCommentStart = []byte("//")
|
||||
lineCommentEnd = []byte("\n")
|
||||
blockCommentStart = []byte("/*")
|
||||
blockCommentEnd = []byte("*/")
|
||||
)
|
||||
|
||||
// consumeExtra consumes leading whitespace and comments.
|
||||
func consumeExtra(n int, b []byte) (int, error) {
|
||||
for len(b) > n {
|
||||
switch b[n] {
|
||||
// Skip past whitespace.
|
||||
case ' ', '\t', '\r', '\n':
|
||||
n += consumeWhitespace(b[n:])
|
||||
// Skip past comments.
|
||||
case '/':
|
||||
switch nc := consumeComment(b[n:]); {
|
||||
case nc == 0:
|
||||
return n, nil
|
||||
case nc < 0:
|
||||
return n, fmt.Errorf("parsing comment: %w", io.ErrUnexpectedEOF)
|
||||
case !utf8.Valid(b[n : n+nc]):
|
||||
return n, fmt.Errorf("invalid UTF-8 in comment")
|
||||
default:
|
||||
n += nc
|
||||
}
|
||||
default:
|
||||
return n, nil
|
||||
}
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func consumeWhitespace(b []byte) (n int) {
|
||||
for len(b) > n && (b[n] == ' ' || b[n] == '\t' || b[n] == '\r' || b[n] == '\n') {
|
||||
n++
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// consumeComment consumes a line or block comment start in b.
|
||||
// It returns the length of the comment if valid, otherwise
|
||||
// it returns 0 if it is not a comment and -1 if it is invalid.
|
||||
func consumeComment(b []byte) (n int) {
|
||||
var start, end []byte
|
||||
switch {
|
||||
case bytes.HasPrefix(b, lineCommentStart):
|
||||
start, end = lineCommentStart, lineCommentEnd
|
||||
case bytes.HasPrefix(b, blockCommentStart):
|
||||
start, end = blockCommentStart, blockCommentEnd
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
i := bytes.Index(b[len(start):], end)
|
||||
if i < 0 {
|
||||
return -1
|
||||
}
|
||||
return len(start) + i + len(end)
|
||||
}
|
||||
|
||||
func newInvalidCharacterError(prefix []byte, where string) error {
|
||||
var what string
|
||||
r, n := utf8.DecodeRune(prefix)
|
||||
switch {
|
||||
case r == utf8.RuneError && n == 1:
|
||||
what = fmt.Sprintf(`'\x%02x'`, prefix[0])
|
||||
case unicode.IsPrint(r):
|
||||
what = fmt.Sprintf(`%q`, r)
|
||||
case r <= '\uffff':
|
||||
what = fmt.Sprintf(`'\u%04x'`, r)
|
||||
default:
|
||||
what = fmt.Sprintf(`'\U%08x'`, r)
|
||||
}
|
||||
return errors.New("invalid character " + what + " " + where)
|
||||
}
|
||||
463
vendor/github.com/tailscale/hujson/patch.go
generated
vendored
Normal file
463
vendor/github.com/tailscale/hujson/patch.go
generated
vendored
Normal file
@@ -0,0 +1,463 @@
|
||||
// Copyright (c) 2021 Tailscale Inc & 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 hujson
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TODO(dsnet): Insert/remove operations on an array has O(n) complexity
|
||||
// where n is the length of the array. We could improve this with more clever
|
||||
// data structure that has efficient insertion, deletion, and indexing.
|
||||
// One possibility is an "order statistic tree", which provide O(log n)
|
||||
// behavior for the necessary operations.
|
||||
// See https://en.wikipedia.org/wiki/Order_statistic_tree.
|
||||
|
||||
// TODO(dsnet): Name lookup on an object has O(n) complexity performing
|
||||
// a linear search through all names. This can be alleviated by building
|
||||
// a map of names to indexes for relevant objects. Currently, we always insert
|
||||
// a new member at the end of the members list, so that operation carries an
|
||||
// amortized cost of O(1).
|
||||
|
||||
// TODO(dsnet): Cache intermediate lookups when resolving a JSON pointer.
|
||||
// Patch operations tend to operate on paths that are related.
|
||||
// Caching can reduce pointer lookup from O(n) to be closer to O(1)
|
||||
// where n is the number of path segments in the JSON pointer.
|
||||
|
||||
// TODO(dsnet): Batch sequential insert/remove operations performed
|
||||
// on the same object or array. This handles the possibly common case of batch
|
||||
// inserting or removing a number of consecutive members/elements.
|
||||
// Pointer caching may make this optimization unnecessary.
|
||||
|
||||
// Patch patches the value according to the provided patch file (per RFC 6902).
|
||||
// The patch file may be in the HuJSON format where comments around and within
|
||||
// a value being inserted are preserved. If the patch fails to fully apply,
|
||||
// the receiver value will be left in a partially mutated state.
|
||||
// Use Clone to preserve the original value.
|
||||
//
|
||||
// It does not format the value. It is recommended that Format be called after
|
||||
// applying a patch.
|
||||
func (v *Value) Patch(patch []byte) error {
|
||||
ops, err := parsePatch(patch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i, op := range ops {
|
||||
var err error
|
||||
switch op.op {
|
||||
case "add":
|
||||
err = v.patchAdd(i, op)
|
||||
case "remove", "replace":
|
||||
err = v.patchRemoveOrReplace(i, op)
|
||||
case "move", "copy":
|
||||
err = v.patchMoveOrCopy(i, op)
|
||||
case "test":
|
||||
err = v.patchTest(i, op)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type patchOperation struct {
|
||||
op string // "add" | "remove" | "replace" | "move" | "copy" | "test"
|
||||
path string // used by all operations
|
||||
from string // used by "move" and "copy"
|
||||
value Value // used by "add", "replace", and "test"
|
||||
}
|
||||
|
||||
func parsePatch(patch []byte) ([]patchOperation, error) {
|
||||
v, err := Parse(patch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
arr, ok := v.Value.(*Array)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("hujson: patch must be a JSON array")
|
||||
}
|
||||
var ops []patchOperation
|
||||
for i, e := range arr.Elements {
|
||||
obj, ok := e.Value.(*Object)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("hujson: patch operation %d: must be a JSON object", i)
|
||||
}
|
||||
seen := make(map[string]bool)
|
||||
var op patchOperation
|
||||
for j, m := range obj.Members {
|
||||
name := m.Name.Value.(Literal).String()
|
||||
if seen[name] {
|
||||
return nil, fmt.Errorf("hujson: patch operation %d: duplicate name %q", i, m.Name.Value)
|
||||
}
|
||||
seen[name] = true
|
||||
switch name {
|
||||
case "op":
|
||||
if m.Value.Value.Kind() != '"' {
|
||||
return nil, fmt.Errorf("hujson: patch operation %d: member %q must be a JSON string", i, name)
|
||||
}
|
||||
switch opType := m.Value.Value.(Literal).String(); opType {
|
||||
case "add", "remove", "replace", "move", "copy", "test":
|
||||
op.op = opType
|
||||
default:
|
||||
return nil, fmt.Errorf("hujson: patch operation %d: unknown operation %q", i, m.Value.Value)
|
||||
}
|
||||
case "path":
|
||||
if m.Value.Value.Kind() != '"' {
|
||||
return nil, fmt.Errorf("hujson: patch operation %d: member %q must be a JSON string", i, name)
|
||||
}
|
||||
op.path = m.Value.Value.(Literal).String()
|
||||
case "from":
|
||||
if m.Value.Value.Kind() != '"' {
|
||||
return nil, fmt.Errorf("hujson: patch operation %d: member %q must be a JSON string", i, name)
|
||||
}
|
||||
op.from = m.Value.Value.(Literal).String()
|
||||
case "value":
|
||||
m.Value.BeforeExtra = obj.beforeExtraAt(j + 0).extractLeadingComments(true)
|
||||
m.Value.AfterExtra = obj.beforeExtraAt(j + 1).extractTrailingcomments(true)
|
||||
op.value = m.Value
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case !seen["op"]:
|
||||
return nil, fmt.Errorf("hujson: patch operation %d: missing required member %q", i, "op")
|
||||
case !seen["path"]:
|
||||
return nil, fmt.Errorf("hujson: patch operation %d: missing required member %q", i, "path")
|
||||
case !seen["from"] && (op.op == "move" || op.op == "copy"):
|
||||
return nil, fmt.Errorf("hujson: patch operation %d: missing required member %q", i, "from")
|
||||
case !seen["value"] && (op.op == "add" || op.op == "replace" || op.op == "test"):
|
||||
return nil, fmt.Errorf("hujson: patch operation %d: missing required member %q", i, "value")
|
||||
}
|
||||
ops = append(ops, op)
|
||||
}
|
||||
return ops, nil
|
||||
}
|
||||
|
||||
func (v *Value) patchAdd(i int, op patchOperation) error {
|
||||
s, err := v.find(findState{pointer: op.path})
|
||||
if err != nil && (err != errNotFound || len(s.pointer) != s.offset) {
|
||||
return fmt.Errorf("hujson: patch operation %d: %v", i, err)
|
||||
}
|
||||
if s.parent == nil {
|
||||
*v = op.value // only occurs for root
|
||||
} else {
|
||||
switch comp := s.parent.(type) {
|
||||
case *Object:
|
||||
if s.idx < comp.length() {
|
||||
replaceAt(comp, s.idx, op.value)
|
||||
} else {
|
||||
insertAt(comp, s.idx, op.value)
|
||||
comp.Members[s.idx].Name.Value = String(s.name)
|
||||
}
|
||||
case *Array:
|
||||
insertAt(comp, s.idx, op.value)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *Value) patchRemoveOrReplace(i int, op patchOperation) error {
|
||||
s, err := v.find(findState{pointer: op.path})
|
||||
if err != nil {
|
||||
return fmt.Errorf("hujson: patch operation %d: %v", i, err)
|
||||
}
|
||||
if s.parent == nil {
|
||||
return fmt.Errorf("hujson: patch operation %d: cannot %s root value", i, op.op)
|
||||
}
|
||||
switch op.op {
|
||||
case "remove":
|
||||
removeAt(s.parent, s.idx)
|
||||
case "replace":
|
||||
replaceAt(s.parent, s.idx, op.value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *Value) patchMoveOrCopy(i int, op patchOperation) error {
|
||||
if op.from == "" || (op.op == "move" && hasPathPrefix(op.path, op.from)) {
|
||||
return fmt.Errorf("hujson: patch operation %d: cannot %s %q into %q", i, op.op, op.from, op.path)
|
||||
}
|
||||
sFrom, err := v.find(findState{pointer: op.from})
|
||||
if err != nil {
|
||||
return fmt.Errorf("hujson: patch operation %d: %v", i, err)
|
||||
}
|
||||
// TODO(dsnet): For a move operation within the same object,
|
||||
// we should simplify this as just a rename or replace.
|
||||
switch op.op {
|
||||
case "move":
|
||||
op.value = removeAt(sFrom.parent, sFrom.idx)
|
||||
case "copy":
|
||||
op.value = copyAt(sFrom.parent, sFrom.idx)
|
||||
}
|
||||
return v.patchAdd(i, op)
|
||||
}
|
||||
|
||||
func (v *Value) patchTest(i int, op patchOperation) error {
|
||||
s, err := v.find(findState{pointer: op.path})
|
||||
if err != nil {
|
||||
return fmt.Errorf("hujson: patch operation %d: %v", i, err)
|
||||
}
|
||||
if !equalValue(*s.value, op.value) {
|
||||
return fmt.Errorf("hujson: patch operation %d: values differ at %q", i, op.path)
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// hasPathPrefix is a stricter version of strings.HasPrefix where
|
||||
// the prefix must end on a path segment boundary.
|
||||
func hasPathPrefix(s, prefix string) bool {
|
||||
if strings.HasPrefix(s, prefix) {
|
||||
return len(s) == len(prefix) || s[len(prefix)] == '/'
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func equalValue(x, y Value) bool {
|
||||
// TODO(dsnet): This definition of equality is both naive and slow.
|
||||
// * It fails to properly compare strings with invalid UTF-8.
|
||||
// * It fails to precisely compare integers beyond ±2⁵³.
|
||||
// * It cannot handle values greater than ±math.MaxFloat64.
|
||||
// * Comparison of objects with duplicate names has undefined behavior.
|
||||
unmarshal := func(v Value) (vi interface{}) {
|
||||
v = v.Clone()
|
||||
v.Standardize()
|
||||
if json.Unmarshal(v.Pack(), &vi) != nil {
|
||||
return nil
|
||||
}
|
||||
return vi
|
||||
}
|
||||
vx := unmarshal(x)
|
||||
vy := unmarshal(y)
|
||||
return reflect.DeepEqual(vx, vy) && vx != nil && vy != nil
|
||||
}
|
||||
|
||||
func (obj *Object) getAt(i int) ValueTrimmed {
|
||||
return obj.Members[i].Value.Value
|
||||
}
|
||||
func (obj *Object) setAt(i int, v ValueTrimmed) {
|
||||
obj.Members[i].Value.Value = v
|
||||
}
|
||||
func (obj *Object) insertAt(i int, v ValueTrimmed) {
|
||||
// TODO(dsnet): Use slices.Insert. See https://golang.org/issue/45955.
|
||||
obj.Members = append(obj.Members, ObjectMember{})
|
||||
copy(obj.Members[i+1:], obj.Members[i:])
|
||||
obj.Members[i] = ObjectMember{Value: Value{Value: v}}
|
||||
}
|
||||
func (obj *Object) removeAt(i int) ValueTrimmed {
|
||||
// TODO(dsnet): Use slices.Delete. See https://golang.org/issue/45955.
|
||||
v := obj.Members[i].Value.Value
|
||||
copy(obj.Members[i:], obj.Members[i+1:])
|
||||
obj.Members = obj.Members[:obj.length()-1]
|
||||
return v
|
||||
}
|
||||
|
||||
func (arr *Array) getAt(i int) ValueTrimmed {
|
||||
return arr.Elements[i].Value
|
||||
}
|
||||
func (arr *Array) setAt(i int, v ValueTrimmed) {
|
||||
arr.Elements[i].Value = v
|
||||
}
|
||||
func (arr *Array) insertAt(i int, v ValueTrimmed) {
|
||||
// TODO(dsnet): Use slices.Insert. See https://golang.org/issue/45955.
|
||||
arr.Elements = append(arr.Elements, ArrayElement{})
|
||||
copy(arr.Elements[i+1:], arr.Elements[i:])
|
||||
arr.Elements[i] = ArrayElement{Value: v}
|
||||
}
|
||||
func (arr *Array) removeAt(i int) ValueTrimmed {
|
||||
// TODO(dsnet): Use slices.Delete. See https://golang.org/issue/45955.
|
||||
v := arr.Elements[i].Value
|
||||
copy(arr.Elements[i:], arr.Elements[i+1:])
|
||||
arr.Elements = arr.Elements[:arr.length()-1]
|
||||
return v
|
||||
}
|
||||
|
||||
// Preserving and moving comments is impossible to perform reasonably in all
|
||||
// conceivable situations given that the placement of comments is more
|
||||
// a matter of human taste than it is a matter of mathematical rigor.
|
||||
//
|
||||
// We assume that:
|
||||
// * comments do not appear between the object member name and the colon
|
||||
// (i.e., ObjectMember.Name.AfterExtra is nil),
|
||||
// * comments do not appear between the colon and the object member value
|
||||
// (i.e., ObjectMember.Value.BeforeExtra is nil), and
|
||||
// * comments do not appear between the value and the comma
|
||||
// (i.e., ObjectMember.Value.AfterExtra and ArrayElement.AfterExtra are nil).
|
||||
// Such comments will be lost when patching.
|
||||
//
|
||||
// We further assume that:
|
||||
// * comments before an object member name and before an array element value
|
||||
// are strongly associated with that member/element, and
|
||||
// * comments immediately after an object member value and after an
|
||||
// array element value are strongly associated with that member/element.
|
||||
// Such comments will be moved along with the member/element.
|
||||
//
|
||||
// Consider the following example:
|
||||
// {
|
||||
// ...
|
||||
// // Comment1
|
||||
//
|
||||
// // Comment2
|
||||
// "name": "value", // Comment3
|
||||
// // Comment4
|
||||
//
|
||||
// // Comment5
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
// Moving "/name" will move only Comment2, Comment3, and Comment4.
|
||||
// All other comments will be left alone.
|
||||
//
|
||||
// The above approach may perform contrary to expectation in this example:
|
||||
// {
|
||||
// // Comment1
|
||||
// "name1": "value1",
|
||||
// "name2": "value2",
|
||||
// "name3": "value3",
|
||||
// }
|
||||
//
|
||||
// Moving "/name1" will move Comment1. It is unclear whether Comment1 is
|
||||
// strongly associated with just "name1" or the entire sequence of members
|
||||
// from "name1" to "name2".
|
||||
|
||||
func copyAt(comp composite, i int) (v Value) {
|
||||
v.BeforeExtra = comp.beforeExtraAt(i + 0).extractLeadingComments(true)
|
||||
v.AfterExtra = comp.beforeExtraAt(i + 1).extractTrailingcomments(true)
|
||||
v.Value = comp.getAt(i).clone()
|
||||
return v
|
||||
}
|
||||
func replaceAt(comp composite, i int, v Value) {
|
||||
comp.beforeExtraAt(i + 0).injectLeadingComments(v.BeforeExtra)
|
||||
comp.beforeExtraAt(i + 1).injectTrailingComments(v.AfterExtra)
|
||||
comp.setAt(i, v.Value)
|
||||
}
|
||||
func insertAt(comp composite, i int, v Value) {
|
||||
comp.insertAt(i, v.Value)
|
||||
trailing := comp.beforeExtraAt(i + 1).extractTrailingcomments(false)
|
||||
comp.beforeExtraAt(i + 0).injectTrailingComments(trailing)
|
||||
comp.beforeExtraAt(i + 0).injectLeadingComments(v.BeforeExtra)
|
||||
comp.beforeExtraAt(i + 1).injectTrailingComments(v.AfterExtra)
|
||||
}
|
||||
func removeAt(comp composite, i int) (v Value) {
|
||||
v.BeforeExtra = comp.beforeExtraAt(i + 0).extractLeadingComments(false)
|
||||
v.AfterExtra = comp.beforeExtraAt(i + 1).extractTrailingcomments(false)
|
||||
if trailing := *comp.beforeExtraAt(i + 0); trailing.hasComment() {
|
||||
leading := *comp.beforeExtraAt(i + 1)
|
||||
leading = leading[consumeWhitespace(leading):]
|
||||
*comp.beforeExtraAt(i + 1) = append(trailing, leading...)
|
||||
}
|
||||
v.Value = comp.removeAt(i)
|
||||
return v
|
||||
}
|
||||
|
||||
// injectLeadingComments injects leading comments into the bottom of b.
|
||||
func (b *Extra) injectLeadingComments(leading Extra) {
|
||||
if len(leading) > 0 {
|
||||
_, currStart := b.classifyComments()
|
||||
blankLen := consumeWhitespace((*b)[currStart:])
|
||||
*b = (*b)[:currStart+blankLen]
|
||||
leading = leading[consumeWhitespace(leading):]
|
||||
if len(leading) > 0 {
|
||||
if i := bytes.LastIndexByte(*b, '\n'); i < 0 || (*b)[i:].hasComment() {
|
||||
*b = append(*b, newline...)
|
||||
}
|
||||
*b = append(*b, leading...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// extractLeadingComments extracts leading comments from the bottom of b.
|
||||
// If readonly, then the source is not mutated.
|
||||
func (b *Extra) extractLeadingComments(readonly bool) (leading Extra) {
|
||||
_, currStart := b.classifyComments()
|
||||
blankLen := consumeWhitespace((*b)[currStart:])
|
||||
leading = copyBytes((*b)[currStart+blankLen:])
|
||||
if !readonly {
|
||||
*b = (*b)[:currStart+blankLen]
|
||||
}
|
||||
return leading
|
||||
}
|
||||
|
||||
// injectTrailingComments injects trailing comments into the top of b.
|
||||
func (b *Extra) injectTrailingComments(trailing Extra) {
|
||||
if len(trailing) > 0 {
|
||||
prevEnd, _ := b.classifyComments()
|
||||
if bytes.HasSuffix((*b)[:prevEnd], newline) {
|
||||
prevEnd-- // preserve trailing newline
|
||||
}
|
||||
*b = (*b)[prevEnd:]
|
||||
if trailing.hasComment() {
|
||||
if bytes.HasSuffix(trailing, newline) && bytes.HasPrefix(*b, newline) {
|
||||
trailing = trailing[:len(trailing)-1] // drop trailing newline
|
||||
}
|
||||
*b = append(copyBytes(trailing), *b...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// extractTrailingcomments extracts trailing comments from the top of b.
|
||||
// If readonly, then the source is not mutated.
|
||||
func (b *Extra) extractTrailingcomments(readonly bool) (trailing Extra) {
|
||||
prevEnd, _ := b.classifyComments()
|
||||
trailing = copyBytes((*b)[:prevEnd])
|
||||
if !readonly {
|
||||
if bytes.HasSuffix(trailing, newline) {
|
||||
prevEnd-- // preserve trailing newline
|
||||
}
|
||||
*b = (*b)[prevEnd:]
|
||||
}
|
||||
return trailing
|
||||
}
|
||||
|
||||
// classifyComments classifies comments as belonging to the previous element
|
||||
// or belonging to the current element such that:
|
||||
// - b[:prevEnd] belongs to the previous element, and
|
||||
// - b[currStart:] belongs to the current element.
|
||||
//
|
||||
// Invariant: prevEnd <= currStart
|
||||
func (b Extra) classifyComments() (prevEnd, currStart int) {
|
||||
// Scan for dividers between comment blocks.
|
||||
var firstDivider, lastDivider, numDividers int
|
||||
var n, prevNewline int
|
||||
for len(b) > n {
|
||||
nw := consumeWhitespace(b[n:])
|
||||
if prevNewline+bytes.Count(b[n:][:nw], newline) >= 2 {
|
||||
if numDividers == 0 {
|
||||
firstDivider = n
|
||||
}
|
||||
lastDivider = n
|
||||
numDividers++
|
||||
}
|
||||
n += nw
|
||||
|
||||
nc := consumeComment(b[n:])
|
||||
if nc <= 0 {
|
||||
break
|
||||
}
|
||||
prevNewline = 0
|
||||
if bytes.HasSuffix(b[n:][:nc], newline) {
|
||||
prevNewline = 1 // adjust newline accounting for next iteration
|
||||
}
|
||||
n += nc
|
||||
}
|
||||
|
||||
// Without dividers, a line comment starting on the first line belongs
|
||||
// to the previous element.
|
||||
if numDividers == 0 {
|
||||
nw := consumeWhitespace(b)
|
||||
nc := consumeComment(b[nw:])
|
||||
if n = nw + nc; bytes.Count(b[:n], newline) == 1 && bytes.HasSuffix(b[:n], lineCommentEnd) {
|
||||
return n, n
|
||||
}
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
// Ownership is more clear when there is at least one divider.
|
||||
return firstDivider, lastDivider
|
||||
}
|
||||
89
vendor/github.com/tailscale/hujson/standard.go
generated
vendored
Normal file
89
vendor/github.com/tailscale/hujson/standard.go
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright (c) 2021 Tailscale Inc & 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 hujson
|
||||
|
||||
// IsStandard reports whether this is standard JSON
|
||||
// by checking that there are no comments and no trailing commas.
|
||||
func (v Value) IsStandard() bool {
|
||||
return v.isStandard()
|
||||
}
|
||||
func (v *Value) isStandard() bool {
|
||||
if !v.BeforeExtra.IsStandard() {
|
||||
return false
|
||||
}
|
||||
if comp, ok := v.Value.(composite); ok {
|
||||
for v2 := range comp.allValues() {
|
||||
if !v2.isStandard() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if hasTrailingComma(comp) || !comp.afterExtra().IsStandard() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if !v.AfterExtra.IsStandard() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IsStandard reports whether this is standard JSON whitespace.
|
||||
func (b Extra) IsStandard() bool {
|
||||
return !b.hasComment()
|
||||
}
|
||||
func (b Extra) hasComment() bool {
|
||||
return consumeWhitespace(b) < len(b)
|
||||
}
|
||||
|
||||
// Minimize removes all whitespace, comments, and trailing commas from v,
|
||||
// making it compliant with standard JSON per RFC 8259.
|
||||
func (v *Value) Minimize() {
|
||||
v.minimize()
|
||||
v.UpdateOffsets()
|
||||
}
|
||||
func (v *Value) minimize() {
|
||||
v.BeforeExtra = nil
|
||||
if v2, ok := v.Value.(composite); ok {
|
||||
for v3 := range v2.allValues() {
|
||||
v3.minimize()
|
||||
}
|
||||
setTrailingComma(v2, false)
|
||||
*v2.afterExtra() = nil
|
||||
}
|
||||
v.AfterExtra = nil
|
||||
}
|
||||
|
||||
// Standardize strips any features specific to HuJSON from v,
|
||||
// making it compliant with standard JSON per RFC 8259.
|
||||
// All comments and trailing commas are replaced with a space character
|
||||
// in order to preserve the original line numbers and byte offsets.
|
||||
func (v *Value) Standardize() {
|
||||
v.standardize()
|
||||
v.UpdateOffsets() // should be noop if offsets are already correct
|
||||
}
|
||||
func (v *Value) standardize() {
|
||||
v.BeforeExtra.standardize()
|
||||
if comp, ok := v.Value.(composite); ok {
|
||||
for v2 := range comp.allValues() {
|
||||
v2.standardize()
|
||||
}
|
||||
if last := comp.lastValue(); last != nil && last.AfterExtra != nil {
|
||||
*comp.afterExtra() = append(append(last.AfterExtra, ' '), *comp.afterExtra()...)
|
||||
last.AfterExtra = nil
|
||||
}
|
||||
comp.afterExtra().standardize()
|
||||
}
|
||||
v.AfterExtra.standardize()
|
||||
}
|
||||
func (b *Extra) standardize() {
|
||||
for i, c := range *b {
|
||||
switch c {
|
||||
case ' ', '\t', '\r', '\n':
|
||||
// NOTE: Avoid changing '\n' to keep line numbers the same.
|
||||
default:
|
||||
(*b)[i] = ' '
|
||||
}
|
||||
}
|
||||
}
|
||||
561
vendor/github.com/tailscale/hujson/types.go
generated
vendored
Normal file
561
vendor/github.com/tailscale/hujson/types.go
generated
vendored
Normal file
@@ -0,0 +1,561 @@
|
||||
// Copyright (c) 2021 Tailscale Inc & 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 hujson contains a parser and packer for the JWCC format:
|
||||
// JSON With Commas and Comments (or "human JSON").
|
||||
//
|
||||
// JWCC is an extension of standard JSON (as defined in RFC 8259) in order to
|
||||
// make it more suitable for humans and configuration files. In particular,
|
||||
// it supports line comments (e.g., //...), block comments (e.g., /*...*/), and
|
||||
// trailing commas after the last member or element in a JSON object or array.
|
||||
//
|
||||
// See https://nigeltao.github.io/blog/2021/json-with-commas-comments.html
|
||||
//
|
||||
// # Functionality
|
||||
//
|
||||
// The Parse function parses HuJSON input as a Value,
|
||||
// which is a syntax tree exactly representing the input.
|
||||
// Comments and whitespace are represented using the Extra type.
|
||||
// Composite types in JSON are represented using the Object and Array types.
|
||||
// Primitive types in JSON are represented using the Literal type.
|
||||
// The Value.Pack method serializes the syntax tree as raw output,
|
||||
// which is byte-for-byte identical to the input if no transformations
|
||||
// were performed on the value.
|
||||
//
|
||||
// A HuJSON value can be transformed using the Minimize, Standardize, Format,
|
||||
// or Patch methods. Each of these methods mutate the value in place.
|
||||
// Call the Clone method beforehand in order to preserve the original value.
|
||||
// The Minimize and Standardize methods coerces HuJSON into standard JSON.
|
||||
// The Format method formats the value; it is similar to `go fmt`,
|
||||
// but instead for the HuJSON and standard JSON format.
|
||||
// The Patch method applies a JSON Patch (RFC 6902) to the receiving value.
|
||||
//
|
||||
// # Grammar
|
||||
//
|
||||
// The changes to the JSON grammar are:
|
||||
//
|
||||
// --- grammar.json
|
||||
// +++ grammar.hujson
|
||||
// @@ -1,13 +1,31 @@
|
||||
// members
|
||||
// member
|
||||
// + member ',' ws
|
||||
// member ',' members
|
||||
//
|
||||
// elements
|
||||
// element
|
||||
// + element ',' ws
|
||||
// element ',' elements
|
||||
//
|
||||
// +comments
|
||||
// + "*/"
|
||||
// + comment comments
|
||||
// +
|
||||
// +comment
|
||||
// + '0000' . '10FFFF'
|
||||
// +
|
||||
// +linecomments
|
||||
// + '\n'
|
||||
// + linecomment linecomments
|
||||
// +
|
||||
// +linecomment
|
||||
// + '0000' . '10FFFF' - '\n'
|
||||
// +
|
||||
// ws
|
||||
// ""
|
||||
// + "/*" comments
|
||||
// + "//" linecomments
|
||||
// '0020' ws
|
||||
// '000A' ws
|
||||
// '000D' ws
|
||||
//
|
||||
// # Use with the Standard Library
|
||||
//
|
||||
// This package operates with HuJSON as an AST. In order to parse HuJSON
|
||||
// into arbitrary Go types, use this package to parse HuJSON input as an AST,
|
||||
// strip the AST of any HuJSON-specific lexicographical elements, and
|
||||
// then pack the AST as a standard JSON output.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// b, err := hujson.Standardize(b)
|
||||
// if err != nil {
|
||||
// ... // handle err
|
||||
// }
|
||||
// if err := json.Unmarshal(b, &v); err != nil {
|
||||
// ... // handle err
|
||||
// }
|
||||
package hujson
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"iter"
|
||||
"math"
|
||||
"strconv"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// Kind reports the kind of the JSON value.
|
||||
// It is the first byte of the grammar for that JSON value,
|
||||
// with the exception that JSON numbers are represented as a '0'.
|
||||
//
|
||||
// 'n': null
|
||||
// 'f': false
|
||||
// 't': true
|
||||
// '"': string
|
||||
// '0': number
|
||||
// '{': object
|
||||
// '[': array
|
||||
type Kind byte
|
||||
|
||||
// Value is an exact syntactic representation of a JSON value.
|
||||
// The starting and ending byte offsets are populated when parsing,
|
||||
// but are otherwise ignored when packing.
|
||||
//
|
||||
// By convention, code should operate on a non-pointer Value as a soft signal
|
||||
// that the value should not be mutated, while operating on a pointer to Value
|
||||
// to indicate that the value may be mutated. A non-pointer Value does not
|
||||
// provide any language-enforced guarantees that it cannot be mutated.
|
||||
// The Value.Clone method can be used to produce a deep copy of Value such that
|
||||
// mutations on it will not be observed in the original Value.
|
||||
type Value struct {
|
||||
// BeforeExtra are the comments and whitespace before Value.
|
||||
// This is the extra after the preceding open brace, open bracket,
|
||||
// colon, comma, or start of input.
|
||||
BeforeExtra Extra
|
||||
// StartOffset is the offset of the first byte in Value.
|
||||
StartOffset int
|
||||
// Value is the JSON value without surrounding whitespace or comments.
|
||||
Value ValueTrimmed
|
||||
// EndOffset is the offset of the next byte after Value.
|
||||
EndOffset int
|
||||
// AfterExtra are the comments and whitespace after Value.
|
||||
// This is the extra before the succeeding colon, comma, or end of input.
|
||||
AfterExtra Extra
|
||||
}
|
||||
|
||||
// Clone returns a deep copy of the value.
|
||||
func (v Value) Clone() Value {
|
||||
v.BeforeExtra = copyBytes(v.BeforeExtra)
|
||||
v.Value = v.Value.clone()
|
||||
v.AfterExtra = copyBytes(v.AfterExtra)
|
||||
return v
|
||||
}
|
||||
|
||||
// Range iterates through a Value in depth-first order and
|
||||
// calls f for each value (including the root value).
|
||||
// It stops iteration when f returns false.
|
||||
//
|
||||
// Deprecated: Use [All] instead.
|
||||
func (v *Value) Range(f func(v *Value) bool) bool {
|
||||
for v2 := range v.All() {
|
||||
if !f(v2) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// All returns an iterator over all values in depth-first order,
|
||||
// starting with v itself.
|
||||
func (v *Value) All() iter.Seq[*Value] {
|
||||
return func(yield func(*Value) bool) {
|
||||
if !yield(v) {
|
||||
return
|
||||
}
|
||||
if comp, ok := v.Value.(composite); ok {
|
||||
for v2 := range comp.allValues() {
|
||||
for v3 := range v2.All() {
|
||||
if !yield(v3) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ValueTrimmed is a JSON value without surrounding whitespace or comments.
|
||||
// This is a sum type consisting of Literal, *Object, or *Array.
|
||||
type ValueTrimmed interface {
|
||||
// Kind reports the kind of the JSON value.
|
||||
Kind() Kind
|
||||
// clone returns a deep copy of the value.
|
||||
clone() ValueTrimmed
|
||||
|
||||
isValueTrimmed()
|
||||
}
|
||||
|
||||
var (
|
||||
_ ValueTrimmed = Literal(nil)
|
||||
_ ValueTrimmed = (*Object)(nil)
|
||||
_ ValueTrimmed = (*Array)(nil)
|
||||
)
|
||||
|
||||
// Literal is the raw bytes for a JSON null, boolean, string, or number.
|
||||
// It contains no surrounding whitespace or comments.
|
||||
type Literal []byte // e.g., null, false, true, "string", or 3.14159
|
||||
|
||||
// Bool constructs a JSON literal for a boolean.
|
||||
func Bool(v bool) Literal {
|
||||
if v {
|
||||
return Literal("true")
|
||||
} else {
|
||||
return Literal("false")
|
||||
}
|
||||
}
|
||||
|
||||
// String constructs a JSON literal for string.
|
||||
// Invalid UTF-8 is mangled with the Unicode replacement character.
|
||||
func String(v string) Literal {
|
||||
// Format according to RFC 8785, section 3.2.2.2.
|
||||
b := make([]byte, 0, len(`"`)+len(v)+len(`"`))
|
||||
b = append(b, '"')
|
||||
var arr [utf8.UTFMax]byte
|
||||
for _, r := range v {
|
||||
switch {
|
||||
case r < ' ' || r == '\\' || r == '"':
|
||||
switch r {
|
||||
case '\b':
|
||||
b = append(b, `\b`...)
|
||||
case '\t':
|
||||
b = append(b, `\t`...)
|
||||
case '\n':
|
||||
b = append(b, `\n`...)
|
||||
case '\f':
|
||||
b = append(b, `\f`...)
|
||||
case '\r':
|
||||
b = append(b, `\r`...)
|
||||
case '\\':
|
||||
b = append(b, `\\`...)
|
||||
case '"':
|
||||
b = append(b, `\"`...)
|
||||
default:
|
||||
b = append(b, fmt.Sprintf(`\u%04x`, r)...)
|
||||
}
|
||||
default:
|
||||
b = append(b, arr[:utf8.EncodeRune(arr[:], r)]...)
|
||||
}
|
||||
}
|
||||
b = append(b, '"')
|
||||
return Literal(b)
|
||||
}
|
||||
|
||||
// Int construct a JSON literal for a signed integer.
|
||||
func Int(v int64) Literal {
|
||||
return Literal(strconv.AppendInt(nil, v, 10))
|
||||
}
|
||||
|
||||
// Uint construct a JSON literal for an unsigned integer.
|
||||
func Uint(v uint64) Literal {
|
||||
return Literal(strconv.AppendUint(nil, v, 10))
|
||||
}
|
||||
|
||||
// Float construct a JSON literal for a floating-point number.
|
||||
// The values NaN, +Inf, and -Inf will be represented as a JSON string
|
||||
// with the values "NaN", "Infinity", and "-Infinity".
|
||||
func Float(v float64) Literal {
|
||||
switch {
|
||||
case math.IsNaN(v):
|
||||
return Literal(`"NaN"`)
|
||||
case math.IsInf(v, +1):
|
||||
return Literal(`"Infinity"`)
|
||||
case math.IsInf(v, -1):
|
||||
return Literal(`"-Infinity"`)
|
||||
default:
|
||||
b, _ := json.Marshal(v)
|
||||
return Literal(b)
|
||||
}
|
||||
}
|
||||
|
||||
func (b Literal) clone() ValueTrimmed {
|
||||
return Literal(copyBytes(b))
|
||||
}
|
||||
|
||||
// Kind represents each possible JSON literal kind with a single byte,
|
||||
// which is conveniently the first byte of that kind's grammar
|
||||
// with the restriction that numbers always be represented with '0'.
|
||||
func (b Literal) Kind() Kind {
|
||||
if len(b) == 0 {
|
||||
return 0
|
||||
}
|
||||
switch k := b[0]; k {
|
||||
case 'n', 'f', 't', '"':
|
||||
return Kind(k)
|
||||
case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
return '0'
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// IsValid reports whether b is a valid JSON null, boolean, string, or number.
|
||||
// The literal must not have surrounding whitespace.
|
||||
func (b Literal) IsValid() bool {
|
||||
// NOTE: The v1 json package is non-compliant with RFC 8259, section 8.1
|
||||
// in that it does not enforce the use of valid UTF-8.
|
||||
return json.Valid(b) && len(b) == len(bytes.TrimSpace(b))
|
||||
}
|
||||
|
||||
// Bool returns the value for a JSON boolean.
|
||||
// It returns false if the literal is not a JSON boolean.
|
||||
func (b Literal) Bool() bool {
|
||||
return string(b) == "true"
|
||||
}
|
||||
|
||||
// String returns the unescaped string value for a JSON string.
|
||||
// For other JSON kinds, this returns the raw JSON represention.
|
||||
func (b Literal) String() (s string) {
|
||||
if b.Kind() == '"' && json.Unmarshal(b, &s) == nil {
|
||||
return s
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// Int returns the signed integer value for a JSON number.
|
||||
// It returns 0 if the literal is not a signed integer.
|
||||
func (b Literal) Int() (n int64) {
|
||||
if b.Kind() == '0' && json.Unmarshal(b, &n) == nil {
|
||||
return n
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Uin returns the unsigned integer value for a JSON number.
|
||||
// It returns 0 if the literal is not an unsigned integer.
|
||||
func (b Literal) Uint() (n uint64) {
|
||||
if b.Kind() == '0' && json.Unmarshal(b, &n) == nil {
|
||||
return n
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Float returns the floating-point value for a JSON number.
|
||||
// It returns a NaN, +Inf, or -Inf value for any JSON string with the values
|
||||
// "NaN", "Infinity", or "-Infinity".
|
||||
// It returns 0 for all other cases.
|
||||
func (b Literal) Float() (n float64) {
|
||||
if b.Kind() == '0' && json.Unmarshal(b, &n) == nil {
|
||||
return n
|
||||
}
|
||||
if b.Kind() == '"' {
|
||||
switch b.String() {
|
||||
case "NaN":
|
||||
return math.NaN()
|
||||
case "Infinity":
|
||||
return math.Inf(+1)
|
||||
case "-Infinity":
|
||||
return math.Inf(-1)
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (Literal) isValueTrimmed() {}
|
||||
|
||||
// Object is an exact syntactic representation of a JSON object.
|
||||
type Object struct {
|
||||
// Members are the members of a JSON object.
|
||||
// A trailing comma is emitted only if the Value.AfterExtra
|
||||
// on the last value is non-nil. Otherwise it is omitted.
|
||||
Members []ObjectMember
|
||||
// AfterExtra are the comments and whitespace
|
||||
// after the preceding open brace or comma and before the closing brace.
|
||||
AfterExtra Extra
|
||||
}
|
||||
|
||||
type ObjectMember struct {
|
||||
Name, Value Value
|
||||
}
|
||||
|
||||
func (obj Object) length() int {
|
||||
return len(obj.Members)
|
||||
}
|
||||
|
||||
func (obj Object) firstValue() *Value {
|
||||
if len(obj.Members) > 0 {
|
||||
return &obj.Members[0].Name
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// allValues iterates all members of the object,
|
||||
// interleaved between the member name and the member value.
|
||||
func (obj Object) allValues() iter.Seq[*Value] {
|
||||
return func(yield func(*Value) bool) {
|
||||
for i := range obj.Members {
|
||||
if !yield(&obj.Members[i].Name) {
|
||||
return
|
||||
}
|
||||
if !yield(&obj.Members[i].Value) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (obj Object) lastValue() *Value {
|
||||
if len(obj.Members) > 0 {
|
||||
return &obj.Members[len(obj.Members)-1].Value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (obj *Object) beforeExtraAt(i int) *Extra {
|
||||
if i < len(obj.Members) {
|
||||
return &obj.Members[i].Name.BeforeExtra
|
||||
}
|
||||
return &obj.AfterExtra
|
||||
}
|
||||
|
||||
func (obj *Object) afterExtra() *Extra {
|
||||
return &obj.AfterExtra
|
||||
}
|
||||
|
||||
func (obj Object) clone() ValueTrimmed {
|
||||
if obj.Members != nil {
|
||||
obj.Members = append([]ObjectMember(nil), obj.Members...)
|
||||
for i := range obj.Members {
|
||||
obj.Members[i].Name = obj.Members[i].Name.Clone()
|
||||
obj.Members[i].Value = obj.Members[i].Value.Clone()
|
||||
}
|
||||
}
|
||||
obj.AfterExtra = copyBytes(obj.AfterExtra)
|
||||
return &obj
|
||||
}
|
||||
|
||||
func (Object) Kind() Kind { return '{' }
|
||||
|
||||
func (*Object) isValueTrimmed() {}
|
||||
|
||||
// Array is an exact syntactic representation of a JSON array.
|
||||
type Array struct {
|
||||
// Elements are the elements of a JSON array.
|
||||
// A trailing comma is emitted only if the Value.AfterExtra
|
||||
// on the last value is non-nil. Otherwise it is omitted.
|
||||
Elements []ArrayElement
|
||||
// AfterExtra are the comments and whitespace
|
||||
// after the preceding open bracket or comma and before the closing bracket.
|
||||
AfterExtra Extra
|
||||
}
|
||||
|
||||
type ArrayElement = Value
|
||||
|
||||
func (arr Array) length() int {
|
||||
return len(arr.Elements)
|
||||
}
|
||||
|
||||
func (arr Array) firstValue() *Value {
|
||||
if len(arr.Elements) > 0 {
|
||||
return &arr.Elements[0]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// allValues iterates all elements of the array.
|
||||
func (arr Array) allValues() iter.Seq[*Value] {
|
||||
return func(yield func(*Value) bool) {
|
||||
for i := range arr.Elements {
|
||||
if !yield(&arr.Elements[i]) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (arr Array) lastValue() *Value {
|
||||
if len(arr.Elements) > 0 {
|
||||
return &arr.Elements[len(arr.Elements)-1]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (arr *Array) beforeExtraAt(i int) *Extra {
|
||||
if i < len(arr.Elements) {
|
||||
return &arr.Elements[i].BeforeExtra
|
||||
}
|
||||
return &arr.AfterExtra
|
||||
}
|
||||
|
||||
func (arr *Array) afterExtra() *Extra {
|
||||
return &arr.AfterExtra
|
||||
}
|
||||
|
||||
func (arr Array) clone() ValueTrimmed {
|
||||
if arr.Elements != nil {
|
||||
arr.Elements = append([]Value(nil), arr.Elements...)
|
||||
for i := range arr.Elements {
|
||||
arr.Elements[i] = arr.Elements[i].Clone()
|
||||
}
|
||||
}
|
||||
arr.AfterExtra = copyBytes(arr.AfterExtra)
|
||||
return &arr
|
||||
}
|
||||
|
||||
func (Array) Kind() Kind { return '[' }
|
||||
|
||||
func (*Array) isValueTrimmed() {}
|
||||
|
||||
// composite are the common methods of Object and Array.
|
||||
type composite interface {
|
||||
Kind() Kind
|
||||
length() int
|
||||
|
||||
firstValue() *Value
|
||||
allValues() iter.Seq[*Value]
|
||||
lastValue() *Value
|
||||
|
||||
getAt(int) ValueTrimmed
|
||||
setAt(int, ValueTrimmed)
|
||||
insertAt(int, ValueTrimmed)
|
||||
removeAt(int) ValueTrimmed
|
||||
|
||||
beforeExtraAt(int) *Extra
|
||||
afterExtra() *Extra
|
||||
}
|
||||
|
||||
func hasTrailingComma(comp composite) bool {
|
||||
if last := comp.lastValue(); last != nil && last.AfterExtra != nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
func setTrailingComma(comp composite, v bool) {
|
||||
if last := comp.lastValue(); last != nil {
|
||||
switch {
|
||||
case v && last.AfterExtra == nil:
|
||||
last.AfterExtra = []byte{}
|
||||
case !v && last.AfterExtra != nil:
|
||||
*comp.afterExtra() = append(last.AfterExtra, *comp.afterExtra()...)
|
||||
last.AfterExtra = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
_ composite = (*Object)(nil)
|
||||
_ composite = (*Array)(nil)
|
||||
)
|
||||
|
||||
// Extra is the raw bytes for whitespace and comments.
|
||||
// Whitespace per RFC 8259, section 2 are permitted.
|
||||
// Line comments that start with "//" and end with "\n" are permitted.
|
||||
// Block comments that start with "/*" and end with "*/" are permitted.
|
||||
type Extra []byte
|
||||
|
||||
// IsValid reports whether the whitespace and comments are valid
|
||||
// according to the HuJSON grammar.
|
||||
func (b Extra) IsValid() bool {
|
||||
n, err := consumeExtra(0, b)
|
||||
return n == len(b) && err == nil
|
||||
}
|
||||
|
||||
func copyBytes(b []byte) []byte {
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
return append([]byte(nil), b...)
|
||||
}
|
||||
Reference in New Issue
Block a user