vendor: move vendored sources in-tree

This should make it easier to see changes instead of just a blob
This commit is contained in:
2025-08-25 19:43:16 +02:00
parent 7e055c3169
commit 082db173f3
634 changed files with 148182 additions and 20 deletions

27
vendor/github.com/tailscale/hujson/LICENSE generated vendored Normal file
View 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
View File

@@ -0,0 +1,36 @@
# HuJSON - "Human JSON" ([JWCC](https://nigeltao.github.io/blog/2021/json-with-commas-comments.html))
[![Go Reference](https://pkg.go.dev/badge/github.com/tailscale/hujson.svg)](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
View 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
View 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
View File

@@ -0,0 +1,6 @@
go 1.23
use (
.
./cmd/hujsonfmt
)

92
vendor/github.com/tailscale/hujson/pack.go generated vendored Normal file
View 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
View 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
View 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
View 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
View 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...)
}