vendor dependencies
This commit is contained in:
parent
dd693fb000
commit
af65c66317
3
vendor/github.com/go-json-experiment/json/AUTHORS
generated
vendored
Normal file
3
vendor/github.com/go-json-experiment/json/AUTHORS
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# This source code refers to The Go Authors for copyright purposes.
|
||||
# The master list of authors is in the main Go distribution,
|
||||
# visible at https://tip.golang.org/AUTHORS.
|
3
vendor/github.com/go-json-experiment/json/CONTRIBUTORS
generated
vendored
Normal file
3
vendor/github.com/go-json-experiment/json/CONTRIBUTORS
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# This source code was written by the Go contributors.
|
||||
# The master list of contributors is in the main Go distribution,
|
||||
# visible at https://tip.golang.org/CONTRIBUTORS.
|
27
vendor/github.com/go-json-experiment/json/LICENSE
generated
vendored
Normal file
27
vendor/github.com/go-json-experiment/json/LICENSE
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
Copyright (c) 2020 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
175
vendor/github.com/go-json-experiment/json/README.md
generated
vendored
Normal file
175
vendor/github.com/go-json-experiment/json/README.md
generated
vendored
Normal file
@ -0,0 +1,175 @@
|
||||
# JSON Serialization (v2)
|
||||
|
||||
[](https://pkg.go.dev/github.com/go-json-experiment/json)
|
||||
[](https://github.com/go-json-experiment/json/actions)
|
||||
|
||||
This module hosts an experimental implementation of v2 `encoding/json`.
|
||||
The API is unstable and breaking changes will regularly be made.
|
||||
Do not depend on this in publicly available modules.
|
||||
|
||||
Any commits that make breaking API or behavior changes will be marked
|
||||
with the string "WARNING: " near the top of the commit message.
|
||||
It is your responsibility to inspect the list of commit changes
|
||||
when upgrading the module. Not all breaking changes will lead to build failures.
|
||||
|
||||
A [proposal to include this module in Go as `encoding/json/v2` and `encoding/json/jsontext`](https://github.com/golang/go/issues/71497) has been started on the Go Github project on 2025-01-30. Please provide your feedback there.
|
||||
|
||||
## Goals and objectives
|
||||
|
||||
* **Mostly backwards compatible:** If possible, v2 should aim to be _mostly_
|
||||
compatible with v1 in terms of both API and default behavior to ease migration.
|
||||
For example, the `Marshal` and `Unmarshal` functions are the most widely used
|
||||
declarations in the v1 package. It seems sensible for equivalent functionality
|
||||
in v2 to be named the same and have a mostly compatible signature.
|
||||
Behaviorally, we should aim for 95% to 99% backwards compatibility.
|
||||
We do not aim for 100% compatibility since we want the freedom to break
|
||||
certain behaviors that are now considered to have been a mistake.
|
||||
Options exist that can bring the v2 implementation to 100% compatibility,
|
||||
but it will not be the default.
|
||||
|
||||
* **More flexible:** There is a
|
||||
[long list of feature requests](https://github.com/golang/go/issues?q=is%3Aissue+is%3Aopen+encoding%2Fjson+in%3Atitle).
|
||||
We should aim to provide the most flexible features that addresses most usages.
|
||||
We do not want to over fit the v2 API to handle every possible use case.
|
||||
Ideally, the features provided should be orthogonal in nature such that
|
||||
any combination of features results in as few surprising edge cases as possible.
|
||||
|
||||
* **More performant:** JSON serialization is widely used and any bit of extra
|
||||
performance gains will be greatly appreciated. Some rarely used behaviors of v1
|
||||
may be dropped in favor of better performance. For example,
|
||||
despite `Encoder` and `Decoder` operating on an `io.Writer` and `io.Reader`,
|
||||
they do not operate in a truly streaming manner,
|
||||
leading to a loss in performance. The v2 implementation should aim to be truly
|
||||
streaming by default (see [#33714](https://golang.org/issue/33714)).
|
||||
|
||||
* **Easy to use (hard to misuse):** The v2 API should aim to make
|
||||
the common case easy and the less common case at least possible.
|
||||
The API should avoid behavior that goes contrary to user expectation,
|
||||
which may result in subtle bugs (see [#36225](https://golang.org/issue/36225)).
|
||||
|
||||
* **v1 and v2 maintainability:** Since the v1 implementation must stay forever,
|
||||
it would be beneficial if v1 could be implemented under the hood with v2,
|
||||
allowing for less maintenance burden in the future. This probably implies that
|
||||
behavioral changes in v2 relative to v1 need to be exposed as options.
|
||||
|
||||
* **Avoid unsafe:** Standard library packages generally avoid the use of
|
||||
package `unsafe` even if it could provide a performance boost.
|
||||
We aim to preserve this property.
|
||||
|
||||
## Expectations
|
||||
|
||||
While this module aims to possibly be the v2 implementation of `encoding/json`,
|
||||
there is no guarantee that this outcome will occur. As with any major change
|
||||
to the Go standard library, this will eventually go through the
|
||||
[Go proposal process](https://github.com/golang/proposal#readme).
|
||||
At the present moment, this is still in the design and experimentation phase
|
||||
and is not ready for a formal proposal.
|
||||
|
||||
There are several possible outcomes from this experiment:
|
||||
1. We determine that a v2 `encoding/json` would not provide sufficient benefit
|
||||
over the existing v1 `encoding/json` package. Thus, we abandon this effort.
|
||||
2. We propose a v2 `encoding/json` design, but it is rejected in favor of some
|
||||
other design that is considered superior.
|
||||
3. We propose a v2 `encoding/json` design, but rather than adding an entirely
|
||||
new v2 `encoding/json` package, we decide to merge its functionality into
|
||||
the existing v1 `encoding/json` package.
|
||||
4. We propose a v2 `encoding/json` design and it is accepted, resulting in
|
||||
its addition to the standard library.
|
||||
5. Some other unforeseen outcome (among the infinite number of possibilities).
|
||||
|
||||
## Development
|
||||
|
||||
This module is primarily developed by
|
||||
[@dsnet](https://github.com/dsnet),
|
||||
[@mvdan](https://github.com/mvdan), and
|
||||
[@johanbrandhorst](https://github.com/johanbrandhorst)
|
||||
with feedback provided by
|
||||
[@rogpeppe](https://github.com/rogpeppe),
|
||||
[@ChrisHines](https://github.com/ChrisHines), and
|
||||
[@rsc](https://github.com/rsc).
|
||||
|
||||
Discussion about semantics occur semi-regularly, where a
|
||||
[record of past meetings can be found here](https://docs.google.com/document/d/1rovrOTd-wTawGMPPlPuKhwXaYBg9VszTXR9AQQL5LfI/edit?usp=sharing).
|
||||
|
||||
## Design overview
|
||||
|
||||
This package aims to provide a clean separation between syntax and semantics.
|
||||
Syntax deals with the structural representation of JSON (as specified in
|
||||
[RFC 4627](https://tools.ietf.org/html/rfc4627),
|
||||
[RFC 7159](https://tools.ietf.org/html/rfc7159),
|
||||
[RFC 7493](https://tools.ietf.org/html/rfc7493),
|
||||
[RFC 8259](https://tools.ietf.org/html/rfc8259), and
|
||||
[RFC 8785](https://tools.ietf.org/html/rfc8785)).
|
||||
Semantics deals with the meaning of syntactic data as usable application data.
|
||||
|
||||
The `Encoder` and `Decoder` types are streaming tokenizers concerned with the
|
||||
packing or parsing of JSON data. They operate on `Token` and `Value` types
|
||||
which represent the common data structures that are representable in JSON.
|
||||
`Encoder` and `Decoder` do not aim to provide any interpretation of the data.
|
||||
|
||||
Functions like `Marshal`, `MarshalWrite`, `MarshalEncode`, `Unmarshal`,
|
||||
`UnmarshalRead`, and `UnmarshalDecode` provide semantic meaning by correlating
|
||||
any arbitrary Go type with some JSON representation of that type (as stored in
|
||||
data types like `[]byte`, `io.Writer`, `io.Reader`, `Encoder`, or `Decoder`).
|
||||
|
||||

|
||||
|
||||
This diagram provides a high-level overview of the v2 `json` and `jsontext` packages.
|
||||
Purple blocks represent types, while blue blocks represent functions or methods.
|
||||
The arrows and their direction represent the approximate flow of data.
|
||||
The bottom half of the diagram contains functionality that is only concerned
|
||||
with syntax (implemented by the `jsontext` package),
|
||||
while the upper half contains functionality that assigns
|
||||
semantic meaning to syntactic data handled by the bottom half
|
||||
(as implemented by the v2 `json` package).
|
||||
|
||||
In contrast to v1 `encoding/json`, options are represented as separate types
|
||||
rather than being setter methods on the `Encoder` or `Decoder` types.
|
||||
Some options affects JSON serialization at the syntactic layer,
|
||||
while others affect it at the semantic layer.
|
||||
Some options only affect JSON when decoding,
|
||||
while others affect JSON while encoding.
|
||||
|
||||
## Behavior changes
|
||||
|
||||
The v2 `json` package changes the default behavior of `Marshal` and `Unmarshal`
|
||||
relative to the v1 `json` package to be more sensible.
|
||||
Some of these behavior changes have options and workarounds to opt into
|
||||
behavior similar to what v1 provided.
|
||||
|
||||
This table shows an overview of the changes:
|
||||
|
||||
| v1 | v2 | Details |
|
||||
| -- | -- | ------- |
|
||||
| JSON object members are unmarshaled into a Go struct using a **case-insensitive name match**. | JSON object members are unmarshaled into a Go struct using a **case-sensitive name match**. | [CaseSensitivity](/v1/diff_test.go#:~:text=TestCaseSensitivity) |
|
||||
| When marshaling a Go struct, a struct field marked as `omitempty` is omitted if **the field value is an empty Go value**, which is defined as false, 0, a nil pointer, a nil interface value, and any empty array, slice, map, or string. | When marshaling a Go struct, a struct field marked as `omitempty` is omitted if **the field value would encode as an empty JSON value**, which is defined as a JSON null, or an empty JSON string, object, or array. | [OmitEmptyOption](/v1/diff_test.go#:~:text=TestOmitEmptyOption) |
|
||||
| The `string` option **does affect** Go strings and bools. | The `string` option **does not affect** Go strings or bools. | [StringOption](/v1/diff_test.go#:~:text=TestStringOption) |
|
||||
| The `string` option **does not recursively affect** sub-values of the Go field value. | The `string` option **does recursively affect** sub-values of the Go field value. | [StringOption](/v1/diff_test.go#:~:text=TestStringOption) |
|
||||
| The `string` option **sometimes accepts** a JSON null escaped within a JSON string. | The `string` option **never accepts** a JSON null escaped within a JSON string. | [StringOption](/v1/diff_test.go#:~:text=TestStringOption) |
|
||||
| A nil Go slice is marshaled as a **JSON null**. | A nil Go slice is marshaled as an **empty JSON array**. | [NilSlicesAndMaps](/v1/diff_test.go#:~:text=TestNilSlicesAndMaps) |
|
||||
| A nil Go map is marshaled as a **JSON null**. | A nil Go map is marshaled as an **empty JSON object**. | [NilSlicesAndMaps](/v1/diff_test.go#:~:text=TestNilSlicesAndMaps) |
|
||||
| A Go array may be unmarshaled from a **JSON array of any length**. | A Go array must be unmarshaled from a **JSON array of the same length**. | [Arrays](/v1/diff_test.go#:~:text=Arrays) |
|
||||
| A Go byte array is represented as a **JSON array of JSON numbers**. | A Go byte array is represented as a **Base64-encoded JSON string**. | [ByteArrays](/v1/diff_test.go#:~:text=TestByteArrays) |
|
||||
| `MarshalJSON` and `UnmarshalJSON` methods declared on a pointer receiver are **inconsistently called**. | `MarshalJSON` and `UnmarshalJSON` methods declared on a pointer receiver are **consistently called**. | [PointerReceiver](/v1/diff_test.go#:~:text=TestPointerReceiver) |
|
||||
| A Go map is marshaled in a **deterministic order**. | A Go map is marshaled in a **non-deterministic order**. | [MapDeterminism](/v1/diff_test.go#:~:text=TestMapDeterminism) |
|
||||
| JSON strings are encoded **with HTML-specific characters being escaped**. | JSON strings are encoded **without any characters being escaped** (unless necessary). | [EscapeHTML](/v1/diff_test.go#:~:text=TestEscapeHTML) |
|
||||
| When marshaling, invalid UTF-8 within a Go string **are silently replaced**. | When marshaling, invalid UTF-8 within a Go string **results in an error**. | [InvalidUTF8](/v1/diff_test.go#:~:text=TestInvalidUTF8) |
|
||||
| When unmarshaling, invalid UTF-8 within a JSON string **are silently replaced**. | When unmarshaling, invalid UTF-8 within a JSON string **results in an error**. | [InvalidUTF8](/v1/diff_test.go#:~:text=TestInvalidUTF8) |
|
||||
| When marshaling, **an error does not occur** if the output JSON value contains objects with duplicate names. | When marshaling, **an error does occur** if the output JSON value contains objects with duplicate names. | [DuplicateNames](/v1/diff_test.go#:~:text=TestDuplicateNames) |
|
||||
| When unmarshaling, **an error does not occur** if the input JSON value contains objects with duplicate names. | When unmarshaling, **an error does occur** if the input JSON value contains objects with duplicate names. | [DuplicateNames](/v1/diff_test.go#:~:text=TestDuplicateNames) |
|
||||
| Unmarshaling a JSON null into a non-empty Go value **inconsistently clears the value or does nothing**. | Unmarshaling a JSON null into a non-empty Go value **always clears the value**. | [MergeNull](/v1/diff_test.go#:~:text=TestMergeNull) |
|
||||
| Unmarshaling a JSON value into a non-empty Go value **follows inconsistent and bizarre behavior**. | Unmarshaling a JSON value into a non-empty Go value **always merges if the input is an object, and otherwise replaces**. | [MergeComposite](/v1/diff_test.go#:~:text=TestMergeComposite) |
|
||||
| A `time.Duration` is represented as a **JSON number containing the decimal number of nanoseconds**. | A `time.Duration` is represented as a **JSON string containing the formatted duration (e.g., "1h2m3.456s")**. | [TimeDurations](/v1/diff_test.go#:~:text=TestTimeDurations) |
|
||||
| A Go struct with only unexported fields **can be serialized**. | A Go struct with only unexported fields **cannot be serialized**. | [EmptyStructs](/v1/diff_test.go#:~:text=TestEmptyStructs) |
|
||||
|
||||
See [diff_test.go](/v1/diff_test.go) for details about every change.
|
||||
|
||||
## Performance
|
||||
|
||||
One of the goals of the v2 module is to be more performant than v1,
|
||||
but not at the expense of correctness.
|
||||
In general, v2 is at performance parity with v1 for marshaling,
|
||||
but dramatically faster for unmarshaling.
|
||||
|
||||
See https://github.com/go-json-experiment/jsonbench for benchmarks
|
||||
comparing v2 with v1 and a number of other popular JSON implementations.
|
BIN
vendor/github.com/go-json-experiment/json/api.png
generated
vendored
Normal file
BIN
vendor/github.com/go-json-experiment/json/api.png
generated
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 151 KiB |
568
vendor/github.com/go-json-experiment/json/arshal.go
generated
vendored
Normal file
568
vendor/github.com/go-json-experiment/json/arshal.go
generated
vendored
Normal file
@ -0,0 +1,568 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding"
|
||||
"io"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-json-experiment/json/internal"
|
||||
"github.com/go-json-experiment/json/internal/jsonflags"
|
||||
"github.com/go-json-experiment/json/internal/jsonopts"
|
||||
"github.com/go-json-experiment/json/jsontext"
|
||||
)
|
||||
|
||||
// Reference encoding and time packages to assist pkgsite
|
||||
// in being able to hotlink references to those packages.
|
||||
var (
|
||||
_ encoding.TextMarshaler
|
||||
_ encoding.TextAppender
|
||||
_ encoding.TextUnmarshaler
|
||||
_ time.Time
|
||||
_ time.Duration
|
||||
)
|
||||
|
||||
// export exposes internal functionality of the "jsontext" package.
|
||||
var export = jsontext.Internal.Export(&internal.AllowInternalUse)
|
||||
|
||||
// Marshal serializes a Go value as a []byte according to the provided
|
||||
// marshal and encode options (while ignoring unmarshal or decode options).
|
||||
// It does not terminate the output with a newline.
|
||||
//
|
||||
// Type-specific marshal functions and methods take precedence
|
||||
// over the default representation of a value.
|
||||
// Functions or methods that operate on *T are only called when encoding
|
||||
// a value of type T (by taking its address) or a non-nil value of *T.
|
||||
// Marshal ensures that a value is always addressable
|
||||
// (by boxing it on the heap if necessary) so that
|
||||
// these functions and methods can be consistently called. For performance,
|
||||
// it is recommended that Marshal be passed a non-nil pointer to the value.
|
||||
//
|
||||
// The input value is encoded as JSON according the following rules:
|
||||
//
|
||||
// - If any type-specific functions in a [WithMarshalers] option match
|
||||
// the value type, then those functions are called to encode the value.
|
||||
// If all applicable functions return [SkipFunc],
|
||||
// then the value is encoded according to subsequent rules.
|
||||
//
|
||||
// - If the value type implements [MarshalerTo],
|
||||
// then the MarshalJSONTo method is called to encode the value.
|
||||
//
|
||||
// - If the value type implements [Marshaler],
|
||||
// then the MarshalJSON method is called to encode the value.
|
||||
//
|
||||
// - If the value type implements [encoding.TextAppender],
|
||||
// then the AppendText method is called to encode the value and
|
||||
// subsequently encode its result as a JSON string.
|
||||
//
|
||||
// - If the value type implements [encoding.TextMarshaler],
|
||||
// then the MarshalText method is called to encode the value and
|
||||
// subsequently encode its result as a JSON string.
|
||||
//
|
||||
// - Otherwise, the value is encoded according to the value's type
|
||||
// as described in detail below.
|
||||
//
|
||||
// Most Go types have a default JSON representation.
|
||||
// Certain types support specialized formatting according to
|
||||
// a format flag optionally specified in the Go struct tag
|
||||
// for the struct field that contains the current value
|
||||
// (see the “JSON Representation of Go structs” section for more details).
|
||||
//
|
||||
// The representation of each type is as follows:
|
||||
//
|
||||
// - A Go boolean is encoded as a JSON boolean (e.g., true or false).
|
||||
// It does not support any custom format flags.
|
||||
//
|
||||
// - A Go string is encoded as a JSON string.
|
||||
// It does not support any custom format flags.
|
||||
//
|
||||
// - A Go []byte or [N]byte is encoded as a JSON string containing
|
||||
// the binary value encoded using RFC 4648.
|
||||
// If the format is "base64" or unspecified, then this uses RFC 4648, section 4.
|
||||
// If the format is "base64url", then this uses RFC 4648, section 5.
|
||||
// If the format is "base32", then this uses RFC 4648, section 6.
|
||||
// If the format is "base32hex", then this uses RFC 4648, section 7.
|
||||
// If the format is "base16" or "hex", then this uses RFC 4648, section 8.
|
||||
// If the format is "array", then the bytes value is encoded as a JSON array
|
||||
// where each byte is recursively JSON-encoded as each JSON array element.
|
||||
//
|
||||
// - A Go integer is encoded as a JSON number without fractions or exponents.
|
||||
// If [StringifyNumbers] is specified or encoding a JSON object name,
|
||||
// then the JSON number is encoded within a JSON string.
|
||||
// It does not support any custom format flags.
|
||||
//
|
||||
// - A Go float is encoded as a JSON number.
|
||||
// If [StringifyNumbers] is specified or encoding a JSON object name,
|
||||
// then the JSON number is encoded within a JSON string.
|
||||
// If the format is "nonfinite", then NaN, +Inf, and -Inf are encoded as
|
||||
// the JSON strings "NaN", "Infinity", and "-Infinity", respectively.
|
||||
// Otherwise, the presence of non-finite numbers results in a [SemanticError].
|
||||
//
|
||||
// - A Go map is encoded as a JSON object, where each Go map key and value
|
||||
// is recursively encoded as a name and value pair in the JSON object.
|
||||
// The Go map key must encode as a JSON string, otherwise this results
|
||||
// in a [SemanticError]. The Go map is traversed in a non-deterministic order.
|
||||
// For deterministic encoding, consider using the [Deterministic] option.
|
||||
// If the format is "emitnull", then a nil map is encoded as a JSON null.
|
||||
// If the format is "emitempty", then a nil map is encoded as an empty JSON object,
|
||||
// regardless of whether [FormatNilMapAsNull] is specified.
|
||||
// Otherwise by default, a nil map is encoded as an empty JSON object.
|
||||
//
|
||||
// - A Go struct is encoded as a JSON object.
|
||||
// See the “JSON Representation of Go structs” section
|
||||
// in the package-level documentation for more details.
|
||||
//
|
||||
// - A Go slice is encoded as a JSON array, where each Go slice element
|
||||
// is recursively JSON-encoded as the elements of the JSON array.
|
||||
// If the format is "emitnull", then a nil slice is encoded as a JSON null.
|
||||
// If the format is "emitempty", then a nil slice is encoded as an empty JSON array,
|
||||
// regardless of whether [FormatNilSliceAsNull] is specified.
|
||||
// Otherwise by default, a nil slice is encoded as an empty JSON array.
|
||||
//
|
||||
// - A Go array is encoded as a JSON array, where each Go array element
|
||||
// is recursively JSON-encoded as the elements of the JSON array.
|
||||
// The JSON array length is always identical to the Go array length.
|
||||
// It does not support any custom format flags.
|
||||
//
|
||||
// - A Go pointer is encoded as a JSON null if nil, otherwise it is
|
||||
// the recursively JSON-encoded representation of the underlying value.
|
||||
// Format flags are forwarded to the encoding of the underlying value.
|
||||
//
|
||||
// - A Go interface is encoded as a JSON null if nil, otherwise it is
|
||||
// the recursively JSON-encoded representation of the underlying value.
|
||||
// It does not support any custom format flags.
|
||||
//
|
||||
// - A Go [time.Time] is encoded as a JSON string containing the timestamp
|
||||
// formatted in RFC 3339 with nanosecond precision.
|
||||
// If the format matches one of the format constants declared
|
||||
// in the time package (e.g., RFC1123), then that format is used.
|
||||
// If the format is "unix", "unixmilli", "unixmicro", or "unixnano",
|
||||
// then the timestamp is encoded as a JSON number of the number of seconds
|
||||
// (or milliseconds, microseconds, or nanoseconds) since the Unix epoch,
|
||||
// which is January 1st, 1970 at 00:00:00 UTC.
|
||||
// Otherwise, the format is used as-is with [time.Time.Format] if non-empty.
|
||||
//
|
||||
// - A Go [time.Duration] is encoded as a JSON string containing the duration
|
||||
// formatted according to [time.Duration.String].
|
||||
// If the format is "sec", "milli", "micro", or "nano",
|
||||
// then the duration is encoded as a JSON number of the number of seconds
|
||||
// (or milliseconds, microseconds, or nanoseconds) in the duration.
|
||||
// If the format is "units", it uses [time.Duration.String].
|
||||
//
|
||||
// - All other Go types (e.g., complex numbers, channels, and functions)
|
||||
// have no default representation and result in a [SemanticError].
|
||||
//
|
||||
// JSON cannot represent cyclic data structures and Marshal does not handle them.
|
||||
// Passing cyclic structures will result in an error.
|
||||
func Marshal(in any, opts ...Options) (out []byte, err error) {
|
||||
enc := export.GetBufferedEncoder(opts...)
|
||||
defer export.PutBufferedEncoder(enc)
|
||||
xe := export.Encoder(enc)
|
||||
xe.Flags.Set(jsonflags.OmitTopLevelNewline | 1)
|
||||
err = marshalEncode(enc, in, &xe.Struct)
|
||||
if err != nil && xe.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
|
||||
return nil, internal.TransformMarshalError(in, err)
|
||||
}
|
||||
return bytes.Clone(xe.Buf), err
|
||||
}
|
||||
|
||||
// MarshalWrite serializes a Go value into an [io.Writer] according to the provided
|
||||
// marshal and encode options (while ignoring unmarshal or decode options).
|
||||
// It does not terminate the output with a newline.
|
||||
// See [Marshal] for details about the conversion of a Go value into JSON.
|
||||
func MarshalWrite(out io.Writer, in any, opts ...Options) (err error) {
|
||||
enc := export.GetStreamingEncoder(out, opts...)
|
||||
defer export.PutStreamingEncoder(enc)
|
||||
xe := export.Encoder(enc)
|
||||
xe.Flags.Set(jsonflags.OmitTopLevelNewline | 1)
|
||||
err = marshalEncode(enc, in, &xe.Struct)
|
||||
if err != nil && xe.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
|
||||
return internal.TransformMarshalError(in, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// MarshalEncode serializes a Go value into an [jsontext.Encoder] according to
|
||||
// the provided marshal options (while ignoring unmarshal, encode, or decode options).
|
||||
// Any marshal-relevant options already specified on the [jsontext.Encoder]
|
||||
// take lower precedence than the set of options provided by the caller.
|
||||
// Unlike [Marshal] and [MarshalWrite], encode options are ignored because
|
||||
// they must have already been specified on the provided [jsontext.Encoder].
|
||||
//
|
||||
// See [Marshal] for details about the conversion of a Go value into JSON.
|
||||
func MarshalEncode(out *jsontext.Encoder, in any, opts ...Options) (err error) {
|
||||
xe := export.Encoder(out)
|
||||
if len(opts) > 0 {
|
||||
optsOriginal := xe.Struct
|
||||
defer func() { xe.Struct = optsOriginal }()
|
||||
xe.Struct.JoinWithoutCoderOptions(opts...)
|
||||
}
|
||||
err = marshalEncode(out, in, &xe.Struct)
|
||||
if err != nil && xe.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
|
||||
return internal.TransformMarshalError(in, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func marshalEncode(out *jsontext.Encoder, in any, mo *jsonopts.Struct) (err error) {
|
||||
v := reflect.ValueOf(in)
|
||||
if !v.IsValid() || (v.Kind() == reflect.Pointer && v.IsNil()) {
|
||||
return out.WriteToken(jsontext.Null)
|
||||
}
|
||||
// Shallow copy non-pointer values to obtain an addressable value.
|
||||
// It is beneficial to performance to always pass pointers to avoid this.
|
||||
forceAddr := v.Kind() != reflect.Pointer
|
||||
if forceAddr {
|
||||
v2 := reflect.New(v.Type())
|
||||
v2.Elem().Set(v)
|
||||
v = v2
|
||||
}
|
||||
va := addressableValue{v.Elem(), forceAddr} // dereferenced pointer is always addressable
|
||||
t := va.Type()
|
||||
|
||||
// Lookup and call the marshal function for this type.
|
||||
marshal := lookupArshaler(t).marshal
|
||||
if mo.Marshalers != nil {
|
||||
marshal, _ = mo.Marshalers.(*Marshalers).lookup(marshal, t)
|
||||
}
|
||||
if err := marshal(out, va, mo); err != nil {
|
||||
if !mo.Flags.Get(jsonflags.AllowDuplicateNames) {
|
||||
export.Encoder(out).Tokens.InvalidateDisabledNamespaces()
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unmarshal decodes a []byte input into a Go value according to the provided
|
||||
// unmarshal and decode options (while ignoring marshal or encode options).
|
||||
// The input must be a single JSON value with optional whitespace interspersed.
|
||||
// The output must be a non-nil pointer.
|
||||
//
|
||||
// Type-specific unmarshal functions and methods take precedence
|
||||
// over the default representation of a value.
|
||||
// Functions or methods that operate on *T are only called when decoding
|
||||
// a value of type T (by taking its address) or a non-nil value of *T.
|
||||
// Unmarshal ensures that a value is always addressable
|
||||
// (by boxing it on the heap if necessary) so that
|
||||
// these functions and methods can be consistently called.
|
||||
//
|
||||
// The input is decoded into the output according the following rules:
|
||||
//
|
||||
// - If any type-specific functions in a [WithUnmarshalers] option match
|
||||
// the value type, then those functions are called to decode the JSON
|
||||
// value. If all applicable functions return [SkipFunc],
|
||||
// then the input is decoded according to subsequent rules.
|
||||
//
|
||||
// - If the value type implements [UnmarshalerFrom],
|
||||
// then the UnmarshalJSONFrom method is called to decode the JSON value.
|
||||
//
|
||||
// - If the value type implements [Unmarshaler],
|
||||
// then the UnmarshalJSON method is called to decode the JSON value.
|
||||
//
|
||||
// - If the value type implements [encoding.TextUnmarshaler],
|
||||
// then the input is decoded as a JSON string and
|
||||
// the UnmarshalText method is called with the decoded string value.
|
||||
// This fails with a [SemanticError] if the input is not a JSON string.
|
||||
//
|
||||
// - Otherwise, the JSON value is decoded according to the value's type
|
||||
// as described in detail below.
|
||||
//
|
||||
// Most Go types have a default JSON representation.
|
||||
// Certain types support specialized formatting according to
|
||||
// a format flag optionally specified in the Go struct tag
|
||||
// for the struct field that contains the current value
|
||||
// (see the “JSON Representation of Go structs” section for more details).
|
||||
// A JSON null may be decoded into every supported Go value where
|
||||
// it is equivalent to storing the zero value of the Go value.
|
||||
// If the input JSON kind is not handled by the current Go value type,
|
||||
// then this fails with a [SemanticError]. Unless otherwise specified,
|
||||
// the decoded value replaces any pre-existing value.
|
||||
//
|
||||
// The representation of each type is as follows:
|
||||
//
|
||||
// - A Go boolean is decoded from a JSON boolean (e.g., true or false).
|
||||
// It does not support any custom format flags.
|
||||
//
|
||||
// - A Go string is decoded from a JSON string.
|
||||
// It does not support any custom format flags.
|
||||
//
|
||||
// - A Go []byte or [N]byte is decoded from a JSON string
|
||||
// containing the binary value encoded using RFC 4648.
|
||||
// If the format is "base64" or unspecified, then this uses RFC 4648, section 4.
|
||||
// If the format is "base64url", then this uses RFC 4648, section 5.
|
||||
// If the format is "base32", then this uses RFC 4648, section 6.
|
||||
// If the format is "base32hex", then this uses RFC 4648, section 7.
|
||||
// If the format is "base16" or "hex", then this uses RFC 4648, section 8.
|
||||
// If the format is "array", then the Go slice or array is decoded from a
|
||||
// JSON array where each JSON element is recursively decoded for each byte.
|
||||
// When decoding into a non-nil []byte, the slice length is reset to zero
|
||||
// and the decoded input is appended to it.
|
||||
// When decoding into a [N]byte, the input must decode to exactly N bytes,
|
||||
// otherwise it fails with a [SemanticError].
|
||||
//
|
||||
// - A Go integer is decoded from a JSON number.
|
||||
// It must be decoded from a JSON string containing a JSON number
|
||||
// if [StringifyNumbers] is specified or decoding a JSON object name.
|
||||
// It fails with a [SemanticError] if the JSON number
|
||||
// has a fractional or exponent component.
|
||||
// It also fails if it overflows the representation of the Go integer type.
|
||||
// It does not support any custom format flags.
|
||||
//
|
||||
// - A Go float is decoded from a JSON number.
|
||||
// It must be decoded from a JSON string containing a JSON number
|
||||
// if [StringifyNumbers] is specified or decoding a JSON object name.
|
||||
// It fails if it overflows the representation of the Go float type.
|
||||
// If the format is "nonfinite", then the JSON strings
|
||||
// "NaN", "Infinity", and "-Infinity" are decoded as NaN, +Inf, and -Inf.
|
||||
// Otherwise, the presence of such strings results in a [SemanticError].
|
||||
//
|
||||
// - A Go map is decoded from a JSON object,
|
||||
// where each JSON object name and value pair is recursively decoded
|
||||
// as the Go map key and value. Maps are not cleared.
|
||||
// If the Go map is nil, then a new map is allocated to decode into.
|
||||
// If the decoded key matches an existing Go map entry, the entry value
|
||||
// is reused by decoding the JSON object value into it.
|
||||
// The formats "emitnull" and "emitempty" have no effect when decoding.
|
||||
//
|
||||
// - A Go struct is decoded from a JSON object.
|
||||
// See the “JSON Representation of Go structs” section
|
||||
// in the package-level documentation for more details.
|
||||
//
|
||||
// - A Go slice is decoded from a JSON array, where each JSON element
|
||||
// is recursively decoded and appended to the Go slice.
|
||||
// Before appending into a Go slice, a new slice is allocated if it is nil,
|
||||
// otherwise the slice length is reset to zero.
|
||||
// The formats "emitnull" and "emitempty" have no effect when decoding.
|
||||
//
|
||||
// - A Go array is decoded from a JSON array, where each JSON array element
|
||||
// is recursively decoded as each corresponding Go array element.
|
||||
// Each Go array element is zeroed before decoding into it.
|
||||
// It fails with a [SemanticError] if the JSON array does not contain
|
||||
// the exact same number of elements as the Go array.
|
||||
// It does not support any custom format flags.
|
||||
//
|
||||
// - A Go pointer is decoded based on the JSON kind and underlying Go type.
|
||||
// If the input is a JSON null, then this stores a nil pointer.
|
||||
// Otherwise, it allocates a new underlying value if the pointer is nil,
|
||||
// and recursively JSON decodes into the underlying value.
|
||||
// Format flags are forwarded to the decoding of the underlying type.
|
||||
//
|
||||
// - A Go interface is decoded based on the JSON kind and underlying Go type.
|
||||
// If the input is a JSON null, then this stores a nil interface value.
|
||||
// Otherwise, a nil interface value of an empty interface type is initialized
|
||||
// with a zero Go bool, string, float64, map[string]any, or []any if the
|
||||
// input is a JSON boolean, string, number, object, or array, respectively.
|
||||
// If the interface value is still nil, then this fails with a [SemanticError]
|
||||
// since decoding could not determine an appropriate Go type to decode into.
|
||||
// For example, unmarshaling into a nil io.Reader fails since
|
||||
// there is no concrete type to populate the interface value with.
|
||||
// Otherwise an underlying value exists and it recursively decodes
|
||||
// the JSON input into it. It does not support any custom format flags.
|
||||
//
|
||||
// - A Go [time.Time] is decoded from a JSON string containing the time
|
||||
// formatted in RFC 3339 with nanosecond precision.
|
||||
// If the format matches one of the format constants declared in
|
||||
// the time package (e.g., RFC1123), then that format is used for parsing.
|
||||
// If the format is "unix", "unixmilli", "unixmicro", or "unixnano",
|
||||
// then the timestamp is decoded from a JSON number of the number of seconds
|
||||
// (or milliseconds, microseconds, or nanoseconds) since the Unix epoch,
|
||||
// which is January 1st, 1970 at 00:00:00 UTC.
|
||||
// Otherwise, the format is used as-is with [time.Time.Parse] if non-empty.
|
||||
//
|
||||
// - A Go [time.Duration] is decoded from a JSON string by
|
||||
// passing the decoded string to [time.ParseDuration].
|
||||
// If the format is "sec", "milli", "micro", or "nano",
|
||||
// then the duration is decoded from a JSON number of the number of seconds
|
||||
// (or milliseconds, microseconds, or nanoseconds) in the duration.
|
||||
// If the format is "units", it uses [time.ParseDuration].
|
||||
//
|
||||
// - All other Go types (e.g., complex numbers, channels, and functions)
|
||||
// have no default representation and result in a [SemanticError].
|
||||
//
|
||||
// In general, unmarshaling follows merge semantics (similar to RFC 7396)
|
||||
// where the decoded Go value replaces the destination value
|
||||
// for any JSON kind other than an object.
|
||||
// For JSON objects, the input object is merged into the destination value
|
||||
// where matching object members recursively apply merge semantics.
|
||||
func Unmarshal(in []byte, out any, opts ...Options) (err error) {
|
||||
dec := export.GetBufferedDecoder(in, opts...)
|
||||
defer export.PutBufferedDecoder(dec)
|
||||
xd := export.Decoder(dec)
|
||||
err = unmarshalFull(dec, out, &xd.Struct)
|
||||
if err != nil && xd.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
|
||||
return internal.TransformUnmarshalError(out, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// UnmarshalRead deserializes a Go value from an [io.Reader] according to the
|
||||
// provided unmarshal and decode options (while ignoring marshal or encode options).
|
||||
// The input must be a single JSON value with optional whitespace interspersed.
|
||||
// It consumes the entirety of [io.Reader] until [io.EOF] is encountered,
|
||||
// without reporting an error for EOF. The output must be a non-nil pointer.
|
||||
// See [Unmarshal] for details about the conversion of JSON into a Go value.
|
||||
func UnmarshalRead(in io.Reader, out any, opts ...Options) (err error) {
|
||||
dec := export.GetStreamingDecoder(in, opts...)
|
||||
defer export.PutStreamingDecoder(dec)
|
||||
xd := export.Decoder(dec)
|
||||
err = unmarshalFull(dec, out, &xd.Struct)
|
||||
if err != nil && xd.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
|
||||
return internal.TransformUnmarshalError(out, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func unmarshalFull(in *jsontext.Decoder, out any, uo *jsonopts.Struct) error {
|
||||
switch err := unmarshalDecode(in, out, uo); err {
|
||||
case nil:
|
||||
return export.Decoder(in).CheckEOF()
|
||||
case io.EOF:
|
||||
return io.ErrUnexpectedEOF
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalDecode deserializes a Go value from a [jsontext.Decoder] according to
|
||||
// the provided unmarshal options (while ignoring marshal, encode, or decode options).
|
||||
// Any unmarshal options already specified on the [jsontext.Decoder]
|
||||
// take lower precedence than the set of options provided by the caller.
|
||||
// Unlike [Unmarshal] and [UnmarshalRead], decode options are ignored because
|
||||
// they must have already been specified on the provided [jsontext.Decoder].
|
||||
//
|
||||
// The input may be a stream of one or more JSON values,
|
||||
// where this only unmarshals the next JSON value in the stream.
|
||||
// The output must be a non-nil pointer.
|
||||
// See [Unmarshal] for details about the conversion of JSON into a Go value.
|
||||
func UnmarshalDecode(in *jsontext.Decoder, out any, opts ...Options) (err error) {
|
||||
xd := export.Decoder(in)
|
||||
if len(opts) > 0 {
|
||||
optsOriginal := xd.Struct
|
||||
defer func() { xd.Struct = optsOriginal }()
|
||||
xd.Struct.JoinWithoutCoderOptions(opts...)
|
||||
}
|
||||
err = unmarshalDecode(in, out, &xd.Struct)
|
||||
if err != nil && xd.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
|
||||
return internal.TransformUnmarshalError(out, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func unmarshalDecode(in *jsontext.Decoder, out any, uo *jsonopts.Struct) (err error) {
|
||||
v := reflect.ValueOf(out)
|
||||
if v.Kind() != reflect.Pointer || v.IsNil() {
|
||||
return &SemanticError{action: "unmarshal", GoType: reflect.TypeOf(out), Err: internal.ErrNonNilReference}
|
||||
}
|
||||
va := addressableValue{v.Elem(), false} // dereferenced pointer is always addressable
|
||||
t := va.Type()
|
||||
|
||||
// In legacy semantics, the entirety of the next JSON value
|
||||
// was validated before attempting to unmarshal it.
|
||||
if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
|
||||
if err := export.Decoder(in).CheckNextValue(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Lookup and call the unmarshal function for this type.
|
||||
unmarshal := lookupArshaler(t).unmarshal
|
||||
if uo.Unmarshalers != nil {
|
||||
unmarshal, _ = uo.Unmarshalers.(*Unmarshalers).lookup(unmarshal, t)
|
||||
}
|
||||
if err := unmarshal(in, va, uo); err != nil {
|
||||
if !uo.Flags.Get(jsonflags.AllowDuplicateNames) {
|
||||
export.Decoder(in).Tokens.InvalidateDisabledNamespaces()
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// addressableValue is a reflect.Value that is guaranteed to be addressable
|
||||
// such that calling the Addr and Set methods do not panic.
|
||||
//
|
||||
// There is no compile magic that enforces this property,
|
||||
// but rather the need to construct this type makes it easier to examine each
|
||||
// construction site to ensure that this property is upheld.
|
||||
type addressableValue struct {
|
||||
reflect.Value
|
||||
|
||||
// forcedAddr reports whether this value is addressable
|
||||
// only through the use of [newAddressableValue].
|
||||
// This is only used for [jsonflags.CallMethodsWithLegacySemantics].
|
||||
forcedAddr bool
|
||||
}
|
||||
|
||||
// newAddressableValue constructs a new addressable value of type t.
|
||||
func newAddressableValue(t reflect.Type) addressableValue {
|
||||
return addressableValue{reflect.New(t).Elem(), true}
|
||||
}
|
||||
|
||||
// TODO: Remove *jsonopts.Struct argument from [marshaler] and [unmarshaler].
|
||||
// This can be directly accessed on the encoder or decoder.
|
||||
|
||||
// All marshal and unmarshal behavior is implemented using these signatures.
|
||||
// The *jsonopts.Struct argument is guaranteed to identical to or at least
|
||||
// a strict super-set of the options in Encoder.Struct or Decoder.Struct.
|
||||
// It is identical for Marshal, Unmarshal, MarshalWrite, and UnmarshalRead.
|
||||
// It is a super-set for MarshalEncode and UnmarshalDecode.
|
||||
type (
|
||||
marshaler = func(*jsontext.Encoder, addressableValue, *jsonopts.Struct) error
|
||||
unmarshaler = func(*jsontext.Decoder, addressableValue, *jsonopts.Struct) error
|
||||
)
|
||||
|
||||
type arshaler struct {
|
||||
marshal marshaler
|
||||
unmarshal unmarshaler
|
||||
nonDefault bool
|
||||
}
|
||||
|
||||
var lookupArshalerCache sync.Map // map[reflect.Type]*arshaler
|
||||
|
||||
func lookupArshaler(t reflect.Type) *arshaler {
|
||||
if v, ok := lookupArshalerCache.Load(t); ok {
|
||||
return v.(*arshaler)
|
||||
}
|
||||
|
||||
fncs := makeDefaultArshaler(t)
|
||||
fncs = makeMethodArshaler(fncs, t)
|
||||
fncs = makeTimeArshaler(fncs, t)
|
||||
|
||||
// Use the last stored so that duplicate arshalers can be garbage collected.
|
||||
v, _ := lookupArshalerCache.LoadOrStore(t, fncs)
|
||||
return v.(*arshaler)
|
||||
}
|
||||
|
||||
var stringsPools = &sync.Pool{New: func() any { return new(stringSlice) }}
|
||||
|
||||
type stringSlice []string
|
||||
|
||||
// getStrings returns a non-nil pointer to a slice with length n.
|
||||
func getStrings(n int) *stringSlice {
|
||||
s := stringsPools.Get().(*stringSlice)
|
||||
if cap(*s) < n {
|
||||
*s = make([]string, n)
|
||||
}
|
||||
*s = (*s)[:n]
|
||||
return s
|
||||
}
|
||||
|
||||
func putStrings(s *stringSlice) {
|
||||
if cap(*s) > 1<<10 {
|
||||
*s = nil // avoid pinning arbitrarily large amounts of memory
|
||||
}
|
||||
stringsPools.Put(s)
|
||||
}
|
||||
|
||||
func (ss *stringSlice) Sort() {
|
||||
slices.SortFunc(*ss, func(x, y string) int { return strings.Compare(x, y) })
|
||||
}
|
281
vendor/github.com/go-json-experiment/json/arshal_any.go
generated
vendored
Normal file
281
vendor/github.com/go-json-experiment/json/arshal_any.go
generated
vendored
Normal file
@ -0,0 +1,281 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-json-experiment/json/internal"
|
||||
"github.com/go-json-experiment/json/internal/jsonflags"
|
||||
"github.com/go-json-experiment/json/internal/jsonopts"
|
||||
"github.com/go-json-experiment/json/internal/jsonwire"
|
||||
"github.com/go-json-experiment/json/jsontext"
|
||||
)
|
||||
|
||||
// This file contains an optimized marshal and unmarshal implementation
|
||||
// for the any type. This type is often used when the Go program has
|
||||
// no knowledge of the JSON schema. This is a common enough occurrence
|
||||
// to justify the complexity of adding logic for this.
|
||||
|
||||
// marshalValueAny marshals a Go any as a JSON value.
|
||||
// This assumes that there are no special formatting directives
|
||||
// for any possible nested value.
|
||||
func marshalValueAny(enc *jsontext.Encoder, val any, mo *jsonopts.Struct) error {
|
||||
switch val := val.(type) {
|
||||
case nil:
|
||||
return enc.WriteToken(jsontext.Null)
|
||||
case bool:
|
||||
return enc.WriteToken(jsontext.Bool(val))
|
||||
case string:
|
||||
return enc.WriteToken(jsontext.String(val))
|
||||
case float64:
|
||||
return enc.WriteToken(jsontext.Float(val))
|
||||
case map[string]any:
|
||||
return marshalObjectAny(enc, val, mo)
|
||||
case []any:
|
||||
return marshalArrayAny(enc, val, mo)
|
||||
default:
|
||||
v := newAddressableValue(reflect.TypeOf(val))
|
||||
v.Set(reflect.ValueOf(val))
|
||||
marshal := lookupArshaler(v.Type()).marshal
|
||||
if mo.Marshalers != nil {
|
||||
marshal, _ = mo.Marshalers.(*Marshalers).lookup(marshal, v.Type())
|
||||
}
|
||||
return marshal(enc, v, mo)
|
||||
}
|
||||
}
|
||||
|
||||
// unmarshalValueAny unmarshals a JSON value as a Go any.
|
||||
// This assumes that there are no special formatting directives
|
||||
// for any possible nested value.
|
||||
// Duplicate names must be rejected since this does not implement merging.
|
||||
func unmarshalValueAny(dec *jsontext.Decoder, uo *jsonopts.Struct) (any, error) {
|
||||
switch k := dec.PeekKind(); k {
|
||||
case '{':
|
||||
return unmarshalObjectAny(dec, uo)
|
||||
case '[':
|
||||
return unmarshalArrayAny(dec, uo)
|
||||
default:
|
||||
xd := export.Decoder(dec)
|
||||
var flags jsonwire.ValueFlags
|
||||
val, err := xd.ReadValue(&flags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch val.Kind() {
|
||||
case 'n':
|
||||
return nil, nil
|
||||
case 'f':
|
||||
return false, nil
|
||||
case 't':
|
||||
return true, nil
|
||||
case '"':
|
||||
val = jsonwire.UnquoteMayCopy(val, flags.IsVerbatim())
|
||||
if xd.StringCache == nil {
|
||||
xd.StringCache = new(stringCache)
|
||||
}
|
||||
return makeString(xd.StringCache, val), nil
|
||||
case '0':
|
||||
if uo.Flags.Get(jsonflags.UnmarshalAnyWithRawNumber) {
|
||||
return internal.RawNumberOf(val), nil
|
||||
}
|
||||
fv, ok := jsonwire.ParseFloat(val, 64)
|
||||
if !ok {
|
||||
return fv, newUnmarshalErrorAfterWithValue(dec, float64Type, strconv.ErrRange)
|
||||
}
|
||||
return fv, nil
|
||||
default:
|
||||
panic("BUG: invalid kind: " + k.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// marshalObjectAny marshals a Go map[string]any as a JSON object
|
||||
// (or as a JSON null if nil and [jsonflags.FormatNilMapAsNull]).
|
||||
func marshalObjectAny(enc *jsontext.Encoder, obj map[string]any, mo *jsonopts.Struct) error {
|
||||
// Check for cycles.
|
||||
xe := export.Encoder(enc)
|
||||
if xe.Tokens.Depth() > startDetectingCyclesAfter {
|
||||
v := reflect.ValueOf(obj)
|
||||
if err := visitPointer(&xe.SeenPointers, v); err != nil {
|
||||
return newMarshalErrorBefore(enc, anyType, err)
|
||||
}
|
||||
defer leavePointer(&xe.SeenPointers, v)
|
||||
}
|
||||
|
||||
// Handle empty maps.
|
||||
if len(obj) == 0 {
|
||||
if mo.Flags.Get(jsonflags.FormatNilMapAsNull) && obj == nil {
|
||||
return enc.WriteToken(jsontext.Null)
|
||||
}
|
||||
// Optimize for marshaling an empty map without any preceding whitespace.
|
||||
if !mo.Flags.Get(jsonflags.AnyWhitespace) && !xe.Tokens.Last.NeedObjectName() {
|
||||
xe.Buf = append(xe.Tokens.MayAppendDelim(xe.Buf, '{'), "{}"...)
|
||||
xe.Tokens.Last.Increment()
|
||||
if xe.NeedFlush() {
|
||||
return xe.Flush()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if err := enc.WriteToken(jsontext.BeginObject); err != nil {
|
||||
return err
|
||||
}
|
||||
// A Go map guarantees that each entry has a unique key
|
||||
// The only possibility of duplicates is due to invalid UTF-8.
|
||||
if !mo.Flags.Get(jsonflags.AllowInvalidUTF8) {
|
||||
xe.Tokens.Last.DisableNamespace()
|
||||
}
|
||||
if !mo.Flags.Get(jsonflags.Deterministic) || len(obj) <= 1 {
|
||||
for name, val := range obj {
|
||||
if err := enc.WriteToken(jsontext.String(name)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := marshalValueAny(enc, val, mo); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
names := getStrings(len(obj))
|
||||
var i int
|
||||
for name := range obj {
|
||||
(*names)[i] = name
|
||||
i++
|
||||
}
|
||||
names.Sort()
|
||||
for _, name := range *names {
|
||||
if err := enc.WriteToken(jsontext.String(name)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := marshalValueAny(enc, obj[name], mo); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
putStrings(names)
|
||||
}
|
||||
if err := enc.WriteToken(jsontext.EndObject); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// unmarshalObjectAny unmarshals a JSON object as a Go map[string]any.
|
||||
// It panics if not decoding a JSON object.
|
||||
func unmarshalObjectAny(dec *jsontext.Decoder, uo *jsonopts.Struct) (map[string]any, error) {
|
||||
switch tok, err := dec.ReadToken(); {
|
||||
case err != nil:
|
||||
return nil, err
|
||||
case tok.Kind() != '{':
|
||||
panic("BUG: invalid kind: " + tok.Kind().String())
|
||||
}
|
||||
obj := make(map[string]any)
|
||||
// A Go map guarantees that each entry has a unique key
|
||||
// The only possibility of duplicates is due to invalid UTF-8.
|
||||
if !uo.Flags.Get(jsonflags.AllowInvalidUTF8) {
|
||||
export.Decoder(dec).Tokens.Last.DisableNamespace()
|
||||
}
|
||||
var errUnmarshal error
|
||||
for dec.PeekKind() != '}' {
|
||||
tok, err := dec.ReadToken()
|
||||
if err != nil {
|
||||
return obj, err
|
||||
}
|
||||
name := tok.String()
|
||||
|
||||
// Manually check for duplicate names.
|
||||
if _, ok := obj[name]; ok {
|
||||
// TODO: Unread the object name.
|
||||
name := export.Decoder(dec).PreviousTokenOrValue()
|
||||
err := newDuplicateNameError(dec.StackPointer(), nil, dec.InputOffset()-len64(name))
|
||||
return obj, err
|
||||
}
|
||||
|
||||
val, err := unmarshalValueAny(dec, uo)
|
||||
obj[name] = val
|
||||
if err != nil {
|
||||
if isFatalError(err, uo.Flags) {
|
||||
return obj, err
|
||||
}
|
||||
errUnmarshal = cmp.Or(err, errUnmarshal)
|
||||
}
|
||||
}
|
||||
if _, err := dec.ReadToken(); err != nil {
|
||||
return obj, err
|
||||
}
|
||||
return obj, errUnmarshal
|
||||
}
|
||||
|
||||
// marshalArrayAny marshals a Go []any as a JSON array
|
||||
// (or as a JSON null if nil and [jsonflags.FormatNilSliceAsNull]).
|
||||
func marshalArrayAny(enc *jsontext.Encoder, arr []any, mo *jsonopts.Struct) error {
|
||||
// Check for cycles.
|
||||
xe := export.Encoder(enc)
|
||||
if xe.Tokens.Depth() > startDetectingCyclesAfter {
|
||||
v := reflect.ValueOf(arr)
|
||||
if err := visitPointer(&xe.SeenPointers, v); err != nil {
|
||||
return newMarshalErrorBefore(enc, sliceAnyType, err)
|
||||
}
|
||||
defer leavePointer(&xe.SeenPointers, v)
|
||||
}
|
||||
|
||||
// Handle empty slices.
|
||||
if len(arr) == 0 {
|
||||
if mo.Flags.Get(jsonflags.FormatNilSliceAsNull) && arr == nil {
|
||||
return enc.WriteToken(jsontext.Null)
|
||||
}
|
||||
// Optimize for marshaling an empty slice without any preceding whitespace.
|
||||
if !mo.Flags.Get(jsonflags.AnyWhitespace) && !xe.Tokens.Last.NeedObjectName() {
|
||||
xe.Buf = append(xe.Tokens.MayAppendDelim(xe.Buf, '['), "[]"...)
|
||||
xe.Tokens.Last.Increment()
|
||||
if xe.NeedFlush() {
|
||||
return xe.Flush()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if err := enc.WriteToken(jsontext.BeginArray); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, val := range arr {
|
||||
if err := marshalValueAny(enc, val, mo); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := enc.WriteToken(jsontext.EndArray); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// unmarshalArrayAny unmarshals a JSON array as a Go []any.
|
||||
// It panics if not decoding a JSON array.
|
||||
func unmarshalArrayAny(dec *jsontext.Decoder, uo *jsonopts.Struct) ([]any, error) {
|
||||
switch tok, err := dec.ReadToken(); {
|
||||
case err != nil:
|
||||
return nil, err
|
||||
case tok.Kind() != '[':
|
||||
panic("BUG: invalid kind: " + tok.Kind().String())
|
||||
}
|
||||
arr := []any{}
|
||||
var errUnmarshal error
|
||||
for dec.PeekKind() != ']' {
|
||||
val, err := unmarshalValueAny(dec, uo)
|
||||
arr = append(arr, val)
|
||||
if err != nil {
|
||||
if isFatalError(err, uo.Flags) {
|
||||
return arr, err
|
||||
}
|
||||
errUnmarshal = cmp.Or(errUnmarshal, err)
|
||||
}
|
||||
}
|
||||
if _, err := dec.ReadToken(); err != nil {
|
||||
return arr, err
|
||||
}
|
||||
return arr, errUnmarshal
|
||||
}
|
1908
vendor/github.com/go-json-experiment/json/arshal_default.go
generated
vendored
Normal file
1908
vendor/github.com/go-json-experiment/json/arshal_default.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
430
vendor/github.com/go-json-experiment/json/arshal_funcs.go
generated
vendored
Normal file
430
vendor/github.com/go-json-experiment/json/arshal_funcs.go
generated
vendored
Normal file
@ -0,0 +1,430 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/go-json-experiment/json/internal"
|
||||
"github.com/go-json-experiment/json/internal/jsonflags"
|
||||
"github.com/go-json-experiment/json/internal/jsonopts"
|
||||
"github.com/go-json-experiment/json/jsontext"
|
||||
)
|
||||
|
||||
// SkipFunc may be returned by [MarshalToFunc] and [UnmarshalFromFunc] functions.
|
||||
//
|
||||
// Any function that returns SkipFunc must not cause observable side effects
|
||||
// on the provided [jsontext.Encoder] or [jsontext.Decoder].
|
||||
// For example, it is permissible to call [jsontext.Decoder.PeekKind],
|
||||
// but not permissible to call [jsontext.Decoder.ReadToken] or
|
||||
// [jsontext.Encoder.WriteToken] since such methods mutate the state.
|
||||
var SkipFunc = errors.New("json: skip function")
|
||||
|
||||
var errSkipMutation = errors.New("must not read or write any tokens when skipping")
|
||||
var errNonSingularValue = errors.New("must read or write exactly one value")
|
||||
|
||||
// Marshalers is a list of functions that may override the marshal behavior
|
||||
// of specific types. Populate [WithMarshalers] to use it with
|
||||
// [Marshal], [MarshalWrite], or [MarshalEncode].
|
||||
// A nil *Marshalers is equivalent to an empty list.
|
||||
// There are no exported fields or methods on Marshalers.
|
||||
type Marshalers = typedMarshalers
|
||||
|
||||
// JoinMarshalers constructs a flattened list of marshal functions.
|
||||
// If multiple functions in the list are applicable for a value of a given type,
|
||||
// then those earlier in the list take precedence over those that come later.
|
||||
// If a function returns [SkipFunc], then the next applicable function is called,
|
||||
// otherwise the default marshaling behavior is used.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// m1 := JoinMarshalers(f1, f2)
|
||||
// m2 := JoinMarshalers(f0, m1, f3) // equivalent to m3
|
||||
// m3 := JoinMarshalers(f0, f1, f2, f3) // equivalent to m2
|
||||
func JoinMarshalers(ms ...*Marshalers) *Marshalers {
|
||||
return newMarshalers(ms...)
|
||||
}
|
||||
|
||||
// Unmarshalers is a list of functions that may override the unmarshal behavior
|
||||
// of specific types. Populate [WithUnmarshalers] to use it with
|
||||
// [Unmarshal], [UnmarshalRead], or [UnmarshalDecode].
|
||||
// A nil *Unmarshalers is equivalent to an empty list.
|
||||
// There are no exported fields or methods on Unmarshalers.
|
||||
type Unmarshalers = typedUnmarshalers
|
||||
|
||||
// JoinUnmarshalers constructs a flattened list of unmarshal functions.
|
||||
// If multiple functions in the list are applicable for a value of a given type,
|
||||
// then those earlier in the list take precedence over those that come later.
|
||||
// If a function returns [SkipFunc], then the next applicable function is called,
|
||||
// otherwise the default unmarshaling behavior is used.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// u1 := JoinUnmarshalers(f1, f2)
|
||||
// u2 := JoinUnmarshalers(f0, u1, f3) // equivalent to u3
|
||||
// u3 := JoinUnmarshalers(f0, f1, f2, f3) // equivalent to u2
|
||||
func JoinUnmarshalers(us ...*Unmarshalers) *Unmarshalers {
|
||||
return newUnmarshalers(us...)
|
||||
}
|
||||
|
||||
type typedMarshalers = typedArshalers[jsontext.Encoder]
|
||||
type typedUnmarshalers = typedArshalers[jsontext.Decoder]
|
||||
type typedArshalers[Coder any] struct {
|
||||
nonComparable
|
||||
|
||||
fncVals []typedArshaler[Coder]
|
||||
fncCache sync.Map // map[reflect.Type]arshaler
|
||||
|
||||
// fromAny reports whether any of Go types used to represent arbitrary JSON
|
||||
// (i.e., any, bool, string, float64, map[string]any, or []any) matches
|
||||
// any of the provided type-specific arshalers.
|
||||
//
|
||||
// This bit of information is needed in arshal_default.go to determine
|
||||
// whether to use the specialized logic in arshal_any.go to handle
|
||||
// the any interface type. The logic in arshal_any.go does not support
|
||||
// type-specific arshal functions, so we must avoid using that logic
|
||||
// if this is true.
|
||||
fromAny bool
|
||||
}
|
||||
type typedMarshaler = typedArshaler[jsontext.Encoder]
|
||||
type typedUnmarshaler = typedArshaler[jsontext.Decoder]
|
||||
type typedArshaler[Coder any] struct {
|
||||
typ reflect.Type
|
||||
fnc func(*Coder, addressableValue, *jsonopts.Struct) error
|
||||
maySkip bool
|
||||
}
|
||||
|
||||
func newMarshalers(ms ...*Marshalers) *Marshalers { return newTypedArshalers(ms...) }
|
||||
func newUnmarshalers(us ...*Unmarshalers) *Unmarshalers { return newTypedArshalers(us...) }
|
||||
func newTypedArshalers[Coder any](as ...*typedArshalers[Coder]) *typedArshalers[Coder] {
|
||||
var a typedArshalers[Coder]
|
||||
for _, a2 := range as {
|
||||
if a2 != nil {
|
||||
a.fncVals = append(a.fncVals, a2.fncVals...)
|
||||
a.fromAny = a.fromAny || a2.fromAny
|
||||
}
|
||||
}
|
||||
if len(a.fncVals) == 0 {
|
||||
return nil
|
||||
}
|
||||
return &a
|
||||
}
|
||||
|
||||
func (a *typedArshalers[Coder]) lookup(fnc func(*Coder, addressableValue, *jsonopts.Struct) error, t reflect.Type) (func(*Coder, addressableValue, *jsonopts.Struct) error, bool) {
|
||||
if a == nil {
|
||||
return fnc, false
|
||||
}
|
||||
if v, ok := a.fncCache.Load(t); ok {
|
||||
if v == nil {
|
||||
return fnc, false
|
||||
}
|
||||
return v.(func(*Coder, addressableValue, *jsonopts.Struct) error), true
|
||||
}
|
||||
|
||||
// Collect a list of arshalers that can be called for this type.
|
||||
// This list may be longer than 1 since some arshalers can be skipped.
|
||||
var fncs []func(*Coder, addressableValue, *jsonopts.Struct) error
|
||||
for _, fncVal := range a.fncVals {
|
||||
if !castableTo(t, fncVal.typ) {
|
||||
continue
|
||||
}
|
||||
fncs = append(fncs, fncVal.fnc)
|
||||
if !fncVal.maySkip {
|
||||
break // subsequent arshalers will never be called
|
||||
}
|
||||
}
|
||||
|
||||
if len(fncs) == 0 {
|
||||
a.fncCache.Store(t, nil) // nil to indicate that no funcs found
|
||||
return fnc, false
|
||||
}
|
||||
|
||||
// Construct an arshaler that may call every applicable arshaler.
|
||||
fncDefault := fnc
|
||||
fnc = func(c *Coder, v addressableValue, o *jsonopts.Struct) error {
|
||||
for _, fnc := range fncs {
|
||||
if err := fnc(c, v, o); err != SkipFunc {
|
||||
return err // may be nil or non-nil
|
||||
}
|
||||
}
|
||||
return fncDefault(c, v, o)
|
||||
}
|
||||
|
||||
// Use the first stored so duplicate work can be garbage collected.
|
||||
v, _ := a.fncCache.LoadOrStore(t, fnc)
|
||||
return v.(func(*Coder, addressableValue, *jsonopts.Struct) error), true
|
||||
}
|
||||
|
||||
// MarshalFunc constructs a type-specific marshaler that
|
||||
// specifies how to marshal values of type T.
|
||||
// T can be any type except a named pointer.
|
||||
// The function is always provided with a non-nil pointer value
|
||||
// if T is an interface or pointer type.
|
||||
//
|
||||
// The function must marshal exactly one JSON value.
|
||||
// The value of T must not be retained outside the function call.
|
||||
// It may not return [SkipFunc].
|
||||
func MarshalFunc[T any](fn func(T) ([]byte, error)) *Marshalers {
|
||||
t := reflect.TypeFor[T]()
|
||||
assertCastableTo(t, true)
|
||||
typFnc := typedMarshaler{
|
||||
typ: t,
|
||||
fnc: func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error {
|
||||
val, err := fn(va.castTo(t).Interface().(T))
|
||||
if err != nil {
|
||||
err = wrapSkipFunc(err, "marshal function of type func(T) ([]byte, error)")
|
||||
if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
|
||||
return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalFunc") // unlike unmarshal, always wrapped
|
||||
}
|
||||
err = newMarshalErrorBefore(enc, t, err)
|
||||
return collapseSemanticErrors(err)
|
||||
}
|
||||
if err := enc.WriteValue(val); err != nil {
|
||||
if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
|
||||
return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalFunc") // unlike unmarshal, always wrapped
|
||||
}
|
||||
if isSyntacticError(err) {
|
||||
err = newMarshalErrorBefore(enc, t, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
return &Marshalers{fncVals: []typedMarshaler{typFnc}, fromAny: castableToFromAny(t)}
|
||||
}
|
||||
|
||||
// MarshalToFunc constructs a type-specific marshaler that
|
||||
// specifies how to marshal values of type T.
|
||||
// T can be any type except a named pointer.
|
||||
// The function is always provided with a non-nil pointer value
|
||||
// if T is an interface or pointer type.
|
||||
//
|
||||
// The function must marshal exactly one JSON value by calling write methods
|
||||
// on the provided encoder. It may return [SkipFunc] such that marshaling can
|
||||
// move on to the next marshal function. However, no mutable method calls may
|
||||
// be called on the encoder if [SkipFunc] is returned.
|
||||
// The pointer to [jsontext.Encoder] and the value of T
|
||||
// must not be retained outside the function call.
|
||||
func MarshalToFunc[T any](fn func(*jsontext.Encoder, T) error) *Marshalers {
|
||||
t := reflect.TypeFor[T]()
|
||||
assertCastableTo(t, true)
|
||||
typFnc := typedMarshaler{
|
||||
typ: t,
|
||||
fnc: func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error {
|
||||
xe := export.Encoder(enc)
|
||||
prevDepth, prevLength := xe.Tokens.DepthLength()
|
||||
xe.Flags.Set(jsonflags.WithinArshalCall | 1)
|
||||
err := fn(enc, va.castTo(t).Interface().(T))
|
||||
xe.Flags.Set(jsonflags.WithinArshalCall | 0)
|
||||
currDepth, currLength := xe.Tokens.DepthLength()
|
||||
if err == nil && (prevDepth != currDepth || prevLength+1 != currLength) {
|
||||
err = errNonSingularValue
|
||||
}
|
||||
if err != nil {
|
||||
if err == SkipFunc {
|
||||
if prevDepth == currDepth && prevLength == currLength {
|
||||
return SkipFunc
|
||||
}
|
||||
err = errSkipMutation
|
||||
}
|
||||
if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
|
||||
return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalToFunc") // unlike unmarshal, always wrapped
|
||||
}
|
||||
if !export.IsIOError(err) {
|
||||
err = newSemanticErrorWithPosition(enc, t, prevDepth, prevLength, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
maySkip: true,
|
||||
}
|
||||
return &Marshalers{fncVals: []typedMarshaler{typFnc}, fromAny: castableToFromAny(t)}
|
||||
}
|
||||
|
||||
// UnmarshalFunc constructs a type-specific unmarshaler that
|
||||
// specifies how to unmarshal values of type T.
|
||||
// T must be an unnamed pointer or an interface type.
|
||||
// The function is always provided with a non-nil pointer value.
|
||||
//
|
||||
// The function must unmarshal exactly one JSON value.
|
||||
// The input []byte must not be mutated.
|
||||
// The input []byte and value T must not be retained outside the function call.
|
||||
// It may not return [SkipFunc].
|
||||
func UnmarshalFunc[T any](fn func([]byte, T) error) *Unmarshalers {
|
||||
t := reflect.TypeFor[T]()
|
||||
assertCastableTo(t, false)
|
||||
typFnc := typedUnmarshaler{
|
||||
typ: t,
|
||||
fnc: func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error {
|
||||
val, err := dec.ReadValue()
|
||||
if err != nil {
|
||||
return err // must be a syntactic or I/O error
|
||||
}
|
||||
err = fn(val, va.castTo(t).Interface().(T))
|
||||
if err != nil {
|
||||
err = wrapSkipFunc(err, "unmarshal function of type func([]byte, T) error")
|
||||
if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
|
||||
return err // unlike marshal, never wrapped
|
||||
}
|
||||
err = newUnmarshalErrorAfter(dec, t, err)
|
||||
return collapseSemanticErrors(err)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
return &Unmarshalers{fncVals: []typedUnmarshaler{typFnc}, fromAny: castableToFromAny(t)}
|
||||
}
|
||||
|
||||
// UnmarshalFromFunc constructs a type-specific unmarshaler that
|
||||
// specifies how to unmarshal values of type T.
|
||||
// T must be an unnamed pointer or an interface type.
|
||||
// The function is always provided with a non-nil pointer value.
|
||||
//
|
||||
// The function must unmarshal exactly one JSON value by calling read methods
|
||||
// on the provided decoder. It may return [SkipFunc] such that unmarshaling can
|
||||
// move on to the next unmarshal function. However, no mutable method calls may
|
||||
// be called on the decoder if [SkipFunc] is returned.
|
||||
// The pointer to [jsontext.Decoder] and the value of T
|
||||
// must not be retained outside the function call.
|
||||
func UnmarshalFromFunc[T any](fn func(*jsontext.Decoder, T) error) *Unmarshalers {
|
||||
t := reflect.TypeFor[T]()
|
||||
assertCastableTo(t, false)
|
||||
typFnc := typedUnmarshaler{
|
||||
typ: t,
|
||||
fnc: func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error {
|
||||
xd := export.Decoder(dec)
|
||||
prevDepth, prevLength := xd.Tokens.DepthLength()
|
||||
xd.Flags.Set(jsonflags.WithinArshalCall | 1)
|
||||
err := fn(dec, va.castTo(t).Interface().(T))
|
||||
xd.Flags.Set(jsonflags.WithinArshalCall | 0)
|
||||
currDepth, currLength := xd.Tokens.DepthLength()
|
||||
if err == nil && (prevDepth != currDepth || prevLength+1 != currLength) {
|
||||
err = errNonSingularValue
|
||||
}
|
||||
if err != nil {
|
||||
if err == SkipFunc {
|
||||
if prevDepth == currDepth && prevLength == currLength {
|
||||
return SkipFunc
|
||||
}
|
||||
err = errSkipMutation
|
||||
}
|
||||
if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
|
||||
if err2 := xd.SkipUntil(prevDepth, prevLength+1); err2 != nil {
|
||||
return err2
|
||||
}
|
||||
return err // unlike marshal, never wrapped
|
||||
}
|
||||
if !isSyntacticError(err) && !export.IsIOError(err) {
|
||||
err = newSemanticErrorWithPosition(dec, t, prevDepth, prevLength, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
maySkip: true,
|
||||
}
|
||||
return &Unmarshalers{fncVals: []typedUnmarshaler{typFnc}, fromAny: castableToFromAny(t)}
|
||||
}
|
||||
|
||||
// assertCastableTo asserts that "to" is a valid type to be casted to.
|
||||
// These are the Go types that type-specific arshalers may operate upon.
|
||||
//
|
||||
// Let AllTypes be the universal set of all possible Go types.
|
||||
// This function generally asserts that:
|
||||
//
|
||||
// len([from for from in AllTypes if castableTo(from, to)]) > 0
|
||||
//
|
||||
// otherwise it panics.
|
||||
//
|
||||
// As a special-case if marshal is false, then we forbid any non-pointer or
|
||||
// non-interface type since it is almost always a bug trying to unmarshal
|
||||
// into something where the end-user caller did not pass in an addressable value
|
||||
// since they will not observe the mutations.
|
||||
func assertCastableTo(to reflect.Type, marshal bool) {
|
||||
switch to.Kind() {
|
||||
case reflect.Interface:
|
||||
return
|
||||
case reflect.Pointer:
|
||||
// Only allow unnamed pointers to be consistent with the fact that
|
||||
// taking the address of a value produces an unnamed pointer type.
|
||||
if to.Name() == "" {
|
||||
return
|
||||
}
|
||||
default:
|
||||
// Technically, non-pointer types are permissible for unmarshal.
|
||||
// However, they are often a bug since the receiver would be immutable.
|
||||
// Thus, only allow them for marshaling.
|
||||
if marshal {
|
||||
return
|
||||
}
|
||||
}
|
||||
if marshal {
|
||||
panic(fmt.Sprintf("input type %v must be an interface type, an unnamed pointer type, or a non-pointer type", to))
|
||||
} else {
|
||||
panic(fmt.Sprintf("input type %v must be an interface type or an unnamed pointer type", to))
|
||||
}
|
||||
}
|
||||
|
||||
// castableTo checks whether values of type "from" can be casted to type "to".
|
||||
// Nil pointer or interface "from" values are never considered castable.
|
||||
//
|
||||
// This function must be kept in sync with addressableValue.castTo.
|
||||
func castableTo(from, to reflect.Type) bool {
|
||||
switch to.Kind() {
|
||||
case reflect.Interface:
|
||||
// TODO: This breaks when ordinary interfaces can have type sets
|
||||
// since interfaces now exist where only the value form of a type (T)
|
||||
// implements the interface, but not the pointer variant (*T).
|
||||
// See https://go.dev/issue/45346.
|
||||
return reflect.PointerTo(from).Implements(to)
|
||||
case reflect.Pointer:
|
||||
// Common case for unmarshaling.
|
||||
// From must be a concrete or interface type.
|
||||
return reflect.PointerTo(from) == to
|
||||
default:
|
||||
// Common case for marshaling.
|
||||
// From must be a concrete type.
|
||||
return from == to
|
||||
}
|
||||
}
|
||||
|
||||
// castTo casts va to the specified type.
|
||||
// If the type is an interface, then the underlying type will always
|
||||
// be a non-nil pointer to a concrete type.
|
||||
//
|
||||
// Requirement: castableTo(va.Type(), to) must hold.
|
||||
func (va addressableValue) castTo(to reflect.Type) reflect.Value {
|
||||
switch to.Kind() {
|
||||
case reflect.Interface:
|
||||
return va.Addr().Convert(to)
|
||||
case reflect.Pointer:
|
||||
return va.Addr()
|
||||
default:
|
||||
return va.Value
|
||||
}
|
||||
}
|
||||
|
||||
// castableToFromAny reports whether "to" can be casted to from any
|
||||
// of the dynamic types used to represent arbitrary JSON.
|
||||
func castableToFromAny(to reflect.Type) bool {
|
||||
for _, from := range []reflect.Type{anyType, boolType, stringType, float64Type, mapStringAnyType, sliceAnyType} {
|
||||
if castableTo(from, to) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func wrapSkipFunc(err error, what string) error {
|
||||
if err == SkipFunc {
|
||||
return errors.New(what + " cannot be skipped")
|
||||
}
|
||||
return err
|
||||
}
|
228
vendor/github.com/go-json-experiment/json/arshal_inlined.go
generated
vendored
Normal file
228
vendor/github.com/go-json-experiment/json/arshal_inlined.go
generated
vendored
Normal file
@ -0,0 +1,228 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"reflect"
|
||||
|
||||
"github.com/go-json-experiment/json/internal/jsonflags"
|
||||
"github.com/go-json-experiment/json/internal/jsonopts"
|
||||
"github.com/go-json-experiment/json/internal/jsonwire"
|
||||
"github.com/go-json-experiment/json/jsontext"
|
||||
)
|
||||
|
||||
// This package supports "inlining" a Go struct field, where the contents
|
||||
// of the serialized field (which must be a JSON object) are treated as if
|
||||
// they are part of the parent Go struct (which represents a JSON object).
|
||||
//
|
||||
// Generally, inlined fields are of a Go struct type, where the fields of the
|
||||
// nested struct are virtually hoisted up to the parent struct using rules
|
||||
// similar to how Go embedding works (but operating within the JSON namespace).
|
||||
//
|
||||
// However, inlined fields may also be of a Go map type with a string key or
|
||||
// a jsontext.Value. Such inlined fields are called "fallback" fields since they
|
||||
// represent any arbitrary JSON object member. Explicitly named fields take
|
||||
// precedence over the inlined fallback. Only one inlined fallback is allowed.
|
||||
|
||||
var errRawInlinedNotObject = errors.New("inlined raw value must be a JSON object")
|
||||
|
||||
var jsontextValueType = reflect.TypeFor[jsontext.Value]()
|
||||
|
||||
// marshalInlinedFallbackAll marshals all the members in an inlined fallback.
|
||||
func marshalInlinedFallbackAll(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct, f *structField, insertUnquotedName func([]byte) bool) error {
|
||||
v := addressableValue{va.Field(f.index0), va.forcedAddr} // addressable if struct value is addressable
|
||||
if len(f.index) > 0 {
|
||||
v = v.fieldByIndex(f.index, false)
|
||||
if !v.IsValid() {
|
||||
return nil // implies a nil inlined field
|
||||
}
|
||||
}
|
||||
v = v.indirect(false)
|
||||
if !v.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if v.Type() == jsontextValueType {
|
||||
// TODO(https://go.dev/issue/62121): Use reflect.Value.AssertTo.
|
||||
b := *v.Addr().Interface().(*jsontext.Value)
|
||||
if len(b) == 0 { // TODO: Should this be nil? What if it were all whitespace?
|
||||
return nil
|
||||
}
|
||||
|
||||
dec := export.GetBufferedDecoder(b)
|
||||
defer export.PutBufferedDecoder(dec)
|
||||
xd := export.Decoder(dec)
|
||||
xd.Flags.Set(jsonflags.AllowDuplicateNames | jsonflags.AllowInvalidUTF8 | 1)
|
||||
|
||||
tok, err := dec.ReadToken()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
return newMarshalErrorBefore(enc, v.Type(), err)
|
||||
}
|
||||
if tok.Kind() != '{' {
|
||||
return newMarshalErrorBefore(enc, v.Type(), errRawInlinedNotObject)
|
||||
}
|
||||
for dec.PeekKind() != '}' {
|
||||
// Parse the JSON object name.
|
||||
var flags jsonwire.ValueFlags
|
||||
val, err := xd.ReadValue(&flags)
|
||||
if err != nil {
|
||||
return newMarshalErrorBefore(enc, v.Type(), err)
|
||||
}
|
||||
if insertUnquotedName != nil {
|
||||
name := jsonwire.UnquoteMayCopy(val, flags.IsVerbatim())
|
||||
if !insertUnquotedName(name) {
|
||||
return newDuplicateNameError(enc.StackPointer().Parent(), val, enc.OutputOffset())
|
||||
}
|
||||
}
|
||||
if err := enc.WriteValue(val); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Parse the JSON object value.
|
||||
val, err = xd.ReadValue(&flags)
|
||||
if err != nil {
|
||||
return newMarshalErrorBefore(enc, v.Type(), err)
|
||||
}
|
||||
if err := enc.WriteValue(val); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if _, err := dec.ReadToken(); err != nil {
|
||||
return newMarshalErrorBefore(enc, v.Type(), err)
|
||||
}
|
||||
if err := xd.CheckEOF(); err != nil {
|
||||
return newMarshalErrorBefore(enc, v.Type(), err)
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
m := v // must be a map[~string]V
|
||||
n := m.Len()
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
mk := newAddressableValue(m.Type().Key())
|
||||
mv := newAddressableValue(m.Type().Elem())
|
||||
marshalKey := func(mk addressableValue) error {
|
||||
b, err := jsonwire.AppendQuote(enc.UnusedBuffer(), mk.String(), &mo.Flags)
|
||||
if err != nil {
|
||||
return newMarshalErrorBefore(enc, m.Type().Key(), err)
|
||||
}
|
||||
if insertUnquotedName != nil {
|
||||
isVerbatim := bytes.IndexByte(b, '\\') < 0
|
||||
name := jsonwire.UnquoteMayCopy(b, isVerbatim)
|
||||
if !insertUnquotedName(name) {
|
||||
return newDuplicateNameError(enc.StackPointer().Parent(), b, enc.OutputOffset())
|
||||
}
|
||||
}
|
||||
return enc.WriteValue(b)
|
||||
}
|
||||
marshalVal := f.fncs.marshal
|
||||
if mo.Marshalers != nil {
|
||||
marshalVal, _ = mo.Marshalers.(*Marshalers).lookup(marshalVal, mv.Type())
|
||||
}
|
||||
if !mo.Flags.Get(jsonflags.Deterministic) || n <= 1 {
|
||||
for iter := m.MapRange(); iter.Next(); {
|
||||
mk.SetIterKey(iter)
|
||||
if err := marshalKey(mk); err != nil {
|
||||
return err
|
||||
}
|
||||
mv.Set(iter.Value())
|
||||
if err := marshalVal(enc, mv, mo); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
names := getStrings(n)
|
||||
for i, iter := 0, m.Value.MapRange(); i < n && iter.Next(); i++ {
|
||||
mk.SetIterKey(iter)
|
||||
(*names)[i] = mk.String()
|
||||
}
|
||||
names.Sort()
|
||||
for _, name := range *names {
|
||||
mk.SetString(name)
|
||||
if err := marshalKey(mk); err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO(https://go.dev/issue/57061): Use mv.SetMapIndexOf.
|
||||
mv.Set(m.MapIndex(mk.Value))
|
||||
if err := marshalVal(enc, mv, mo); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
putStrings(names)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// unmarshalInlinedFallbackNext unmarshals only the next member in an inlined fallback.
|
||||
func unmarshalInlinedFallbackNext(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct, f *structField, quotedName, unquotedName []byte) error {
|
||||
v := addressableValue{va.Field(f.index0), va.forcedAddr} // addressable if struct value is addressable
|
||||
if len(f.index) > 0 {
|
||||
v = v.fieldByIndex(f.index, true)
|
||||
}
|
||||
v = v.indirect(true)
|
||||
|
||||
if v.Type() == jsontextValueType {
|
||||
b := v.Addr().Interface().(*jsontext.Value)
|
||||
if len(*b) == 0 { // TODO: Should this be nil? What if it were all whitespace?
|
||||
*b = append(*b, '{')
|
||||
} else {
|
||||
*b = jsonwire.TrimSuffixWhitespace(*b)
|
||||
if jsonwire.HasSuffixByte(*b, '}') {
|
||||
// TODO: When merging into an object for the first time,
|
||||
// should we verify that it is valid?
|
||||
*b = jsonwire.TrimSuffixByte(*b, '}')
|
||||
*b = jsonwire.TrimSuffixWhitespace(*b)
|
||||
if !jsonwire.HasSuffixByte(*b, ',') && !jsonwire.HasSuffixByte(*b, '{') {
|
||||
*b = append(*b, ',')
|
||||
}
|
||||
} else {
|
||||
return newUnmarshalErrorAfterWithSkipping(dec, uo, v.Type(), errRawInlinedNotObject)
|
||||
}
|
||||
}
|
||||
*b = append(*b, quotedName...)
|
||||
*b = append(*b, ':')
|
||||
val, err := dec.ReadValue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*b = append(*b, val...)
|
||||
*b = append(*b, '}')
|
||||
return nil
|
||||
} else {
|
||||
name := string(unquotedName) // TODO: Intern this?
|
||||
|
||||
m := v // must be a map[~string]V
|
||||
if m.IsNil() {
|
||||
m.Set(reflect.MakeMap(m.Type()))
|
||||
}
|
||||
mk := reflect.ValueOf(name)
|
||||
if mkt := m.Type().Key(); mkt != stringType {
|
||||
mk = mk.Convert(mkt)
|
||||
}
|
||||
mv := newAddressableValue(m.Type().Elem()) // TODO: Cache across calls?
|
||||
if v2 := m.MapIndex(mk); v2.IsValid() {
|
||||
mv.Set(v2)
|
||||
}
|
||||
|
||||
unmarshal := f.fncs.unmarshal
|
||||
if uo.Unmarshalers != nil {
|
||||
unmarshal, _ = uo.Unmarshalers.(*Unmarshalers).lookup(unmarshal, mv.Type())
|
||||
}
|
||||
err := unmarshal(dec, mv, uo)
|
||||
m.SetMapIndex(mk, mv.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
335
vendor/github.com/go-json-experiment/json/arshal_methods.go
generated
vendored
Normal file
335
vendor/github.com/go-json-experiment/json/arshal_methods.go
generated
vendored
Normal file
@ -0,0 +1,335 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"errors"
|
||||
"reflect"
|
||||
|
||||
"github.com/go-json-experiment/json/internal"
|
||||
"github.com/go-json-experiment/json/internal/jsonflags"
|
||||
"github.com/go-json-experiment/json/internal/jsonopts"
|
||||
"github.com/go-json-experiment/json/internal/jsonwire"
|
||||
"github.com/go-json-experiment/json/jsontext"
|
||||
)
|
||||
|
||||
var errNonStringValue = errors.New("JSON value must be string type")
|
||||
|
||||
// Interfaces for custom serialization.
|
||||
var (
|
||||
jsonMarshalerType = reflect.TypeFor[Marshaler]()
|
||||
jsonMarshalerToType = reflect.TypeFor[MarshalerTo]()
|
||||
jsonUnmarshalerType = reflect.TypeFor[Unmarshaler]()
|
||||
jsonUnmarshalerFromType = reflect.TypeFor[UnmarshalerFrom]()
|
||||
textAppenderType = reflect.TypeFor[encoding.TextAppender]()
|
||||
textMarshalerType = reflect.TypeFor[encoding.TextMarshaler]()
|
||||
textUnmarshalerType = reflect.TypeFor[encoding.TextUnmarshaler]()
|
||||
|
||||
allMarshalerTypes = []reflect.Type{jsonMarshalerToType, jsonMarshalerType, textAppenderType, textMarshalerType}
|
||||
allUnmarshalerTypes = []reflect.Type{jsonUnmarshalerFromType, jsonUnmarshalerType, textUnmarshalerType}
|
||||
allMethodTypes = append(allMarshalerTypes, allUnmarshalerTypes...)
|
||||
)
|
||||
|
||||
// Marshaler is implemented by types that can marshal themselves.
|
||||
// It is recommended that types implement [MarshalerTo] unless the implementation
|
||||
// is trying to avoid a hard dependency on the "jsontext" package.
|
||||
//
|
||||
// It is recommended that implementations return a buffer that is safe
|
||||
// for the caller to retain and potentially mutate.
|
||||
type Marshaler interface {
|
||||
MarshalJSON() ([]byte, error)
|
||||
}
|
||||
|
||||
// MarshalerTo is implemented by types that can marshal themselves.
|
||||
// It is recommended that types implement MarshalerTo instead of [Marshaler]
|
||||
// since this is both more performant and flexible.
|
||||
// If a type implements both Marshaler and MarshalerTo,
|
||||
// then MarshalerTo takes precedence. In such a case, both implementations
|
||||
// should aim to have equivalent behavior for the default marshal options.
|
||||
//
|
||||
// The implementation must write only one JSON value to the Encoder and
|
||||
// must not retain the pointer to [jsontext.Encoder].
|
||||
type MarshalerTo interface {
|
||||
MarshalJSONTo(*jsontext.Encoder) error
|
||||
|
||||
// TODO: Should users call the MarshalEncode function or
|
||||
// should/can they call this method directly? Does it matter?
|
||||
}
|
||||
|
||||
// Unmarshaler is implemented by types that can unmarshal themselves.
|
||||
// It is recommended that types implement [UnmarshalerFrom] unless the implementation
|
||||
// is trying to avoid a hard dependency on the "jsontext" package.
|
||||
//
|
||||
// The input can be assumed to be a valid encoding of a JSON value
|
||||
// if called from unmarshal functionality in this package.
|
||||
// UnmarshalJSON must copy the JSON data if it is retained after returning.
|
||||
// It is recommended that UnmarshalJSON implement merge semantics when
|
||||
// unmarshaling into a pre-populated value.
|
||||
//
|
||||
// Implementations must not retain or mutate the input []byte.
|
||||
type Unmarshaler interface {
|
||||
UnmarshalJSON([]byte) error
|
||||
}
|
||||
|
||||
// UnmarshalerFrom is implemented by types that can unmarshal themselves.
|
||||
// It is recommended that types implement UnmarshalerFrom instead of [Unmarshaler]
|
||||
// since this is both more performant and flexible.
|
||||
// If a type implements both Unmarshaler and UnmarshalerFrom,
|
||||
// then UnmarshalerFrom takes precedence. In such a case, both implementations
|
||||
// should aim to have equivalent behavior for the default unmarshal options.
|
||||
//
|
||||
// The implementation must read only one JSON value from the Decoder.
|
||||
// It is recommended that UnmarshalJSONFrom implement merge semantics when
|
||||
// unmarshaling into a pre-populated value.
|
||||
//
|
||||
// Implementations must not retain the pointer to [jsontext.Decoder].
|
||||
type UnmarshalerFrom interface {
|
||||
UnmarshalJSONFrom(*jsontext.Decoder) error
|
||||
|
||||
// TODO: Should users call the UnmarshalDecode function or
|
||||
// should/can they call this method directly? Does it matter?
|
||||
}
|
||||
|
||||
func makeMethodArshaler(fncs *arshaler, t reflect.Type) *arshaler {
|
||||
// Avoid injecting method arshaler on the pointer or interface version
|
||||
// to avoid ever calling the method on a nil pointer or interface receiver.
|
||||
// Let it be injected on the value receiver (which is always addressable).
|
||||
if t.Kind() == reflect.Pointer || t.Kind() == reflect.Interface {
|
||||
return fncs
|
||||
}
|
||||
|
||||
if needAddr, ok := implements(t, textMarshalerType); ok {
|
||||
fncs.nonDefault = true
|
||||
prevMarshal := fncs.marshal
|
||||
fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error {
|
||||
if mo.Flags.Get(jsonflags.CallMethodsWithLegacySemantics) &&
|
||||
(needAddr && va.forcedAddr) {
|
||||
return prevMarshal(enc, va, mo)
|
||||
}
|
||||
marshaler := va.Addr().Interface().(encoding.TextMarshaler)
|
||||
if err := export.Encoder(enc).AppendRaw('"', false, func(b []byte) ([]byte, error) {
|
||||
b2, err := marshaler.MarshalText()
|
||||
return append(b, b2...), err
|
||||
}); err != nil {
|
||||
err = wrapSkipFunc(err, "marshal method")
|
||||
if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
|
||||
return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalText") // unlike unmarshal, always wrapped
|
||||
}
|
||||
if !isSemanticError(err) && !export.IsIOError(err) {
|
||||
err = newMarshalErrorBefore(enc, t, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if needAddr, ok := implements(t, textAppenderType); ok {
|
||||
fncs.nonDefault = true
|
||||
prevMarshal := fncs.marshal
|
||||
fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) (err error) {
|
||||
if mo.Flags.Get(jsonflags.CallMethodsWithLegacySemantics) &&
|
||||
(needAddr && va.forcedAddr) {
|
||||
return prevMarshal(enc, va, mo)
|
||||
}
|
||||
appender := va.Addr().Interface().(encoding.TextAppender)
|
||||
if err := export.Encoder(enc).AppendRaw('"', false, appender.AppendText); err != nil {
|
||||
err = wrapSkipFunc(err, "append method")
|
||||
if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
|
||||
return internal.NewMarshalerError(va.Addr().Interface(), err, "AppendText") // unlike unmarshal, always wrapped
|
||||
}
|
||||
if !isSemanticError(err) && !export.IsIOError(err) {
|
||||
err = newMarshalErrorBefore(enc, t, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if needAddr, ok := implements(t, jsonMarshalerType); ok {
|
||||
fncs.nonDefault = true
|
||||
prevMarshal := fncs.marshal
|
||||
fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error {
|
||||
if mo.Flags.Get(jsonflags.CallMethodsWithLegacySemantics) &&
|
||||
((needAddr && va.forcedAddr) || export.Encoder(enc).Tokens.Last.NeedObjectName()) {
|
||||
return prevMarshal(enc, va, mo)
|
||||
}
|
||||
marshaler := va.Addr().Interface().(Marshaler)
|
||||
val, err := marshaler.MarshalJSON()
|
||||
if err != nil {
|
||||
err = wrapSkipFunc(err, "marshal method")
|
||||
if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
|
||||
return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalJSON") // unlike unmarshal, always wrapped
|
||||
}
|
||||
err = newMarshalErrorBefore(enc, t, err)
|
||||
return collapseSemanticErrors(err)
|
||||
}
|
||||
if err := enc.WriteValue(val); err != nil {
|
||||
if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
|
||||
return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalJSON") // unlike unmarshal, always wrapped
|
||||
}
|
||||
if isSyntacticError(err) {
|
||||
err = newMarshalErrorBefore(enc, t, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if needAddr, ok := implements(t, jsonMarshalerToType); ok {
|
||||
fncs.nonDefault = true
|
||||
prevMarshal := fncs.marshal
|
||||
fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error {
|
||||
if mo.Flags.Get(jsonflags.CallMethodsWithLegacySemantics) &&
|
||||
((needAddr && va.forcedAddr) || export.Encoder(enc).Tokens.Last.NeedObjectName()) {
|
||||
return prevMarshal(enc, va, mo)
|
||||
}
|
||||
xe := export.Encoder(enc)
|
||||
prevDepth, prevLength := xe.Tokens.DepthLength()
|
||||
xe.Flags.Set(jsonflags.WithinArshalCall | 1)
|
||||
err := va.Addr().Interface().(MarshalerTo).MarshalJSONTo(enc)
|
||||
xe.Flags.Set(jsonflags.WithinArshalCall | 0)
|
||||
currDepth, currLength := xe.Tokens.DepthLength()
|
||||
if (prevDepth != currDepth || prevLength+1 != currLength) && err == nil {
|
||||
err = errNonSingularValue
|
||||
}
|
||||
if err != nil {
|
||||
err = wrapSkipFunc(err, "marshal method")
|
||||
if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
|
||||
return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalJSONTo") // unlike unmarshal, always wrapped
|
||||
}
|
||||
if !export.IsIOError(err) {
|
||||
err = newSemanticErrorWithPosition(enc, t, prevDepth, prevLength, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := implements(t, textUnmarshalerType); ok {
|
||||
fncs.nonDefault = true
|
||||
fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error {
|
||||
xd := export.Decoder(dec)
|
||||
var flags jsonwire.ValueFlags
|
||||
val, err := xd.ReadValue(&flags)
|
||||
if err != nil {
|
||||
return err // must be a syntactic or I/O error
|
||||
}
|
||||
if val.Kind() == 'n' {
|
||||
if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) {
|
||||
va.SetZero()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if val.Kind() != '"' {
|
||||
return newUnmarshalErrorAfter(dec, t, errNonStringValue)
|
||||
}
|
||||
s := jsonwire.UnquoteMayCopy(val, flags.IsVerbatim())
|
||||
unmarshaler := va.Addr().Interface().(encoding.TextUnmarshaler)
|
||||
if err := unmarshaler.UnmarshalText(s); err != nil {
|
||||
err = wrapSkipFunc(err, "unmarshal method")
|
||||
if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
|
||||
return err // unlike marshal, never wrapped
|
||||
}
|
||||
if !isSemanticError(err) && !isSyntacticError(err) && !export.IsIOError(err) {
|
||||
err = newUnmarshalErrorAfter(dec, t, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := implements(t, jsonUnmarshalerType); ok {
|
||||
fncs.nonDefault = true
|
||||
prevUnmarshal := fncs.unmarshal
|
||||
fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error {
|
||||
if uo.Flags.Get(jsonflags.CallMethodsWithLegacySemantics) &&
|
||||
export.Decoder(dec).Tokens.Last.NeedObjectName() {
|
||||
return prevUnmarshal(dec, va, uo)
|
||||
}
|
||||
val, err := dec.ReadValue()
|
||||
if err != nil {
|
||||
return err // must be a syntactic or I/O error
|
||||
}
|
||||
unmarshaler := va.Addr().Interface().(Unmarshaler)
|
||||
if err := unmarshaler.UnmarshalJSON(val); err != nil {
|
||||
err = wrapSkipFunc(err, "unmarshal method")
|
||||
if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
|
||||
return err // unlike marshal, never wrapped
|
||||
}
|
||||
err = newUnmarshalErrorAfter(dec, t, err)
|
||||
return collapseSemanticErrors(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := implements(t, jsonUnmarshalerFromType); ok {
|
||||
fncs.nonDefault = true
|
||||
prevUnmarshal := fncs.unmarshal
|
||||
fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error {
|
||||
if uo.Flags.Get(jsonflags.CallMethodsWithLegacySemantics) &&
|
||||
export.Decoder(dec).Tokens.Last.NeedObjectName() {
|
||||
return prevUnmarshal(dec, va, uo)
|
||||
}
|
||||
xd := export.Decoder(dec)
|
||||
prevDepth, prevLength := xd.Tokens.DepthLength()
|
||||
xd.Flags.Set(jsonflags.WithinArshalCall | 1)
|
||||
err := va.Addr().Interface().(UnmarshalerFrom).UnmarshalJSONFrom(dec)
|
||||
xd.Flags.Set(jsonflags.WithinArshalCall | 0)
|
||||
currDepth, currLength := xd.Tokens.DepthLength()
|
||||
if (prevDepth != currDepth || prevLength+1 != currLength) && err == nil {
|
||||
err = errNonSingularValue
|
||||
}
|
||||
if err != nil {
|
||||
err = wrapSkipFunc(err, "unmarshal method")
|
||||
if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
|
||||
if err2 := xd.SkipUntil(prevDepth, prevLength+1); err2 != nil {
|
||||
return err2
|
||||
}
|
||||
return err // unlike marshal, never wrapped
|
||||
}
|
||||
if !isSyntacticError(err) && !export.IsIOError(err) {
|
||||
err = newSemanticErrorWithPosition(dec, t, prevDepth, prevLength, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fncs
|
||||
}
|
||||
|
||||
// implementsAny is like t.Implements(ifaceType) for a list of interfaces,
|
||||
// but checks whether either t or reflect.PointerTo(t) implements the interface.
|
||||
func implementsAny(t reflect.Type, ifaceTypes ...reflect.Type) bool {
|
||||
for _, ifaceType := range ifaceTypes {
|
||||
if _, ok := implements(t, ifaceType); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// implements is like t.Implements(ifaceType) but checks whether
|
||||
// either t or reflect.PointerTo(t) implements the interface.
|
||||
// It also reports whether the value needs to be addressed
|
||||
// in order to satisfy the interface.
|
||||
func implements(t, ifaceType reflect.Type) (needAddr, ok bool) {
|
||||
switch {
|
||||
case t.Implements(ifaceType):
|
||||
return false, true
|
||||
case reflect.PointerTo(t).Implements(ifaceType):
|
||||
return true, true
|
||||
default:
|
||||
return false, false
|
||||
}
|
||||
}
|
598
vendor/github.com/go-json-experiment/json/arshal_time.go
generated
vendored
Normal file
598
vendor/github.com/go-json-experiment/json/arshal_time.go
generated
vendored
Normal file
@ -0,0 +1,598 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"cmp"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/bits"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-json-experiment/json/internal"
|
||||
"github.com/go-json-experiment/json/internal/jsonflags"
|
||||
"github.com/go-json-experiment/json/internal/jsonopts"
|
||||
"github.com/go-json-experiment/json/internal/jsonwire"
|
||||
"github.com/go-json-experiment/json/jsontext"
|
||||
)
|
||||
|
||||
var (
|
||||
timeDurationType = reflect.TypeFor[time.Duration]()
|
||||
timeTimeType = reflect.TypeFor[time.Time]()
|
||||
)
|
||||
|
||||
func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler {
|
||||
// Ideally, time types would implement MarshalerTo and UnmarshalerFrom,
|
||||
// but that would incur a dependency on package json from package time.
|
||||
// Given how widely used time is, it is more acceptable that we incur a
|
||||
// dependency on time from json.
|
||||
//
|
||||
// Injecting the arshaling functionality like this will not be identical
|
||||
// to actually declaring methods on the time types since embedding of the
|
||||
// time types will not be able to forward this functionality.
|
||||
switch t {
|
||||
case timeDurationType:
|
||||
fncs.nonDefault = true
|
||||
marshalNano := fncs.marshal
|
||||
fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error {
|
||||
xe := export.Encoder(enc)
|
||||
var m durationArshaler
|
||||
if mo.Format != "" && mo.FormatDepth == xe.Tokens.Depth() {
|
||||
if !m.initFormat(mo.Format) {
|
||||
return newInvalidFormatError(enc, t, mo)
|
||||
}
|
||||
} else if mo.Flags.Get(jsonflags.FormatTimeWithLegacySemantics) {
|
||||
return marshalNano(enc, va, mo)
|
||||
}
|
||||
|
||||
// TODO(https://go.dev/issue/62121): Use reflect.Value.AssertTo.
|
||||
m.td = *va.Addr().Interface().(*time.Duration)
|
||||
k := stringOrNumberKind(!m.isNumeric() || xe.Tokens.Last.NeedObjectName() || mo.Flags.Get(jsonflags.StringifyNumbers))
|
||||
if err := xe.AppendRaw(k, true, m.appendMarshal); err != nil {
|
||||
if !isSyntacticError(err) && !export.IsIOError(err) {
|
||||
err = newMarshalErrorBefore(enc, t, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
unmarshalNano := fncs.unmarshal
|
||||
fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error {
|
||||
xd := export.Decoder(dec)
|
||||
var u durationArshaler
|
||||
if uo.Format != "" && uo.FormatDepth == xd.Tokens.Depth() {
|
||||
if !u.initFormat(uo.Format) {
|
||||
return newInvalidFormatError(dec, t, uo)
|
||||
}
|
||||
} else if uo.Flags.Get(jsonflags.FormatTimeWithLegacySemantics) {
|
||||
return unmarshalNano(dec, va, uo)
|
||||
}
|
||||
|
||||
stringify := !u.isNumeric() || xd.Tokens.Last.NeedObjectName() || uo.Flags.Get(jsonflags.StringifyNumbers)
|
||||
var flags jsonwire.ValueFlags
|
||||
td := va.Addr().Interface().(*time.Duration)
|
||||
val, err := xd.ReadValue(&flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch k := val.Kind(); k {
|
||||
case 'n':
|
||||
if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) {
|
||||
*td = time.Duration(0)
|
||||
}
|
||||
return nil
|
||||
case '"':
|
||||
if !stringify {
|
||||
break
|
||||
}
|
||||
val = jsonwire.UnquoteMayCopy(val, flags.IsVerbatim())
|
||||
if err := u.unmarshal(val); err != nil {
|
||||
return newUnmarshalErrorAfter(dec, t, err)
|
||||
}
|
||||
*td = u.td
|
||||
return nil
|
||||
case '0':
|
||||
if stringify {
|
||||
break
|
||||
}
|
||||
if err := u.unmarshal(val); err != nil {
|
||||
return newUnmarshalErrorAfter(dec, t, err)
|
||||
}
|
||||
*td = u.td
|
||||
return nil
|
||||
}
|
||||
return newUnmarshalErrorAfter(dec, t, nil)
|
||||
}
|
||||
case timeTimeType:
|
||||
fncs.nonDefault = true
|
||||
fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) (err error) {
|
||||
xe := export.Encoder(enc)
|
||||
var m timeArshaler
|
||||
if mo.Format != "" && mo.FormatDepth == xe.Tokens.Depth() {
|
||||
if !m.initFormat(mo.Format) {
|
||||
return newInvalidFormatError(enc, t, mo)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(https://go.dev/issue/62121): Use reflect.Value.AssertTo.
|
||||
m.tt = *va.Addr().Interface().(*time.Time)
|
||||
k := stringOrNumberKind(!m.isNumeric() || xe.Tokens.Last.NeedObjectName() || mo.Flags.Get(jsonflags.StringifyNumbers))
|
||||
if err := xe.AppendRaw(k, !m.hasCustomFormat(), m.appendMarshal); err != nil {
|
||||
if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
|
||||
return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalJSON") // unlike unmarshal, always wrapped
|
||||
}
|
||||
if !isSyntacticError(err) && !export.IsIOError(err) {
|
||||
err = newMarshalErrorBefore(enc, t, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) (err error) {
|
||||
xd := export.Decoder(dec)
|
||||
var u timeArshaler
|
||||
if uo.Format != "" && uo.FormatDepth == xd.Tokens.Depth() {
|
||||
if !u.initFormat(uo.Format) {
|
||||
return newInvalidFormatError(dec, t, uo)
|
||||
}
|
||||
} else if uo.Flags.Get(jsonflags.FormatTimeWithLegacySemantics) {
|
||||
u.looseRFC3339 = true
|
||||
}
|
||||
|
||||
stringify := !u.isNumeric() || xd.Tokens.Last.NeedObjectName() || uo.Flags.Get(jsonflags.StringifyNumbers)
|
||||
var flags jsonwire.ValueFlags
|
||||
tt := va.Addr().Interface().(*time.Time)
|
||||
val, err := xd.ReadValue(&flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch k := val.Kind(); k {
|
||||
case 'n':
|
||||
if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) {
|
||||
*tt = time.Time{}
|
||||
}
|
||||
return nil
|
||||
case '"':
|
||||
if !stringify {
|
||||
break
|
||||
}
|
||||
val = jsonwire.UnquoteMayCopy(val, flags.IsVerbatim())
|
||||
if err := u.unmarshal(val); err != nil {
|
||||
if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
|
||||
return err // unlike marshal, never wrapped
|
||||
}
|
||||
return newUnmarshalErrorAfter(dec, t, err)
|
||||
}
|
||||
*tt = u.tt
|
||||
return nil
|
||||
case '0':
|
||||
if stringify {
|
||||
break
|
||||
}
|
||||
if err := u.unmarshal(val); err != nil {
|
||||
if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
|
||||
return err // unlike marshal, never wrapped
|
||||
}
|
||||
return newUnmarshalErrorAfter(dec, t, err)
|
||||
}
|
||||
*tt = u.tt
|
||||
return nil
|
||||
}
|
||||
return newUnmarshalErrorAfter(dec, t, nil)
|
||||
}
|
||||
}
|
||||
return fncs
|
||||
}
|
||||
|
||||
type durationArshaler struct {
|
||||
td time.Duration
|
||||
|
||||
// base records the representation where:
|
||||
// - 0 uses time.Duration.String
|
||||
// - 1e0, 1e3, 1e6, or 1e9 use a decimal encoding of the duration as
|
||||
// nanoseconds, microseconds, milliseconds, or seconds.
|
||||
base uint64
|
||||
}
|
||||
|
||||
func (a *durationArshaler) initFormat(format string) (ok bool) {
|
||||
switch format {
|
||||
case "units":
|
||||
a.base = 0
|
||||
case "sec":
|
||||
a.base = 1e9
|
||||
case "milli":
|
||||
a.base = 1e6
|
||||
case "micro":
|
||||
a.base = 1e3
|
||||
case "nano":
|
||||
a.base = 1e0
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (a *durationArshaler) isNumeric() bool {
|
||||
return a.base != 0 && a.base != 60
|
||||
}
|
||||
|
||||
func (a *durationArshaler) appendMarshal(b []byte) ([]byte, error) {
|
||||
switch a.base {
|
||||
case 0:
|
||||
return append(b, a.td.String()...), nil
|
||||
default:
|
||||
return appendDurationBase10(b, a.td, a.base), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (a *durationArshaler) unmarshal(b []byte) (err error) {
|
||||
switch a.base {
|
||||
case 0:
|
||||
a.td, err = time.ParseDuration(string(b))
|
||||
default:
|
||||
a.td, err = parseDurationBase10(b, a.base)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type timeArshaler struct {
|
||||
tt time.Time
|
||||
|
||||
// base records the representation where:
|
||||
// - 0 uses RFC 3339 encoding of the timestamp
|
||||
// - 1e0, 1e3, 1e6, or 1e9 use a decimal encoding of the timestamp as
|
||||
// seconds, milliseconds, microseconds, or nanoseconds since Unix epoch.
|
||||
// - math.MaxUint uses time.Time.Format to encode the timestamp
|
||||
base uint64
|
||||
format string // time format passed to time.Parse
|
||||
|
||||
looseRFC3339 bool
|
||||
}
|
||||
|
||||
func (a *timeArshaler) initFormat(format string) bool {
|
||||
// We assume that an exported constant in the time package will
|
||||
// always start with an uppercase ASCII letter.
|
||||
if len(format) == 0 {
|
||||
return false
|
||||
}
|
||||
a.base = math.MaxUint // implies custom format
|
||||
if c := format[0]; !('a' <= c && c <= 'z') && !('A' <= c && c <= 'Z') {
|
||||
a.format = format
|
||||
return true
|
||||
}
|
||||
switch format {
|
||||
case "ANSIC":
|
||||
a.format = time.ANSIC
|
||||
case "UnixDate":
|
||||
a.format = time.UnixDate
|
||||
case "RubyDate":
|
||||
a.format = time.RubyDate
|
||||
case "RFC822":
|
||||
a.format = time.RFC822
|
||||
case "RFC822Z":
|
||||
a.format = time.RFC822Z
|
||||
case "RFC850":
|
||||
a.format = time.RFC850
|
||||
case "RFC1123":
|
||||
a.format = time.RFC1123
|
||||
case "RFC1123Z":
|
||||
a.format = time.RFC1123Z
|
||||
case "RFC3339":
|
||||
a.base = 0
|
||||
a.format = time.RFC3339
|
||||
case "RFC3339Nano":
|
||||
a.base = 0
|
||||
a.format = time.RFC3339Nano
|
||||
case "Kitchen":
|
||||
a.format = time.Kitchen
|
||||
case "Stamp":
|
||||
a.format = time.Stamp
|
||||
case "StampMilli":
|
||||
a.format = time.StampMilli
|
||||
case "StampMicro":
|
||||
a.format = time.StampMicro
|
||||
case "StampNano":
|
||||
a.format = time.StampNano
|
||||
case "DateTime":
|
||||
a.format = time.DateTime
|
||||
case "DateOnly":
|
||||
a.format = time.DateOnly
|
||||
case "TimeOnly":
|
||||
a.format = time.TimeOnly
|
||||
case "unix":
|
||||
a.base = 1e0
|
||||
case "unixmilli":
|
||||
a.base = 1e3
|
||||
case "unixmicro":
|
||||
a.base = 1e6
|
||||
case "unixnano":
|
||||
a.base = 1e9
|
||||
default:
|
||||
// Reject any Go identifier in case new constants are supported.
|
||||
if strings.TrimFunc(format, isLetterOrDigit) == "" {
|
||||
return false
|
||||
}
|
||||
a.format = format
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (a *timeArshaler) isNumeric() bool {
|
||||
return int(a.base) > 0
|
||||
}
|
||||
|
||||
func (a *timeArshaler) hasCustomFormat() bool {
|
||||
return a.base == math.MaxUint
|
||||
}
|
||||
|
||||
func (a *timeArshaler) appendMarshal(b []byte) ([]byte, error) {
|
||||
switch a.base {
|
||||
case 0:
|
||||
format := cmp.Or(a.format, time.RFC3339Nano)
|
||||
n0 := len(b)
|
||||
b = a.tt.AppendFormat(b, format)
|
||||
// Not all Go timestamps can be represented as valid RFC 3339.
|
||||
// Explicitly check for these edge cases.
|
||||
// See https://go.dev/issue/4556 and https://go.dev/issue/54580.
|
||||
switch b := b[n0:]; {
|
||||
case b[len("9999")] != '-': // year must be exactly 4 digits wide
|
||||
return b, errors.New("year outside of range [0,9999]")
|
||||
case b[len(b)-1] != 'Z':
|
||||
c := b[len(b)-len("Z07:00")]
|
||||
if ('0' <= c && c <= '9') || parseDec2(b[len(b)-len("07:00"):]) >= 24 {
|
||||
return b, errors.New("timezone hour outside of range [0,23]")
|
||||
}
|
||||
}
|
||||
return b, nil
|
||||
case math.MaxUint:
|
||||
return a.tt.AppendFormat(b, a.format), nil
|
||||
default:
|
||||
return appendTimeUnix(b, a.tt, a.base), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (a *timeArshaler) unmarshal(b []byte) (err error) {
|
||||
switch a.base {
|
||||
case 0:
|
||||
// Use time.Time.UnmarshalText to avoid possible string allocation.
|
||||
if err := a.tt.UnmarshalText(b); err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO(https://go.dev/issue/57912):
|
||||
// RFC 3339 specifies the grammar for a valid timestamp.
|
||||
// However, the parsing functionality in "time" is too loose and
|
||||
// incorrectly accepts invalid timestamps as valid.
|
||||
// Remove these manual checks when "time" checks it for us.
|
||||
newParseError := func(layout, value, layoutElem, valueElem, message string) error {
|
||||
return &time.ParseError{Layout: layout, Value: value, LayoutElem: layoutElem, ValueElem: valueElem, Message: message}
|
||||
}
|
||||
switch {
|
||||
case a.looseRFC3339:
|
||||
return nil
|
||||
case b[len("2006-01-02T")+1] == ':': // hour must be two digits
|
||||
return newParseError(time.RFC3339, string(b), "15", string(b[len("2006-01-02T"):][:1]), "")
|
||||
case b[len("2006-01-02T15:04:05")] == ',': // sub-second separator must be a period
|
||||
return newParseError(time.RFC3339, string(b), ".", ",", "")
|
||||
case b[len(b)-1] != 'Z':
|
||||
switch {
|
||||
case parseDec2(b[len(b)-len("07:00"):]) >= 24: // timezone hour must be in range
|
||||
return newParseError(time.RFC3339, string(b), "Z07:00", string(b[len(b)-len("Z07:00"):]), ": timezone hour out of range")
|
||||
case parseDec2(b[len(b)-len("00"):]) >= 60: // timezone minute must be in range
|
||||
return newParseError(time.RFC3339, string(b), "Z07:00", string(b[len(b)-len("Z07:00"):]), ": timezone minute out of range")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
case math.MaxUint:
|
||||
a.tt, err = time.Parse(a.format, string(b))
|
||||
return err
|
||||
default:
|
||||
a.tt, err = parseTimeUnix(b, a.base)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// appendDurationBase10 appends d formatted as a decimal fractional number,
|
||||
// where pow10 is a power-of-10 used to scale down the number.
|
||||
func appendDurationBase10(b []byte, d time.Duration, pow10 uint64) []byte {
|
||||
b, n := mayAppendDurationSign(b, d) // append sign
|
||||
whole, frac := bits.Div64(0, n, uint64(pow10)) // compute whole and frac fields
|
||||
b = strconv.AppendUint(b, whole, 10) // append whole field
|
||||
return appendFracBase10(b, frac, pow10) // append frac field
|
||||
}
|
||||
|
||||
// parseDurationBase10 parses d from a decimal fractional number,
|
||||
// where pow10 is a power-of-10 used to scale up the number.
|
||||
func parseDurationBase10(b []byte, pow10 uint64) (time.Duration, error) {
|
||||
suffix, neg := consumeSign(b) // consume sign
|
||||
wholeBytes, fracBytes := bytesCutByte(suffix, '.', true) // consume whole and frac fields
|
||||
whole, okWhole := jsonwire.ParseUint(wholeBytes) // parse whole field; may overflow
|
||||
frac, okFrac := parseFracBase10(fracBytes, pow10) // parse frac field
|
||||
hi, lo := bits.Mul64(whole, uint64(pow10)) // overflow if hi > 0
|
||||
sum, co := bits.Add64(lo, uint64(frac), 0) // overflow if co > 0
|
||||
switch d := mayApplyDurationSign(sum, neg); { // overflow if neg != (d < 0)
|
||||
case (!okWhole && whole != math.MaxUint64) || !okFrac:
|
||||
return 0, fmt.Errorf("invalid duration %q: %w", b, strconv.ErrSyntax)
|
||||
case !okWhole || hi > 0 || co > 0 || neg != (d < 0):
|
||||
return 0, fmt.Errorf("invalid duration %q: %w", b, strconv.ErrRange)
|
||||
default:
|
||||
return d, nil
|
||||
}
|
||||
}
|
||||
|
||||
// mayAppendDurationSign appends a negative sign if n is negative.
|
||||
func mayAppendDurationSign(b []byte, d time.Duration) ([]byte, uint64) {
|
||||
if d < 0 {
|
||||
b = append(b, '-')
|
||||
d *= -1
|
||||
}
|
||||
return b, uint64(d)
|
||||
}
|
||||
|
||||
// mayApplyDurationSign inverts n if neg is specified.
|
||||
func mayApplyDurationSign(n uint64, neg bool) time.Duration {
|
||||
if neg {
|
||||
return -1 * time.Duration(n)
|
||||
} else {
|
||||
return +1 * time.Duration(n)
|
||||
}
|
||||
}
|
||||
|
||||
// appendTimeUnix appends t formatted as a decimal fractional number,
|
||||
// where pow10 is a power-of-10 used to scale up the number.
|
||||
func appendTimeUnix(b []byte, t time.Time, pow10 uint64) []byte {
|
||||
sec, nsec := t.Unix(), int64(t.Nanosecond())
|
||||
if sec < 0 {
|
||||
b = append(b, '-')
|
||||
sec, nsec = negateSecNano(sec, nsec)
|
||||
}
|
||||
switch {
|
||||
case pow10 == 1e0: // fast case where units is in seconds
|
||||
b = strconv.AppendUint(b, uint64(sec), 10)
|
||||
return appendFracBase10(b, uint64(nsec), 1e9)
|
||||
case uint64(sec) < 1e9: // intermediate case where units is not seconds, but no overflow
|
||||
b = strconv.AppendUint(b, uint64(sec)*uint64(pow10)+uint64(uint64(nsec)/(1e9/pow10)), 10)
|
||||
return appendFracBase10(b, (uint64(nsec)*pow10)%1e9, 1e9)
|
||||
default: // slow case where units is not seconds and overflow would occur
|
||||
b = strconv.AppendUint(b, uint64(sec), 10)
|
||||
b = appendPaddedBase10(b, uint64(nsec)/(1e9/pow10), pow10)
|
||||
return appendFracBase10(b, (uint64(nsec)*pow10)%1e9, 1e9)
|
||||
}
|
||||
}
|
||||
|
||||
// parseTimeUnix parses t formatted as a decimal fractional number,
|
||||
// where pow10 is a power-of-10 used to scale down the number.
|
||||
func parseTimeUnix(b []byte, pow10 uint64) (time.Time, error) {
|
||||
suffix, neg := consumeSign(b) // consume sign
|
||||
wholeBytes, fracBytes := bytesCutByte(suffix, '.', true) // consume whole and frac fields
|
||||
whole, okWhole := jsonwire.ParseUint(wholeBytes) // parse whole field; may overflow
|
||||
frac, okFrac := parseFracBase10(fracBytes, 1e9/pow10) // parse frac field
|
||||
var sec, nsec int64
|
||||
switch {
|
||||
case pow10 == 1e0: // fast case where units is in seconds
|
||||
sec = int64(whole) // check overflow later after negation
|
||||
nsec = int64(frac) // cannot overflow
|
||||
case okWhole: // intermediate case where units is not seconds, but no overflow
|
||||
sec = int64(whole / pow10) // check overflow later after negation
|
||||
nsec = int64((whole%pow10)*(1e9/pow10) + frac) // cannot overflow
|
||||
case !okWhole && whole == math.MaxUint64: // slow case where units is not seconds and overflow occurred
|
||||
width := int(math.Log10(float64(pow10))) // compute len(strconv.Itoa(pow10-1))
|
||||
whole, okWhole = jsonwire.ParseUint(wholeBytes[:len(wholeBytes)-width]) // parse the upper whole field
|
||||
mid, _ := parsePaddedBase10(wholeBytes[len(wholeBytes)-width:], pow10) // parse the lower whole field
|
||||
sec = int64(whole) // check overflow later after negation
|
||||
nsec = int64(mid*(1e9/pow10) + frac) // cannot overflow
|
||||
}
|
||||
if neg {
|
||||
sec, nsec = negateSecNano(sec, nsec)
|
||||
}
|
||||
switch t := time.Unix(sec, nsec).UTC(); {
|
||||
case (!okWhole && whole != math.MaxUint64) || !okFrac:
|
||||
return time.Time{}, fmt.Errorf("invalid time %q: %w", b, strconv.ErrSyntax)
|
||||
case !okWhole || neg != (t.Unix() < 0):
|
||||
return time.Time{}, fmt.Errorf("invalid time %q: %w", b, strconv.ErrRange)
|
||||
default:
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
|
||||
// negateSecNano negates a Unix timestamp, where nsec must be within [0, 1e9).
|
||||
func negateSecNano(sec, nsec int64) (int64, int64) {
|
||||
sec = ^sec // twos-complement negation (i.e., -1*sec + 1)
|
||||
nsec = -nsec + 1e9 // negate nsec and add 1e9 (which is the extra +1 from sec negation)
|
||||
sec += int64(nsec / 1e9) // handle possible overflow of nsec if it started as zero
|
||||
nsec %= 1e9 // ensure nsec stays within [0, 1e9)
|
||||
return sec, nsec
|
||||
}
|
||||
|
||||
// appendFracBase10 appends the fraction of n/max10,
|
||||
// where max10 is a power-of-10 that is larger than n.
|
||||
func appendFracBase10(b []byte, n, max10 uint64) []byte {
|
||||
if n == 0 {
|
||||
return b
|
||||
}
|
||||
return bytes.TrimRight(appendPaddedBase10(append(b, '.'), n, max10), "0")
|
||||
}
|
||||
|
||||
// parseFracBase10 parses the fraction of n/max10,
|
||||
// where max10 is a power-of-10 that is larger than n.
|
||||
func parseFracBase10(b []byte, max10 uint64) (n uint64, ok bool) {
|
||||
switch {
|
||||
case len(b) == 0:
|
||||
return 0, true
|
||||
case len(b) < len(".0") || b[0] != '.':
|
||||
return 0, false
|
||||
}
|
||||
return parsePaddedBase10(b[len("."):], max10)
|
||||
}
|
||||
|
||||
// appendPaddedBase10 appends a zero-padded encoding of n,
|
||||
// where max10 is a power-of-10 that is larger than n.
|
||||
func appendPaddedBase10(b []byte, n, max10 uint64) []byte {
|
||||
if n < max10/10 {
|
||||
// Formatting of n is shorter than log10(max10),
|
||||
// so add max10/10 to ensure the length is equal to log10(max10).
|
||||
i := len(b)
|
||||
b = strconv.AppendUint(b, n+max10/10, 10)
|
||||
b[i]-- // subtract the addition of max10/10
|
||||
return b
|
||||
}
|
||||
return strconv.AppendUint(b, n, 10)
|
||||
}
|
||||
|
||||
// parsePaddedBase10 parses b as the zero-padded encoding of n,
|
||||
// where max10 is a power-of-10 that is larger than n.
|
||||
// Truncated suffix is treated as implicit zeros.
|
||||
// Extended suffix is ignored, but verified to contain only digits.
|
||||
func parsePaddedBase10(b []byte, max10 uint64) (n uint64, ok bool) {
|
||||
pow10 := uint64(1)
|
||||
for pow10 < max10 {
|
||||
n *= 10
|
||||
if len(b) > 0 {
|
||||
if b[0] < '0' || '9' < b[0] {
|
||||
return n, false
|
||||
}
|
||||
n += uint64(b[0] - '0')
|
||||
b = b[1:]
|
||||
}
|
||||
pow10 *= 10
|
||||
}
|
||||
if len(b) > 0 && len(bytes.TrimRight(b, "0123456789")) > 0 {
|
||||
return n, false // trailing characters are not digits
|
||||
}
|
||||
return n, true
|
||||
}
|
||||
|
||||
// consumeSign consumes an optional leading negative sign.
|
||||
func consumeSign(b []byte) ([]byte, bool) {
|
||||
if len(b) > 0 && b[0] == '-' {
|
||||
return b[len("-"):], true
|
||||
}
|
||||
return b, false
|
||||
}
|
||||
|
||||
// bytesCutByte is similar to bytes.Cut(b, []byte{c}),
|
||||
// except c may optionally be included as part of the suffix.
|
||||
func bytesCutByte(b []byte, c byte, include bool) ([]byte, []byte) {
|
||||
if i := bytes.IndexByte(b, c); i >= 0 {
|
||||
if include {
|
||||
return b[:i], b[i:]
|
||||
}
|
||||
return b[:i], b[i+1:]
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// parseDec2 parses b as an unsigned, base-10, 2-digit number.
|
||||
// The result is undefined if digits are not base-10.
|
||||
func parseDec2(b []byte) byte {
|
||||
if len(b) < 2 {
|
||||
return 0
|
||||
}
|
||||
return 10*(b[0]-'0') + (b[1] - '0')
|
||||
}
|
168
vendor/github.com/go-json-experiment/json/doc.go
generated
vendored
Normal file
168
vendor/github.com/go-json-experiment/json/doc.go
generated
vendored
Normal file
@ -0,0 +1,168 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package json implements semantic processing of JSON as specified in RFC 8259.
|
||||
// JSON is a simple data interchange format that can represent
|
||||
// primitive data types such as booleans, strings, and numbers,
|
||||
// in addition to structured data types such as objects and arrays.
|
||||
//
|
||||
// [Marshal] and [Unmarshal] encode and decode Go values
|
||||
// to/from JSON text contained within a []byte.
|
||||
// [MarshalWrite] and [UnmarshalRead] operate on JSON text
|
||||
// by writing to or reading from an [io.Writer] or [io.Reader].
|
||||
// [MarshalEncode] and [UnmarshalDecode] operate on JSON text
|
||||
// by encoding to or decoding from a [jsontext.Encoder] or [jsontext.Decoder].
|
||||
// [Options] may be passed to each of the marshal or unmarshal functions
|
||||
// to configure the semantic behavior of marshaling and unmarshaling
|
||||
// (i.e., alter how JSON data is understood as Go data and vice versa).
|
||||
// [jsontext.Options] may also be passed to the marshal or unmarshal functions
|
||||
// to configure the syntactic behavior of encoding or decoding.
|
||||
//
|
||||
// The data types of JSON are mapped to/from the data types of Go based on
|
||||
// the closest logical equivalent between the two type systems. For example,
|
||||
// a JSON boolean corresponds with a Go bool,
|
||||
// a JSON string corresponds with a Go string,
|
||||
// a JSON number corresponds with a Go int, uint or float,
|
||||
// a JSON array corresponds with a Go slice or array, and
|
||||
// a JSON object corresponds with a Go struct or map.
|
||||
// See the documentation on [Marshal] and [Unmarshal] for a comprehensive list
|
||||
// of how the JSON and Go type systems correspond.
|
||||
//
|
||||
// Arbitrary Go types can customize their JSON representation by implementing
|
||||
// [Marshaler], [MarshalerTo], [Unmarshaler], or [UnmarshalerFrom].
|
||||
// This provides authors of Go types with control over how their types are
|
||||
// serialized as JSON. Alternatively, users can implement functions that match
|
||||
// [MarshalFunc], [MarshalToFunc], [UnmarshalFunc], or [UnmarshalFromFunc]
|
||||
// to specify the JSON representation for arbitrary types.
|
||||
// This provides callers of JSON functionality with control over
|
||||
// how any arbitrary type is serialized as JSON.
|
||||
//
|
||||
// # JSON Representation of Go structs
|
||||
//
|
||||
// A Go struct is naturally represented as a JSON object,
|
||||
// where each Go struct field corresponds with a JSON object member.
|
||||
// When marshaling, all Go struct fields are recursively encoded in depth-first
|
||||
// order as JSON object members except those that are ignored or omitted.
|
||||
// When unmarshaling, JSON object members are recursively decoded
|
||||
// into the corresponding Go struct fields.
|
||||
// Object members that do not match any struct fields,
|
||||
// also known as “unknown members”, are ignored by default or rejected
|
||||
// if [RejectUnknownMembers] is specified.
|
||||
//
|
||||
// The representation of each struct field can be customized in the
|
||||
// "json" struct field tag, where the tag is a comma separated list of options.
|
||||
// As a special case, if the entire tag is `json:"-"`,
|
||||
// then the field is ignored with regard to its JSON representation.
|
||||
// Some options also have equivalent behavior controlled by a caller-specified [Options].
|
||||
// Field-specified options take precedence over caller-specified options.
|
||||
//
|
||||
// The first option is the JSON object name override for the Go struct field.
|
||||
// If the name is not specified, then the Go struct field name
|
||||
// is used as the JSON object name. JSON names containing commas or quotes,
|
||||
// or names identical to "" or "-", can be specified using
|
||||
// a single-quoted string literal, where the syntax is identical to
|
||||
// the Go grammar for a double-quoted string literal,
|
||||
// but instead uses single quotes as the delimiters.
|
||||
// By default, unmarshaling uses case-sensitive matching to identify
|
||||
// the Go struct field associated with a JSON object name.
|
||||
//
|
||||
// After the name, the following tag options are supported:
|
||||
//
|
||||
// - omitzero: When marshaling, the "omitzero" option specifies that
|
||||
// the struct field should be omitted if the field value is zero
|
||||
// as determined by the "IsZero() bool" method if present,
|
||||
// otherwise based on whether the field is the zero Go value.
|
||||
// This option has no effect when unmarshaling.
|
||||
//
|
||||
// - omitempty: When marshaling, the "omitempty" option specifies that
|
||||
// the struct field should be omitted if the field value would have been
|
||||
// encoded as a JSON null, empty string, empty object, or empty array.
|
||||
// This option has no effect when unmarshaling.
|
||||
//
|
||||
// - string: The "string" option specifies that [StringifyNumbers]
|
||||
// be set when marshaling or unmarshaling a struct field value.
|
||||
// This causes numeric types to be encoded as a JSON number
|
||||
// within a JSON string, and to be decoded from a JSON string
|
||||
// containing the JSON number without any surrounding whitespace.
|
||||
// This extra level of encoding is often necessary since
|
||||
// many JSON parsers cannot precisely represent 64-bit integers.
|
||||
//
|
||||
// - case: When unmarshaling, the "case" option specifies how
|
||||
// JSON object names are matched with the JSON name for Go struct fields.
|
||||
// The option is a key-value pair specified as "case:value" where
|
||||
// the value must either be 'ignore' or 'strict'.
|
||||
// The 'ignore' value specifies that matching is case-insensitive
|
||||
// where dashes and underscores are also ignored. If multiple fields match,
|
||||
// the first declared field in breadth-first order takes precedence.
|
||||
// The 'strict' value specifies that matching is case-sensitive.
|
||||
// This takes precedence over the [MatchCaseInsensitiveNames] option.
|
||||
//
|
||||
// - inline: The "inline" option specifies that
|
||||
// the JSON representable content of this field type is to be promoted
|
||||
// as if they were specified in the parent struct.
|
||||
// It is the JSON equivalent of Go struct embedding.
|
||||
// A Go embedded field is implicitly inlined unless an explicit JSON name
|
||||
// is specified. The inlined field must be a Go struct
|
||||
// (that does not implement any JSON methods), [jsontext.Value],
|
||||
// map[~string]T, or an unnamed pointer to such types. When marshaling,
|
||||
// inlined fields from a pointer type are omitted if it is nil.
|
||||
// Inlined fields of type [jsontext.Value] and map[~string]T are called
|
||||
// “inlined fallbacks” as they can represent all possible
|
||||
// JSON object members not directly handled by the parent struct.
|
||||
// Only one inlined fallback field may be specified in a struct,
|
||||
// while many non-fallback fields may be specified. This option
|
||||
// must not be specified with any other option (including the JSON name).
|
||||
//
|
||||
// - unknown: The "unknown" option is a specialized variant
|
||||
// of the inlined fallback to indicate that this Go struct field
|
||||
// contains any number of unknown JSON object members. The field type must
|
||||
// be a [jsontext.Value], map[~string]T, or an unnamed pointer to such types.
|
||||
// If [DiscardUnknownMembers] is specified when marshaling,
|
||||
// the contents of this field are ignored.
|
||||
// If [RejectUnknownMembers] is specified when unmarshaling,
|
||||
// any unknown object members are rejected regardless of whether
|
||||
// an inlined fallback with the "unknown" option exists. This option
|
||||
// must not be specified with any other option (including the JSON name).
|
||||
//
|
||||
// - format: The "format" option specifies a format flag
|
||||
// used to specialize the formatting of the field value.
|
||||
// The option is a key-value pair specified as "format:value" where
|
||||
// the value must be either a literal consisting of letters and numbers
|
||||
// (e.g., "format:RFC3339") or a single-quoted string literal
|
||||
// (e.g., "format:'2006-01-02'"). The interpretation of the format flag
|
||||
// is determined by the struct field type.
|
||||
//
|
||||
// The "omitzero" and "omitempty" options are mostly semantically identical.
|
||||
// The former is defined in terms of the Go type system,
|
||||
// while the latter in terms of the JSON type system.
|
||||
// Consequently they behave differently in some circumstances.
|
||||
// For example, only a nil slice or map is omitted under "omitzero", while
|
||||
// an empty slice or map is omitted under "omitempty" regardless of nilness.
|
||||
// The "omitzero" option is useful for types with a well-defined zero value
|
||||
// (e.g., [net/netip.Addr]) or have an IsZero method (e.g., [time.Time.IsZero]).
|
||||
//
|
||||
// Every Go struct corresponds to a list of JSON representable fields
|
||||
// which is constructed by performing a breadth-first search over
|
||||
// all struct fields (excluding unexported or ignored fields),
|
||||
// where the search recursively descends into inlined structs.
|
||||
// The set of non-inlined fields in a struct must have unique JSON names.
|
||||
// If multiple fields all have the same JSON name, then the one
|
||||
// at shallowest depth takes precedence and the other fields at deeper depths
|
||||
// are excluded from the list of JSON representable fields.
|
||||
// If multiple fields at the shallowest depth have the same JSON name,
|
||||
// but exactly one is explicitly tagged with a JSON name,
|
||||
// then that field takes precedence and all others are excluded from the list.
|
||||
// This is analogous to Go visibility rules for struct field selection
|
||||
// with embedded struct types.
|
||||
//
|
||||
// Marshaling or unmarshaling a non-empty struct
|
||||
// without any JSON representable fields results in a [SemanticError].
|
||||
// Unexported fields must not have any `json` tags except for `json:"-"`.
|
||||
package json
|
||||
|
||||
// requireKeyedLiterals can be embedded in a struct to require keyed literals.
|
||||
type requireKeyedLiterals struct{}
|
||||
|
||||
// nonComparable can be embedded in a struct to prevent comparability.
|
||||
type nonComparable [0]func()
|
418
vendor/github.com/go-json-experiment/json/errors.go
generated
vendored
Normal file
418
vendor/github.com/go-json-experiment/json/errors.go
generated
vendored
Normal file
@ -0,0 +1,418 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/go-json-experiment/json/internal/jsonflags"
|
||||
"github.com/go-json-experiment/json/internal/jsonopts"
|
||||
"github.com/go-json-experiment/json/internal/jsonwire"
|
||||
"github.com/go-json-experiment/json/jsontext"
|
||||
)
|
||||
|
||||
// ErrUnknownName indicates that a JSON object member could not be
|
||||
// unmarshaled because the name is not known to the target Go struct.
|
||||
// This error is directly wrapped within a [SemanticError] when produced.
|
||||
//
|
||||
// The name of an unknown JSON object member can be extracted as:
|
||||
//
|
||||
// err := ...
|
||||
// var serr json.SemanticError
|
||||
// if errors.As(err, &serr) && serr.Err == json.ErrUnknownName {
|
||||
// ptr := serr.JSONPointer // JSON pointer to unknown name
|
||||
// name := ptr.LastToken() // unknown name itself
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
// This error is only returned if [RejectUnknownMembers] is true.
|
||||
var ErrUnknownName = errors.New("unknown object member name")
|
||||
|
||||
const errorPrefix = "json: "
|
||||
|
||||
func isSemanticError(err error) bool {
|
||||
_, ok := err.(*SemanticError)
|
||||
return ok
|
||||
}
|
||||
|
||||
func isSyntacticError(err error) bool {
|
||||
_, ok := err.(*jsontext.SyntacticError)
|
||||
return ok
|
||||
}
|
||||
|
||||
// isFatalError reports whether this error must terminate asharling.
|
||||
// All errors are considered fatal unless operating under
|
||||
// [jsonflags.ReportErrorsWithLegacySemantics] in which case only
|
||||
// syntactic errors and I/O errors are considered fatal.
|
||||
func isFatalError(err error, flags jsonflags.Flags) bool {
|
||||
return !flags.Get(jsonflags.ReportErrorsWithLegacySemantics) ||
|
||||
isSyntacticError(err) || export.IsIOError(err)
|
||||
}
|
||||
|
||||
// SemanticError describes an error determining the meaning
|
||||
// of JSON data as Go data or vice-versa.
|
||||
//
|
||||
// The contents of this error as produced by this package may change over time.
|
||||
type SemanticError struct {
|
||||
requireKeyedLiterals
|
||||
nonComparable
|
||||
|
||||
action string // either "marshal" or "unmarshal"
|
||||
|
||||
// ByteOffset indicates that an error occurred after this byte offset.
|
||||
ByteOffset int64
|
||||
// JSONPointer indicates that an error occurred within this JSON value
|
||||
// as indicated using the JSON Pointer notation (see RFC 6901).
|
||||
JSONPointer jsontext.Pointer
|
||||
|
||||
// JSONKind is the JSON kind that could not be handled.
|
||||
JSONKind jsontext.Kind // may be zero if unknown
|
||||
// JSONValue is the JSON number or string that could not be unmarshaled.
|
||||
// It is not populated during marshaling.
|
||||
JSONValue jsontext.Value // may be nil if irrelevant or unknown
|
||||
// GoType is the Go type that could not be handled.
|
||||
GoType reflect.Type // may be nil if unknown
|
||||
|
||||
// Err is the underlying error.
|
||||
Err error // may be nil
|
||||
}
|
||||
|
||||
// coder is implemented by [jsontext.Encoder] or [jsontext.Decoder].
|
||||
type coder interface{ StackPointer() jsontext.Pointer }
|
||||
|
||||
// newInvalidFormatError wraps err in a SemanticError because
|
||||
// the current type t cannot handle the provided options format.
|
||||
// This error must be called before producing or consuming the next value.
|
||||
//
|
||||
// If [jsonflags.ReportErrorsWithLegacySemantics] is specified,
|
||||
// then this automatically skips the next value when unmarshaling
|
||||
// to ensure that the value is fully consumed.
|
||||
func newInvalidFormatError(c coder, t reflect.Type, o *jsonopts.Struct) error {
|
||||
err := fmt.Errorf("invalid format flag %q", o.Format)
|
||||
switch c := c.(type) {
|
||||
case *jsontext.Encoder:
|
||||
err = newMarshalErrorBefore(c, t, err)
|
||||
case *jsontext.Decoder:
|
||||
err = newUnmarshalErrorBeforeWithSkipping(c, o, t, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// newMarshalErrorBefore wraps err in a SemanticError assuming that e
|
||||
// is positioned right before the next token or value, which causes an error.
|
||||
func newMarshalErrorBefore(e *jsontext.Encoder, t reflect.Type, err error) error {
|
||||
return &SemanticError{action: "marshal", GoType: t, Err: err,
|
||||
ByteOffset: e.OutputOffset() + int64(export.Encoder(e).CountNextDelimWhitespace()),
|
||||
JSONPointer: jsontext.Pointer(export.Encoder(e).AppendStackPointer(nil, +1))}
|
||||
}
|
||||
|
||||
// newUnmarshalErrorBefore wraps err in a SemanticError assuming that d
|
||||
// is positioned right before the next token or value, which causes an error.
|
||||
// It does not record the next JSON kind as this error is used to indicate
|
||||
// the receiving Go value is invalid to unmarshal into (and not a JSON error).
|
||||
func newUnmarshalErrorBefore(d *jsontext.Decoder, t reflect.Type, err error) error {
|
||||
return &SemanticError{action: "unmarshal", GoType: t, Err: err,
|
||||
ByteOffset: d.InputOffset() + int64(export.Decoder(d).CountNextDelimWhitespace()),
|
||||
JSONPointer: jsontext.Pointer(export.Decoder(d).AppendStackPointer(nil, +1))}
|
||||
}
|
||||
|
||||
// newUnmarshalErrorBeforeWithSkipping is like [newUnmarshalErrorBefore],
|
||||
// but automatically skips the next value if
|
||||
// [jsonflags.ReportErrorsWithLegacySemantics] is specified.
|
||||
func newUnmarshalErrorBeforeWithSkipping(d *jsontext.Decoder, o *jsonopts.Struct, t reflect.Type, err error) error {
|
||||
err = newUnmarshalErrorBefore(d, t, err)
|
||||
if o.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
|
||||
if err2 := export.Decoder(d).SkipValue(); err2 != nil {
|
||||
return err2
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// newUnmarshalErrorAfter wraps err in a SemanticError assuming that d
|
||||
// is positioned right after the previous token or value, which caused an error.
|
||||
func newUnmarshalErrorAfter(d *jsontext.Decoder, t reflect.Type, err error) error {
|
||||
tokOrVal := export.Decoder(d).PreviousTokenOrValue()
|
||||
return &SemanticError{action: "unmarshal", GoType: t, Err: err,
|
||||
ByteOffset: d.InputOffset() - int64(len(tokOrVal)),
|
||||
JSONPointer: jsontext.Pointer(export.Decoder(d).AppendStackPointer(nil, -1)),
|
||||
JSONKind: jsontext.Value(tokOrVal).Kind()}
|
||||
}
|
||||
|
||||
// newUnmarshalErrorAfter wraps err in a SemanticError assuming that d
|
||||
// is positioned right after the previous token or value, which caused an error.
|
||||
// It also stores a copy of the last JSON value if it is a string or number.
|
||||
func newUnmarshalErrorAfterWithValue(d *jsontext.Decoder, t reflect.Type, err error) error {
|
||||
serr := newUnmarshalErrorAfter(d, t, err).(*SemanticError)
|
||||
if serr.JSONKind == '"' || serr.JSONKind == '0' {
|
||||
serr.JSONValue = jsontext.Value(export.Decoder(d).PreviousTokenOrValue()).Clone()
|
||||
}
|
||||
return serr
|
||||
}
|
||||
|
||||
// newUnmarshalErrorAfterWithSkipping is like [newUnmarshalErrorAfter],
|
||||
// but automatically skips the remainder of the current value if
|
||||
// [jsonflags.ReportErrorsWithLegacySemantics] is specified.
|
||||
func newUnmarshalErrorAfterWithSkipping(d *jsontext.Decoder, o *jsonopts.Struct, t reflect.Type, err error) error {
|
||||
err = newUnmarshalErrorAfter(d, t, err)
|
||||
if o.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
|
||||
if err2 := export.Decoder(d).SkipValueRemainder(); err2 != nil {
|
||||
return err2
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// newSemanticErrorWithPosition wraps err in a SemanticError assuming that
|
||||
// the error occurred at the provided depth, and length.
|
||||
// If err is already a SemanticError, then position information is only
|
||||
// injected if it is currently unpopulated.
|
||||
//
|
||||
// If the position is unpopulated, it is ambiguous where the error occurred
|
||||
// in the user code, whether it was before or after the current position.
|
||||
// For the byte offset, we assume that the error occurred before the last read
|
||||
// token or value when decoding, or before the next value when encoding.
|
||||
// For the JSON pointer, we point to the parent object or array unless
|
||||
// we can be certain that it happened with an object member.
|
||||
//
|
||||
// This is used to annotate errors returned by user-provided
|
||||
// v2 MarshalJSON or UnmarshalJSON methods or functions.
|
||||
func newSemanticErrorWithPosition(c coder, t reflect.Type, prevDepth int, prevLength int64, err error) error {
|
||||
serr, _ := err.(*SemanticError)
|
||||
if serr == nil {
|
||||
serr = &SemanticError{Err: err}
|
||||
}
|
||||
var currDepth int
|
||||
var currLength int64
|
||||
var coderState interface{ AppendStackPointer([]byte, int) []byte }
|
||||
var offset int64
|
||||
switch c := c.(type) {
|
||||
case *jsontext.Encoder:
|
||||
e := export.Encoder(c)
|
||||
serr.action = cmp.Or(serr.action, "marshal")
|
||||
currDepth, currLength = e.Tokens.DepthLength()
|
||||
offset = c.OutputOffset() + int64(export.Encoder(c).CountNextDelimWhitespace())
|
||||
coderState = e
|
||||
case *jsontext.Decoder:
|
||||
d := export.Decoder(c)
|
||||
serr.action = cmp.Or(serr.action, "unmarshal")
|
||||
currDepth, currLength = d.Tokens.DepthLength()
|
||||
tokOrVal := d.PreviousTokenOrValue()
|
||||
offset = c.InputOffset() - int64(len(tokOrVal))
|
||||
if (prevDepth == currDepth && prevLength == currLength) || len(tokOrVal) == 0 {
|
||||
// If no Read method was called in the user-defined method or
|
||||
// if the Peek method was called, then use the offset of the next value.
|
||||
offset = c.InputOffset() + int64(export.Decoder(c).CountNextDelimWhitespace())
|
||||
}
|
||||
coderState = d
|
||||
}
|
||||
serr.ByteOffset = cmp.Or(serr.ByteOffset, offset)
|
||||
if serr.JSONPointer == "" {
|
||||
where := 0 // default to ambiguous positioning
|
||||
switch {
|
||||
case prevDepth == currDepth && prevLength+0 == currLength:
|
||||
where = +1
|
||||
case prevDepth == currDepth && prevLength+1 == currLength:
|
||||
where = -1
|
||||
}
|
||||
serr.JSONPointer = jsontext.Pointer(coderState.AppendStackPointer(nil, where))
|
||||
}
|
||||
serr.GoType = cmp.Or(serr.GoType, t)
|
||||
return serr
|
||||
}
|
||||
|
||||
// collapseSemanticErrors collapses double SemanticErrors at the outer levels
|
||||
// into a single SemanticError by preserving the inner error,
|
||||
// but prepending the ByteOffset and JSONPointer with the outer error.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// collapseSemanticErrors(&SemanticError{
|
||||
// ByteOffset: len64(`[0,{"alpha":[0,1,`),
|
||||
// JSONPointer: "/1/alpha/2",
|
||||
// GoType: reflect.TypeFor[outerType](),
|
||||
// Err: &SemanticError{
|
||||
// ByteOffset: len64(`{"foo":"bar","fizz":[0,`),
|
||||
// JSONPointer: "/fizz/1",
|
||||
// GoType: reflect.TypeFor[innerType](),
|
||||
// Err: ...,
|
||||
// },
|
||||
// })
|
||||
//
|
||||
// results in:
|
||||
//
|
||||
// &SemanticError{
|
||||
// ByteOffset: len64(`[0,{"alpha":[0,1,`) + len64(`{"foo":"bar","fizz":[0,`),
|
||||
// JSONPointer: "/1/alpha/2" + "/fizz/1",
|
||||
// GoType: reflect.TypeFor[innerType](),
|
||||
// Err: ...,
|
||||
// }
|
||||
//
|
||||
// This is used to annotate errors returned by user-provided
|
||||
// v1 MarshalJSON or UnmarshalJSON methods with precise position information
|
||||
// if they themselves happened to return a SemanticError.
|
||||
// Since MarshalJSON and UnmarshalJSON are not operating on the root JSON value,
|
||||
// their positioning must be relative to the nested JSON value
|
||||
// returned by UnmarshalJSON or passed to MarshalJSON.
|
||||
// Therefore, we can construct an absolute position by concatenating
|
||||
// the outer with the inner positions.
|
||||
//
|
||||
// Note that we do not use collapseSemanticErrors with user-provided functions
|
||||
// that take in an [jsontext.Encoder] or [jsontext.Decoder] since they contain
|
||||
// methods to report position relative to the root JSON value.
|
||||
// We assume user-constructed errors are correctly precise about position.
|
||||
func collapseSemanticErrors(err error) error {
|
||||
if serr1, ok := err.(*SemanticError); ok {
|
||||
if serr2, ok := serr1.Err.(*SemanticError); ok {
|
||||
serr2.ByteOffset = serr1.ByteOffset + serr2.ByteOffset
|
||||
serr2.JSONPointer = serr1.JSONPointer + serr2.JSONPointer
|
||||
*serr1 = *serr2
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// errorModalVerb is a modal verb like "cannot" or "unable to".
|
||||
//
|
||||
// Once per process, Hyrum-proof the error message by deliberately
|
||||
// switching between equivalent renderings of the same error message.
|
||||
// The randomization is tied to the Hyrum-proofing already applied
|
||||
// on map iteration in Go.
|
||||
var errorModalVerb = sync.OnceValue(func() string {
|
||||
for phrase := range map[string]struct{}{"cannot": {}, "unable to": {}} {
|
||||
return phrase // use whichever phrase we get in the first iteration
|
||||
}
|
||||
return ""
|
||||
})
|
||||
|
||||
func (e *SemanticError) Error() string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString(errorPrefix)
|
||||
sb.WriteString(errorModalVerb())
|
||||
|
||||
// Format action.
|
||||
var preposition string
|
||||
switch e.action {
|
||||
case "marshal":
|
||||
sb.WriteString(" marshal")
|
||||
preposition = " from"
|
||||
case "unmarshal":
|
||||
sb.WriteString(" unmarshal")
|
||||
preposition = " into"
|
||||
default:
|
||||
sb.WriteString(" handle")
|
||||
preposition = " with"
|
||||
}
|
||||
|
||||
// Format JSON kind.
|
||||
switch e.JSONKind {
|
||||
case 'n':
|
||||
sb.WriteString(" JSON null")
|
||||
case 'f', 't':
|
||||
sb.WriteString(" JSON boolean")
|
||||
case '"':
|
||||
sb.WriteString(" JSON string")
|
||||
case '0':
|
||||
sb.WriteString(" JSON number")
|
||||
case '{', '}':
|
||||
sb.WriteString(" JSON object")
|
||||
case '[', ']':
|
||||
sb.WriteString(" JSON array")
|
||||
default:
|
||||
if e.action == "" {
|
||||
preposition = ""
|
||||
}
|
||||
}
|
||||
if len(e.JSONValue) > 0 && len(e.JSONValue) < 100 {
|
||||
sb.WriteByte(' ')
|
||||
sb.Write(e.JSONValue)
|
||||
}
|
||||
|
||||
// Format Go type.
|
||||
if e.GoType != nil {
|
||||
typeString := e.GoType.String()
|
||||
if len(typeString) > 100 {
|
||||
// An excessively long type string most likely occurs for
|
||||
// an anonymous struct declaration with many fields.
|
||||
// Reduce the noise by just printing the kind,
|
||||
// and optionally prepending it with the package name
|
||||
// if the struct happens to include an unexported field.
|
||||
typeString = e.GoType.Kind().String()
|
||||
if e.GoType.Kind() == reflect.Struct && e.GoType.Name() == "" {
|
||||
for i := range e.GoType.NumField() {
|
||||
if pkgPath := e.GoType.Field(i).PkgPath; pkgPath != "" {
|
||||
typeString = pkgPath[strings.LastIndexByte(pkgPath, '/')+len("/"):] + ".struct"
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sb.WriteString(preposition)
|
||||
sb.WriteString(" Go ")
|
||||
sb.WriteString(typeString)
|
||||
}
|
||||
|
||||
// Special handling for unknown names.
|
||||
if e.Err == ErrUnknownName {
|
||||
sb.WriteString(": ")
|
||||
sb.WriteString(ErrUnknownName.Error())
|
||||
sb.WriteString(" ")
|
||||
sb.WriteString(strconv.Quote(e.JSONPointer.LastToken()))
|
||||
if parent := e.JSONPointer.Parent(); parent != "" {
|
||||
sb.WriteString(" within ")
|
||||
sb.WriteString(strconv.Quote(jsonwire.TruncatePointer(string(parent), 100)))
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// Format where.
|
||||
// Avoid printing if it overlaps with a wrapped SyntacticError.
|
||||
switch serr, _ := e.Err.(*jsontext.SyntacticError); {
|
||||
case e.JSONPointer != "":
|
||||
if serr == nil || !e.JSONPointer.Contains(serr.JSONPointer) {
|
||||
sb.WriteString(" within ")
|
||||
sb.WriteString(strconv.Quote(jsonwire.TruncatePointer(string(e.JSONPointer), 100)))
|
||||
}
|
||||
case e.ByteOffset > 0:
|
||||
if serr == nil || !(e.ByteOffset <= serr.ByteOffset) {
|
||||
sb.WriteString(" after offset ")
|
||||
sb.WriteString(strconv.FormatInt(e.ByteOffset, 10))
|
||||
}
|
||||
}
|
||||
|
||||
// Format underlying error.
|
||||
if e.Err != nil {
|
||||
errString := e.Err.Error()
|
||||
if isSyntacticError(e.Err) {
|
||||
errString = strings.TrimPrefix(errString, "jsontext: ")
|
||||
}
|
||||
sb.WriteString(": ")
|
||||
sb.WriteString(errString)
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (e *SemanticError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
func newDuplicateNameError(ptr jsontext.Pointer, quotedName []byte, offset int64) error {
|
||||
if quotedName != nil {
|
||||
name, _ := jsonwire.AppendUnquote(nil, quotedName)
|
||||
ptr = ptr.AppendToken(string(name))
|
||||
}
|
||||
return &jsontext.SyntacticError{
|
||||
ByteOffset: offset,
|
||||
JSONPointer: ptr,
|
||||
Err: jsontext.ErrDuplicateName,
|
||||
}
|
||||
}
|
644
vendor/github.com/go-json-experiment/json/fields.go
generated
vendored
Normal file
644
vendor/github.com/go-json-experiment/json/fields.go
generated
vendored
Normal file
@ -0,0 +1,644 @@
|
||||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/go-json-experiment/json/internal/jsonflags"
|
||||
"github.com/go-json-experiment/json/internal/jsonwire"
|
||||
)
|
||||
|
||||
type isZeroer interface {
|
||||
IsZero() bool
|
||||
}
|
||||
|
||||
var isZeroerType = reflect.TypeFor[isZeroer]()
|
||||
|
||||
type structFields struct {
|
||||
flattened []structField // listed in depth-first ordering
|
||||
byActualName map[string]*structField
|
||||
byFoldedName map[string][]*structField
|
||||
inlinedFallback *structField
|
||||
}
|
||||
|
||||
// reindex recomputes index to avoid bounds check during runtime.
|
||||
//
|
||||
// During the construction of each [structField] in [makeStructFields],
|
||||
// the index field is 0-indexed. However, before it returns,
|
||||
// the 0th field is stored in index0 and index stores the remainder.
|
||||
func (sf *structFields) reindex() {
|
||||
reindex := func(f *structField) {
|
||||
f.index0 = f.index[0]
|
||||
f.index = f.index[1:]
|
||||
if len(f.index) == 0 {
|
||||
f.index = nil // avoid pinning the backing slice
|
||||
}
|
||||
}
|
||||
for i := range sf.flattened {
|
||||
reindex(&sf.flattened[i])
|
||||
}
|
||||
if sf.inlinedFallback != nil {
|
||||
reindex(sf.inlinedFallback)
|
||||
}
|
||||
}
|
||||
|
||||
// lookupByFoldedName looks up name by a case-insensitive match
|
||||
// that also ignores the presence of dashes and underscores.
|
||||
func (fs *structFields) lookupByFoldedName(name []byte) []*structField {
|
||||
return fs.byFoldedName[string(foldName(name))]
|
||||
}
|
||||
|
||||
type structField struct {
|
||||
id int // unique numeric ID in breadth-first ordering
|
||||
index0 int // 0th index into a struct according to [reflect.Type.FieldByIndex]
|
||||
index []int // 1st index and remainder according to [reflect.Type.FieldByIndex]
|
||||
typ reflect.Type
|
||||
fncs *arshaler
|
||||
isZero func(addressableValue) bool
|
||||
isEmpty func(addressableValue) bool
|
||||
fieldOptions
|
||||
}
|
||||
|
||||
var errNoExportedFields = errors.New("Go struct has no exported fields")
|
||||
|
||||
func makeStructFields(root reflect.Type) (fs structFields, serr *SemanticError) {
|
||||
orErrorf := func(serr *SemanticError, t reflect.Type, f string, a ...any) *SemanticError {
|
||||
return cmp.Or(serr, &SemanticError{GoType: t, Err: fmt.Errorf(f, a...)})
|
||||
}
|
||||
|
||||
// Setup a queue for a breath-first search.
|
||||
var queueIndex int
|
||||
type queueEntry struct {
|
||||
typ reflect.Type
|
||||
index []int
|
||||
visitChildren bool // whether to recursively visit inlined field in this struct
|
||||
}
|
||||
queue := []queueEntry{{root, nil, true}}
|
||||
seen := map[reflect.Type]bool{root: true}
|
||||
|
||||
// Perform a breadth-first search over all reachable fields.
|
||||
// This ensures that len(f.index) will be monotonically increasing.
|
||||
var allFields, inlinedFallbacks []structField
|
||||
for queueIndex < len(queue) {
|
||||
qe := queue[queueIndex]
|
||||
queueIndex++
|
||||
|
||||
t := qe.typ
|
||||
inlinedFallbackIndex := -1 // index of last inlined fallback field in current struct
|
||||
namesIndex := make(map[string]int) // index of each field with a given JSON object name in current struct
|
||||
var hasAnyJSONTag bool // whether any Go struct field has a `json` tag
|
||||
var hasAnyJSONField bool // whether any JSON serializable fields exist in current struct
|
||||
for i := range t.NumField() {
|
||||
sf := t.Field(i)
|
||||
_, hasTag := sf.Tag.Lookup("json")
|
||||
hasAnyJSONTag = hasAnyJSONTag || hasTag
|
||||
options, ignored, err := parseFieldOptions(sf)
|
||||
if err != nil {
|
||||
serr = cmp.Or(serr, &SemanticError{GoType: t, Err: err})
|
||||
}
|
||||
if ignored {
|
||||
continue
|
||||
}
|
||||
hasAnyJSONField = true
|
||||
f := structField{
|
||||
// Allocate a new slice (len=N+1) to hold both
|
||||
// the parent index (len=N) and the current index (len=1).
|
||||
// Do this to avoid clobbering the memory of the parent index.
|
||||
index: append(append(make([]int, 0, len(qe.index)+1), qe.index...), i),
|
||||
typ: sf.Type,
|
||||
fieldOptions: options,
|
||||
}
|
||||
if sf.Anonymous && !f.hasName {
|
||||
if indirectType(f.typ).Kind() != reflect.Struct {
|
||||
serr = orErrorf(serr, t, "embedded Go struct field %s of non-struct type must be explicitly given a JSON name", sf.Name)
|
||||
} else {
|
||||
f.inline = true // implied by use of Go embedding without an explicit name
|
||||
}
|
||||
}
|
||||
if f.inline || f.unknown {
|
||||
// Handle an inlined field that serializes to/from
|
||||
// zero or more JSON object members.
|
||||
|
||||
switch f.fieldOptions {
|
||||
case fieldOptions{name: f.name, quotedName: f.quotedName, inline: true}:
|
||||
case fieldOptions{name: f.name, quotedName: f.quotedName, unknown: true}:
|
||||
case fieldOptions{name: f.name, quotedName: f.quotedName, inline: true, unknown: true}:
|
||||
serr = orErrorf(serr, t, "Go struct field %s cannot have both `inline` and `unknown` specified", sf.Name)
|
||||
f.inline = false // let `unknown` take precedence
|
||||
default:
|
||||
serr = orErrorf(serr, t, "Go struct field %s cannot have any options other than `inline` or `unknown` specified", sf.Name)
|
||||
if f.hasName {
|
||||
continue // invalid inlined field; treat as ignored
|
||||
}
|
||||
f.fieldOptions = fieldOptions{name: f.name, quotedName: f.quotedName, inline: f.inline, unknown: f.unknown}
|
||||
if f.inline && f.unknown {
|
||||
f.inline = false // let `unknown` take precedence
|
||||
}
|
||||
}
|
||||
|
||||
// Reject any types with custom serialization otherwise
|
||||
// it becomes impossible to know what sub-fields to inline.
|
||||
tf := indirectType(f.typ)
|
||||
if implementsAny(tf, allMethodTypes...) && tf != jsontextValueType {
|
||||
serr = orErrorf(serr, t, "inlined Go struct field %s of type %s must not implement marshal or unmarshal methods", sf.Name, tf)
|
||||
}
|
||||
|
||||
// Handle an inlined field that serializes to/from
|
||||
// a finite number of JSON object members backed by a Go struct.
|
||||
if tf.Kind() == reflect.Struct {
|
||||
if f.unknown {
|
||||
serr = orErrorf(serr, t, "inlined Go struct field %s of type %s with `unknown` tag must be a Go map of string key or a jsontext.Value", sf.Name, tf)
|
||||
continue // invalid inlined field; treat as ignored
|
||||
}
|
||||
if qe.visitChildren {
|
||||
queue = append(queue, queueEntry{tf, f.index, !seen[tf]})
|
||||
}
|
||||
seen[tf] = true
|
||||
continue
|
||||
} else if !sf.IsExported() {
|
||||
serr = orErrorf(serr, t, "inlined Go struct field %s is not exported", sf.Name)
|
||||
continue // invalid inlined field; treat as ignored
|
||||
}
|
||||
|
||||
// Handle an inlined field that serializes to/from any number of
|
||||
// JSON object members back by a Go map or jsontext.Value.
|
||||
switch {
|
||||
case tf == jsontextValueType:
|
||||
f.fncs = nil // specially handled in arshal_inlined.go
|
||||
case tf.Kind() == reflect.Map && tf.Key().Kind() == reflect.String:
|
||||
if implementsAny(tf.Key(), allMethodTypes...) {
|
||||
serr = orErrorf(serr, t, "inlined map field %s of type %s must have a string key that does not implement marshal or unmarshal methods", sf.Name, tf)
|
||||
continue // invalid inlined field; treat as ignored
|
||||
}
|
||||
f.fncs = lookupArshaler(tf.Elem())
|
||||
default:
|
||||
serr = orErrorf(serr, t, "inlined Go struct field %s of type %s must be a Go struct, Go map of string key, or jsontext.Value", sf.Name, tf)
|
||||
continue // invalid inlined field; treat as ignored
|
||||
}
|
||||
|
||||
// Reject multiple inlined fallback fields within the same struct.
|
||||
if inlinedFallbackIndex >= 0 {
|
||||
serr = orErrorf(serr, t, "inlined Go struct fields %s and %s cannot both be a Go map or jsontext.Value", t.Field(inlinedFallbackIndex).Name, sf.Name)
|
||||
// Still append f to inlinedFallbacks as there is still a
|
||||
// check for a dominant inlined fallback before returning.
|
||||
}
|
||||
inlinedFallbackIndex = i
|
||||
|
||||
inlinedFallbacks = append(inlinedFallbacks, f)
|
||||
} else {
|
||||
// Handle normal Go struct field that serializes to/from
|
||||
// a single JSON object member.
|
||||
|
||||
// Unexported fields cannot be serialized except for
|
||||
// embedded fields of a struct type,
|
||||
// which might promote exported fields of their own.
|
||||
if !sf.IsExported() {
|
||||
tf := indirectType(f.typ)
|
||||
if !(sf.Anonymous && tf.Kind() == reflect.Struct) {
|
||||
serr = orErrorf(serr, t, "Go struct field %s is not exported", sf.Name)
|
||||
continue
|
||||
}
|
||||
// Unfortunately, methods on the unexported field
|
||||
// still cannot be called.
|
||||
if implementsAny(tf, allMethodTypes...) ||
|
||||
(f.omitzero && implementsAny(tf, isZeroerType)) {
|
||||
serr = orErrorf(serr, t, "Go struct field %s is not exported for method calls", sf.Name)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Provide a function that uses a type's IsZero method.
|
||||
switch {
|
||||
case sf.Type.Kind() == reflect.Interface && sf.Type.Implements(isZeroerType):
|
||||
f.isZero = func(va addressableValue) bool {
|
||||
// Avoid panics calling IsZero on a nil interface or
|
||||
// non-nil interface with nil pointer.
|
||||
return va.IsNil() || (va.Elem().Kind() == reflect.Pointer && va.Elem().IsNil()) || va.Interface().(isZeroer).IsZero()
|
||||
}
|
||||
case sf.Type.Kind() == reflect.Pointer && sf.Type.Implements(isZeroerType):
|
||||
f.isZero = func(va addressableValue) bool {
|
||||
// Avoid panics calling IsZero on nil pointer.
|
||||
return va.IsNil() || va.Interface().(isZeroer).IsZero()
|
||||
}
|
||||
case sf.Type.Implements(isZeroerType):
|
||||
f.isZero = func(va addressableValue) bool { return va.Interface().(isZeroer).IsZero() }
|
||||
case reflect.PointerTo(sf.Type).Implements(isZeroerType):
|
||||
f.isZero = func(va addressableValue) bool { return va.Addr().Interface().(isZeroer).IsZero() }
|
||||
}
|
||||
|
||||
// Provide a function that can determine whether the value would
|
||||
// serialize as an empty JSON value.
|
||||
switch sf.Type.Kind() {
|
||||
case reflect.String, reflect.Map, reflect.Array, reflect.Slice:
|
||||
f.isEmpty = func(va addressableValue) bool { return va.Len() == 0 }
|
||||
case reflect.Pointer, reflect.Interface:
|
||||
f.isEmpty = func(va addressableValue) bool { return va.IsNil() }
|
||||
}
|
||||
|
||||
// Reject multiple fields with same name within the same struct.
|
||||
if j, ok := namesIndex[f.name]; ok {
|
||||
serr = orErrorf(serr, t, "Go struct fields %s and %s conflict over JSON object name %q", t.Field(j).Name, sf.Name, f.name)
|
||||
// Still append f to allFields as there is still a
|
||||
// check for a dominant field before returning.
|
||||
}
|
||||
namesIndex[f.name] = i
|
||||
|
||||
f.id = len(allFields)
|
||||
f.fncs = lookupArshaler(sf.Type)
|
||||
allFields = append(allFields, f)
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: New users to the json package are occasionally surprised that
|
||||
// unexported fields are ignored. This occurs by necessity due to our
|
||||
// inability to directly introspect such fields with Go reflection
|
||||
// without the use of unsafe.
|
||||
//
|
||||
// To reduce friction here, refuse to serialize any Go struct that
|
||||
// has no JSON serializable fields, has at least one Go struct field,
|
||||
// and does not have any `json` tags present. For example,
|
||||
// errors returned by errors.New would fail to serialize.
|
||||
isEmptyStruct := t.NumField() == 0
|
||||
if !isEmptyStruct && !hasAnyJSONTag && !hasAnyJSONField {
|
||||
serr = cmp.Or(serr, &SemanticError{GoType: t, Err: errNoExportedFields})
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the fields by exact name (breaking ties by depth and
|
||||
// then by presence of an explicitly provided JSON name).
|
||||
// Select the dominant field from each set of fields with the same name.
|
||||
// If multiple fields have the same name, then the dominant field
|
||||
// is the one that exists alone at the shallowest depth,
|
||||
// or the one that is uniquely tagged with a JSON name.
|
||||
// Otherwise, no dominant field exists for the set.
|
||||
flattened := allFields[:0]
|
||||
slices.SortStableFunc(allFields, func(x, y structField) int {
|
||||
return cmp.Or(
|
||||
strings.Compare(x.name, y.name),
|
||||
cmp.Compare(len(x.index), len(y.index)),
|
||||
boolsCompare(!x.hasName, !y.hasName))
|
||||
})
|
||||
for len(allFields) > 0 {
|
||||
n := 1 // number of fields with the same exact name
|
||||
for n < len(allFields) && allFields[n-1].name == allFields[n].name {
|
||||
n++
|
||||
}
|
||||
if n == 1 || len(allFields[0].index) != len(allFields[1].index) || allFields[0].hasName != allFields[1].hasName {
|
||||
flattened = append(flattened, allFields[0]) // only keep field if there is a dominant field
|
||||
}
|
||||
allFields = allFields[n:]
|
||||
}
|
||||
|
||||
// Sort the fields according to a breadth-first ordering
|
||||
// so that we can re-number IDs with the smallest possible values.
|
||||
// This optimizes use of uintSet such that it fits in the 64-entry bit set.
|
||||
slices.SortFunc(flattened, func(x, y structField) int {
|
||||
return cmp.Compare(x.id, y.id)
|
||||
})
|
||||
for i := range flattened {
|
||||
flattened[i].id = i
|
||||
}
|
||||
|
||||
// Sort the fields according to a depth-first ordering
|
||||
// as the typical order that fields are marshaled.
|
||||
slices.SortFunc(flattened, func(x, y structField) int {
|
||||
return slices.Compare(x.index, y.index)
|
||||
})
|
||||
|
||||
// Compute the mapping of fields in the byActualName map.
|
||||
// Pre-fold all names so that we can lookup folded names quickly.
|
||||
fs = structFields{
|
||||
flattened: flattened,
|
||||
byActualName: make(map[string]*structField, len(flattened)),
|
||||
byFoldedName: make(map[string][]*structField, len(flattened)),
|
||||
}
|
||||
for i, f := range fs.flattened {
|
||||
foldedName := string(foldName([]byte(f.name)))
|
||||
fs.byActualName[f.name] = &fs.flattened[i]
|
||||
fs.byFoldedName[foldedName] = append(fs.byFoldedName[foldedName], &fs.flattened[i])
|
||||
}
|
||||
for foldedName, fields := range fs.byFoldedName {
|
||||
if len(fields) > 1 {
|
||||
// The precedence order for conflicting ignoreCase names
|
||||
// is by breadth-first order, rather than depth-first order.
|
||||
slices.SortFunc(fields, func(x, y *structField) int {
|
||||
return cmp.Compare(x.id, y.id)
|
||||
})
|
||||
fs.byFoldedName[foldedName] = fields
|
||||
}
|
||||
}
|
||||
if n := len(inlinedFallbacks); n == 1 || (n > 1 && len(inlinedFallbacks[0].index) != len(inlinedFallbacks[1].index)) {
|
||||
fs.inlinedFallback = &inlinedFallbacks[0] // dominant inlined fallback field
|
||||
}
|
||||
fs.reindex()
|
||||
return fs, serr
|
||||
}
|
||||
|
||||
// indirectType unwraps one level of pointer indirection
|
||||
// similar to how Go only allows embedding either T or *T,
|
||||
// but not **T or P (which is a named pointer).
|
||||
func indirectType(t reflect.Type) reflect.Type {
|
||||
if t.Kind() == reflect.Pointer && t.Name() == "" {
|
||||
t = t.Elem()
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// matchFoldedName matches a case-insensitive name depending on the options.
|
||||
// It assumes that foldName(f.name) == foldName(name).
|
||||
//
|
||||
// Case-insensitive matching is used if the `case:ignore` tag option is specified
|
||||
// or the MatchCaseInsensitiveNames call option is specified
|
||||
// (and the `case:strict` tag option is not specified).
|
||||
// Functionally, the `case:ignore` and `case:strict` tag options take precedence.
|
||||
//
|
||||
// The v1 definition of case-insensitivity operated under strings.EqualFold
|
||||
// and would strictly compare dashes and underscores,
|
||||
// while the v2 definition would ignore the presence of dashes and underscores.
|
||||
// Thus, if the MatchCaseSensitiveDelimiter call option is specified,
|
||||
// the match is further restricted to using strings.EqualFold.
|
||||
func (f *structField) matchFoldedName(name []byte, flags *jsonflags.Flags) bool {
|
||||
if f.casing == caseIgnore || (flags.Get(jsonflags.MatchCaseInsensitiveNames) && f.casing != caseStrict) {
|
||||
if !flags.Get(jsonflags.MatchCaseSensitiveDelimiter) || strings.EqualFold(string(name), f.name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const (
|
||||
caseIgnore = 1
|
||||
caseStrict = 2
|
||||
)
|
||||
|
||||
type fieldOptions struct {
|
||||
name string
|
||||
quotedName string // quoted name per RFC 8785, section 3.2.2.2.
|
||||
hasName bool
|
||||
nameNeedEscape bool
|
||||
casing int8 // either 0, caseIgnore, or caseStrict
|
||||
inline bool
|
||||
unknown bool
|
||||
omitzero bool
|
||||
omitempty bool
|
||||
string bool
|
||||
format string
|
||||
}
|
||||
|
||||
// parseFieldOptions parses the `json` tag in a Go struct field as
|
||||
// a structured set of options configuring parameters such as
|
||||
// the JSON member name and other features.
|
||||
func parseFieldOptions(sf reflect.StructField) (out fieldOptions, ignored bool, err error) {
|
||||
tag, hasTag := sf.Tag.Lookup("json")
|
||||
|
||||
// Check whether this field is explicitly ignored.
|
||||
if tag == "-" {
|
||||
return fieldOptions{}, true, nil
|
||||
}
|
||||
|
||||
// Check whether this field is unexported and not embedded,
|
||||
// which Go reflection cannot mutate for the sake of serialization.
|
||||
//
|
||||
// An embedded field of an unexported type is still capable of
|
||||
// forwarding exported fields, which may be JSON serialized.
|
||||
// This technically operates on the edge of what is permissible by
|
||||
// the Go language, but the most recent decision is to permit this.
|
||||
//
|
||||
// See https://go.dev/issue/24153 and https://go.dev/issue/32772.
|
||||
if !sf.IsExported() && !sf.Anonymous {
|
||||
// Tag options specified on an unexported field suggests user error.
|
||||
if hasTag {
|
||||
err = cmp.Or(err, fmt.Errorf("unexported Go struct field %s cannot have non-ignored `json:%q` tag", sf.Name, tag))
|
||||
}
|
||||
return fieldOptions{}, true, err
|
||||
}
|
||||
|
||||
// Determine the JSON member name for this Go field. A user-specified name
|
||||
// may be provided as either an identifier or a single-quoted string.
|
||||
// The single-quoted string allows arbitrary characters in the name.
|
||||
// See https://go.dev/issue/2718 and https://go.dev/issue/3546.
|
||||
out.name = sf.Name // always starts with an uppercase character
|
||||
if len(tag) > 0 && !strings.HasPrefix(tag, ",") {
|
||||
// For better compatibility with v1, accept almost any unescaped name.
|
||||
n := len(tag) - len(strings.TrimLeftFunc(tag, func(r rune) bool {
|
||||
return !strings.ContainsRune(",\\'\"`", r) // reserve comma, backslash, and quotes
|
||||
}))
|
||||
name := tag[:n]
|
||||
|
||||
// If the next character is not a comma, then the name is either
|
||||
// malformed (if n > 0) or a single-quoted name.
|
||||
// In either case, call consumeTagOption to handle it further.
|
||||
var err2 error
|
||||
if !strings.HasPrefix(tag[n:], ",") && len(name) != len(tag) {
|
||||
name, n, err2 = consumeTagOption(tag)
|
||||
if err2 != nil {
|
||||
err = cmp.Or(err, fmt.Errorf("Go struct field %s has malformed `json` tag: %v", sf.Name, err2))
|
||||
}
|
||||
}
|
||||
if !utf8.ValidString(name) {
|
||||
err = cmp.Or(err, fmt.Errorf("Go struct field %s has JSON object name %q with invalid UTF-8", sf.Name, name))
|
||||
name = string([]rune(name)) // replace invalid UTF-8 with utf8.RuneError
|
||||
}
|
||||
if err2 == nil {
|
||||
out.hasName = true
|
||||
out.name = name
|
||||
}
|
||||
tag = tag[n:]
|
||||
}
|
||||
b, _ := jsonwire.AppendQuote(nil, out.name, &jsonflags.Flags{})
|
||||
out.quotedName = string(b)
|
||||
out.nameNeedEscape = jsonwire.NeedEscape(out.name)
|
||||
|
||||
// Handle any additional tag options (if any).
|
||||
var wasFormat bool
|
||||
seenOpts := make(map[string]bool)
|
||||
for len(tag) > 0 {
|
||||
// Consume comma delimiter.
|
||||
if tag[0] != ',' {
|
||||
err = cmp.Or(err, fmt.Errorf("Go struct field %s has malformed `json` tag: invalid character %q before next option (expecting ',')", sf.Name, tag[0]))
|
||||
} else {
|
||||
tag = tag[len(","):]
|
||||
if len(tag) == 0 {
|
||||
err = cmp.Or(err, fmt.Errorf("Go struct field %s has malformed `json` tag: invalid trailing ',' character", sf.Name))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Consume and process the tag option.
|
||||
opt, n, err2 := consumeTagOption(tag)
|
||||
if err2 != nil {
|
||||
err = cmp.Or(err, fmt.Errorf("Go struct field %s has malformed `json` tag: %v", sf.Name, err2))
|
||||
}
|
||||
rawOpt := tag[:n]
|
||||
tag = tag[n:]
|
||||
switch {
|
||||
case wasFormat:
|
||||
err = cmp.Or(err, fmt.Errorf("Go struct field %s has `format` tag option that was not specified last", sf.Name))
|
||||
case strings.HasPrefix(rawOpt, "'") && strings.TrimFunc(opt, isLetterOrDigit) == "":
|
||||
err = cmp.Or(err, fmt.Errorf("Go struct field %s has unnecessarily quoted appearance of `%s` tag option; specify `%s` instead", sf.Name, rawOpt, opt))
|
||||
}
|
||||
switch opt {
|
||||
case "case":
|
||||
if !strings.HasPrefix(tag, ":") {
|
||||
err = cmp.Or(err, fmt.Errorf("Go struct field %s is missing value for `case` tag option; specify `case:ignore` or `case:strict` instead", sf.Name))
|
||||
break
|
||||
}
|
||||
tag = tag[len(":"):]
|
||||
opt, n, err2 := consumeTagOption(tag)
|
||||
if err2 != nil {
|
||||
err = cmp.Or(err, fmt.Errorf("Go struct field %s has malformed value for `case` tag option: %v", sf.Name, err2))
|
||||
break
|
||||
}
|
||||
rawOpt := tag[:n]
|
||||
tag = tag[n:]
|
||||
if strings.HasPrefix(rawOpt, "'") {
|
||||
err = cmp.Or(err, fmt.Errorf("Go struct field %s has unnecessarily quoted appearance of `case:%s` tag option; specify `case:%s` instead", sf.Name, rawOpt, opt))
|
||||
}
|
||||
switch opt {
|
||||
case "ignore":
|
||||
out.casing |= caseIgnore
|
||||
case "strict":
|
||||
out.casing |= caseStrict
|
||||
default:
|
||||
err = cmp.Or(err, fmt.Errorf("Go struct field %s has unknown `case:%s` tag value", sf.Name, rawOpt))
|
||||
}
|
||||
case "inline":
|
||||
out.inline = true
|
||||
case "unknown":
|
||||
out.unknown = true
|
||||
case "omitzero":
|
||||
out.omitzero = true
|
||||
case "omitempty":
|
||||
out.omitempty = true
|
||||
case "string":
|
||||
out.string = true
|
||||
case "format":
|
||||
if !strings.HasPrefix(tag, ":") {
|
||||
err = cmp.Or(err, fmt.Errorf("Go struct field %s is missing value for `format` tag option", sf.Name))
|
||||
break
|
||||
}
|
||||
tag = tag[len(":"):]
|
||||
opt, n, err2 := consumeTagOption(tag)
|
||||
if err2 != nil {
|
||||
err = cmp.Or(err, fmt.Errorf("Go struct field %s has malformed value for `format` tag option: %v", sf.Name, err2))
|
||||
break
|
||||
}
|
||||
tag = tag[n:]
|
||||
out.format = opt
|
||||
wasFormat = true
|
||||
default:
|
||||
// Reject keys that resemble one of the supported options.
|
||||
// This catches invalid mutants such as "omitEmpty" or "omit_empty".
|
||||
normOpt := strings.ReplaceAll(strings.ToLower(opt), "_", "")
|
||||
switch normOpt {
|
||||
case "case", "inline", "unknown", "omitzero", "omitempty", "string", "format":
|
||||
err = cmp.Or(err, fmt.Errorf("Go struct field %s has invalid appearance of `%s` tag option; specify `%s` instead", sf.Name, opt, normOpt))
|
||||
}
|
||||
|
||||
// NOTE: Everything else is ignored. This does not mean it is
|
||||
// forward compatible to insert arbitrary tag options since
|
||||
// a future version of this package may understand that tag.
|
||||
}
|
||||
|
||||
// Reject duplicates.
|
||||
switch {
|
||||
case out.casing == caseIgnore|caseStrict:
|
||||
err = cmp.Or(err, fmt.Errorf("Go struct field %s cannot have both `case:ignore` and `case:strict` tag options", sf.Name))
|
||||
case seenOpts[opt]:
|
||||
err = cmp.Or(err, fmt.Errorf("Go struct field %s has duplicate appearance of `%s` tag option", sf.Name, rawOpt))
|
||||
}
|
||||
seenOpts[opt] = true
|
||||
}
|
||||
return out, false, err
|
||||
}
|
||||
|
||||
// consumeTagOption consumes the next option,
|
||||
// which is either a Go identifier or a single-quoted string.
|
||||
// If the next option is invalid, it returns all of in until the next comma,
|
||||
// and reports an error.
|
||||
func consumeTagOption(in string) (string, int, error) {
|
||||
// For legacy compatibility with v1, assume options are comma-separated.
|
||||
i := strings.IndexByte(in, ',')
|
||||
if i < 0 {
|
||||
i = len(in)
|
||||
}
|
||||
|
||||
switch r, _ := utf8.DecodeRuneInString(in); {
|
||||
// Option as a Go identifier.
|
||||
case r == '_' || unicode.IsLetter(r):
|
||||
n := len(in) - len(strings.TrimLeftFunc(in, isLetterOrDigit))
|
||||
return in[:n], n, nil
|
||||
// Option as a single-quoted string.
|
||||
case r == '\'':
|
||||
// The grammar is nearly identical to a double-quoted Go string literal,
|
||||
// but uses single quotes as the terminators. The reason for a custom
|
||||
// grammar is because both backtick and double quotes cannot be used
|
||||
// verbatim in a struct tag.
|
||||
//
|
||||
// Convert a single-quoted string to a double-quote string and rely on
|
||||
// strconv.Unquote to handle the rest.
|
||||
var inEscape bool
|
||||
b := []byte{'"'}
|
||||
n := len(`'`)
|
||||
for len(in) > n {
|
||||
r, rn := utf8.DecodeRuneInString(in[n:])
|
||||
switch {
|
||||
case inEscape:
|
||||
if r == '\'' {
|
||||
b = b[:len(b)-1] // remove escape character: `\'` => `'`
|
||||
}
|
||||
inEscape = false
|
||||
case r == '\\':
|
||||
inEscape = true
|
||||
case r == '"':
|
||||
b = append(b, '\\') // insert escape character: `"` => `\"`
|
||||
case r == '\'':
|
||||
b = append(b, '"')
|
||||
n += len(`'`)
|
||||
out, err := strconv.Unquote(string(b))
|
||||
if err != nil {
|
||||
return in[:i], i, fmt.Errorf("invalid single-quoted string: %s", in[:n])
|
||||
}
|
||||
return out, n, nil
|
||||
}
|
||||
b = append(b, in[n:][:rn]...)
|
||||
n += rn
|
||||
}
|
||||
if n > 10 {
|
||||
n = 10 // limit the amount of context printed in the error
|
||||
}
|
||||
return in[:i], i, fmt.Errorf("single-quoted string not terminated: %s...", in[:n])
|
||||
case len(in) == 0:
|
||||
return in[:i], i, io.ErrUnexpectedEOF
|
||||
default:
|
||||
return in[:i], i, fmt.Errorf("invalid character %q at start of option (expecting Unicode letter or single quote)", r)
|
||||
}
|
||||
}
|
||||
|
||||
func isLetterOrDigit(r rune) bool {
|
||||
return r == '_' || unicode.IsLetter(r) || unicode.IsNumber(r)
|
||||
}
|
||||
|
||||
// boolsCompare compares x and y, ordering false before true.
|
||||
func boolsCompare(x, y bool) int {
|
||||
switch {
|
||||
case !x && y:
|
||||
return -1
|
||||
default:
|
||||
return 0
|
||||
case x && !y:
|
||||
return +1
|
||||
}
|
||||
}
|
56
vendor/github.com/go-json-experiment/json/fold.go
generated
vendored
Normal file
56
vendor/github.com/go-json-experiment/json/fold.go
generated
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// foldName returns a folded string such that foldName(x) == foldName(y)
|
||||
// is similar to strings.EqualFold(x, y), but ignores underscore and dashes.
|
||||
// This allows foldName to match common naming conventions.
|
||||
func foldName(in []byte) []byte {
|
||||
// This is inlinable to take advantage of "function outlining".
|
||||
// See https://blog.filippo.io/efficient-go-apis-with-the-inliner/
|
||||
var arr [32]byte // large enough for most JSON names
|
||||
return appendFoldedName(arr[:0], in)
|
||||
}
|
||||
func appendFoldedName(out, in []byte) []byte {
|
||||
for i := 0; i < len(in); {
|
||||
// Handle single-byte ASCII.
|
||||
if c := in[i]; c < utf8.RuneSelf {
|
||||
if c != '_' && c != '-' {
|
||||
if 'a' <= c && c <= 'z' {
|
||||
c -= 'a' - 'A'
|
||||
}
|
||||
out = append(out, c)
|
||||
}
|
||||
i++
|
||||
continue
|
||||
}
|
||||
// Handle multi-byte Unicode.
|
||||
r, n := utf8.DecodeRune(in[i:])
|
||||
out = utf8.AppendRune(out, foldRune(r))
|
||||
i += n
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// foldRune is a variation on unicode.SimpleFold that returns the same rune
|
||||
// for all runes in the same fold set.
|
||||
//
|
||||
// Invariant:
|
||||
//
|
||||
// foldRune(x) == foldRune(y) ⇔ strings.EqualFold(string(x), string(y))
|
||||
func foldRune(r rune) rune {
|
||||
for {
|
||||
r2 := unicode.SimpleFold(r)
|
||||
if r2 <= r {
|
||||
return r2 // smallest character in the fold set
|
||||
}
|
||||
r = r2
|
||||
}
|
||||
}
|
86
vendor/github.com/go-json-experiment/json/intern.go
generated
vendored
Normal file
86
vendor/github.com/go-json-experiment/json/intern.go
generated
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"math/bits"
|
||||
)
|
||||
|
||||
// stringCache is a cache for strings converted from a []byte.
|
||||
type stringCache = [256]string // 256*unsafe.Sizeof(string("")) => 4KiB
|
||||
|
||||
// makeString returns the string form of b.
|
||||
// It returns a pre-allocated string from c if present, otherwise
|
||||
// it allocates a new string, inserts it into the cache, and returns it.
|
||||
func makeString(c *stringCache, b []byte) string {
|
||||
const (
|
||||
minCachedLen = 2 // single byte strings are already interned by the runtime
|
||||
maxCachedLen = 256 // large enough for UUIDs, IPv6 addresses, SHA-256 checksums, etc.
|
||||
)
|
||||
if c == nil || len(b) < minCachedLen || len(b) > maxCachedLen {
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// Compute a hash from the fixed-width prefix and suffix of the string.
|
||||
// This ensures hashing a string is a constant time operation.
|
||||
var h uint32
|
||||
switch {
|
||||
case len(b) >= 8:
|
||||
lo := binary.LittleEndian.Uint64(b[:8])
|
||||
hi := binary.LittleEndian.Uint64(b[len(b)-8:])
|
||||
h = hash64(uint32(lo), uint32(lo>>32)) ^ hash64(uint32(hi), uint32(hi>>32))
|
||||
case len(b) >= 4:
|
||||
lo := binary.LittleEndian.Uint32(b[:4])
|
||||
hi := binary.LittleEndian.Uint32(b[len(b)-4:])
|
||||
h = hash64(lo, hi)
|
||||
case len(b) >= 2:
|
||||
lo := binary.LittleEndian.Uint16(b[:2])
|
||||
hi := binary.LittleEndian.Uint16(b[len(b)-2:])
|
||||
h = hash64(uint32(lo), uint32(hi))
|
||||
}
|
||||
|
||||
// Check the cache for the string.
|
||||
i := h % uint32(len(*c))
|
||||
if s := (*c)[i]; s == string(b) {
|
||||
return s
|
||||
}
|
||||
s := string(b)
|
||||
(*c)[i] = s
|
||||
return s
|
||||
}
|
||||
|
||||
// hash64 returns the hash of two uint32s as a single uint32.
|
||||
func hash64(lo, hi uint32) uint32 {
|
||||
// If avalanche=true, this is identical to XXH32 hash on a 8B string:
|
||||
// var b [8]byte
|
||||
// binary.LittleEndian.PutUint32(b[:4], lo)
|
||||
// binary.LittleEndian.PutUint32(b[4:], hi)
|
||||
// return xxhash.Sum32(b[:])
|
||||
const (
|
||||
prime1 = 0x9e3779b1
|
||||
prime2 = 0x85ebca77
|
||||
prime3 = 0xc2b2ae3d
|
||||
prime4 = 0x27d4eb2f
|
||||
prime5 = 0x165667b1
|
||||
)
|
||||
h := prime5 + uint32(8)
|
||||
h += lo * prime3
|
||||
h = bits.RotateLeft32(h, 17) * prime4
|
||||
h += hi * prime3
|
||||
h = bits.RotateLeft32(h, 17) * prime4
|
||||
// Skip final mix (avalanche) step of XXH32 for performance reasons.
|
||||
// Empirical testing shows that the improvements in unbiased distribution
|
||||
// does not outweigh the extra cost in computational complexity.
|
||||
const avalanche = false
|
||||
if avalanche {
|
||||
h ^= h >> 15
|
||||
h *= prime2
|
||||
h ^= h >> 13
|
||||
h *= prime3
|
||||
h ^= h >> 16
|
||||
}
|
||||
return h
|
||||
}
|
39
vendor/github.com/go-json-experiment/json/internal/internal.go
generated
vendored
Normal file
39
vendor/github.com/go-json-experiment/json/internal/internal.go
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright 2023 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package internal
|
||||
|
||||
import "errors"
|
||||
|
||||
// NotForPublicUse is a marker type that an API is for internal use only.
|
||||
// It does not perfectly prevent usage of that API, but helps to restrict usage.
|
||||
// Anything with this marker is not covered by the Go compatibility agreement.
|
||||
type NotForPublicUse struct{}
|
||||
|
||||
// AllowInternalUse is passed from "json" to "jsontext" to authenticate
|
||||
// that the caller can have access to internal functionality.
|
||||
var AllowInternalUse NotForPublicUse
|
||||
|
||||
// Sentinel error values internally shared between jsonv1 and jsonv2.
|
||||
var (
|
||||
ErrCycle = errors.New("encountered a cycle")
|
||||
ErrNonNilReference = errors.New("value must be passed as a non-nil pointer reference")
|
||||
)
|
||||
|
||||
var (
|
||||
// TransformMarshalError converts a v2 error into a v1 error.
|
||||
// It is called only at the top-level of a Marshal function.
|
||||
TransformMarshalError func(any, error) error
|
||||
// NewMarshalerError constructs a jsonv1.MarshalerError.
|
||||
// It is called after a user-defined Marshal method/function fails.
|
||||
NewMarshalerError func(any, error, string) error
|
||||
// TransformUnmarshalError converts a v2 error into a v1 error.
|
||||
// It is called only at the top-level of a Unmarshal function.
|
||||
TransformUnmarshalError func(any, error) error
|
||||
|
||||
// NewRawNumber returns new(jsonv1.Number).
|
||||
NewRawNumber func() any
|
||||
// RawNumberOf returns jsonv1.Number(b).
|
||||
RawNumberOf func(b []byte) any
|
||||
)
|
203
vendor/github.com/go-json-experiment/json/internal/jsonflags/flags.go
generated
vendored
Normal file
203
vendor/github.com/go-json-experiment/json/internal/jsonflags/flags.go
generated
vendored
Normal file
@ -0,0 +1,203 @@
|
||||
// Copyright 2023 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// jsonflags implements all the optional boolean flags.
|
||||
// These flags are shared across both "json", "jsontext", and "jsonopts".
|
||||
package jsonflags
|
||||
|
||||
import "github.com/go-json-experiment/json/internal"
|
||||
|
||||
// Bools represents zero or more boolean flags, all set to true or false.
|
||||
// The least-significant bit is the boolean value of all flags in the set.
|
||||
// The remaining bits identify which particular flags.
|
||||
//
|
||||
// In common usage, this is OR'd with 0 or 1. For example:
|
||||
// - (AllowInvalidUTF8 | 0) means "AllowInvalidUTF8 is false"
|
||||
// - (Multiline | Indent | 1) means "Multiline and Indent are true"
|
||||
type Bools uint64
|
||||
|
||||
func (Bools) JSONOptions(internal.NotForPublicUse) {}
|
||||
|
||||
const (
|
||||
// AllFlags is the set of all flags.
|
||||
AllFlags = AllCoderFlags | AllArshalV2Flags | AllArshalV1Flags
|
||||
|
||||
// AllCoderFlags is the set of all encoder/decoder flags.
|
||||
AllCoderFlags = (maxCoderFlag - 1) - initFlag
|
||||
|
||||
// AllArshalV2Flags is the set of all v2 marshal/unmarshal flags.
|
||||
AllArshalV2Flags = (maxArshalV2Flag - 1) - (maxCoderFlag - 1)
|
||||
|
||||
// AllArshalV1Flags is the set of all v1 marshal/unmarshal flags.
|
||||
AllArshalV1Flags = (maxArshalV1Flag - 1) - (maxArshalV2Flag - 1)
|
||||
|
||||
// NonBooleanFlags is the set of non-boolean flags,
|
||||
// where the value is some other concrete Go type.
|
||||
// The value of the flag is stored within jsonopts.Struct.
|
||||
NonBooleanFlags = 0 |
|
||||
Indent |
|
||||
IndentPrefix |
|
||||
ByteLimit |
|
||||
DepthLimit |
|
||||
Marshalers |
|
||||
Unmarshalers
|
||||
|
||||
// DefaultV1Flags is the set of booleans flags that default to true under
|
||||
// v1 semantics. None of the non-boolean flags differ between v1 and v2.
|
||||
DefaultV1Flags = 0 |
|
||||
AllowDuplicateNames |
|
||||
AllowInvalidUTF8 |
|
||||
EscapeForHTML |
|
||||
EscapeForJS |
|
||||
EscapeInvalidUTF8 |
|
||||
PreserveRawStrings |
|
||||
Deterministic |
|
||||
FormatNilMapAsNull |
|
||||
FormatNilSliceAsNull |
|
||||
MatchCaseInsensitiveNames |
|
||||
CallMethodsWithLegacySemantics |
|
||||
FormatBytesWithLegacySemantics |
|
||||
FormatTimeWithLegacySemantics |
|
||||
MatchCaseSensitiveDelimiter |
|
||||
MergeWithLegacySemantics |
|
||||
OmitEmptyWithLegacyDefinition |
|
||||
ReportErrorsWithLegacySemantics |
|
||||
StringifyWithLegacySemantics |
|
||||
UnmarshalArrayFromAnyLength
|
||||
|
||||
// AnyWhitespace reports whether the encoded output might have any whitespace.
|
||||
AnyWhitespace = Multiline | SpaceAfterColon | SpaceAfterComma
|
||||
|
||||
// WhitespaceFlags is the set of flags related to whitespace formatting.
|
||||
// In contrast to AnyWhitespace, this includes Indent and IndentPrefix
|
||||
// as those settings take no effect if Multiline is false.
|
||||
WhitespaceFlags = AnyWhitespace | Indent | IndentPrefix
|
||||
|
||||
// AnyEscape is the set of flags related to escaping in a JSON string.
|
||||
AnyEscape = EscapeForHTML | EscapeForJS | EscapeInvalidUTF8
|
||||
|
||||
// CanonicalizeNumbers is the set of flags related to raw number canonicalization.
|
||||
CanonicalizeNumbers = CanonicalizeRawInts | CanonicalizeRawFloats
|
||||
)
|
||||
|
||||
// Encoder and decoder flags.
|
||||
const (
|
||||
initFlag Bools = 1 << iota // reserved for the boolean value itself
|
||||
|
||||
AllowDuplicateNames // encode or decode
|
||||
AllowInvalidUTF8 // encode or decode
|
||||
WithinArshalCall // encode or decode; for internal use by json.Marshal and json.Unmarshal
|
||||
OmitTopLevelNewline // encode only; for internal use by json.Marshal and json.MarshalWrite
|
||||
PreserveRawStrings // encode only
|
||||
CanonicalizeRawInts // encode only
|
||||
CanonicalizeRawFloats // encode only
|
||||
ReorderRawObjects // encode only
|
||||
EscapeForHTML // encode only
|
||||
EscapeForJS // encode only
|
||||
EscapeInvalidUTF8 // encode only; only exposed in v1
|
||||
Multiline // encode only
|
||||
SpaceAfterColon // encode only
|
||||
SpaceAfterComma // encode only
|
||||
Indent // encode only; non-boolean flag
|
||||
IndentPrefix // encode only; non-boolean flag
|
||||
ByteLimit // encode or decode; non-boolean flag
|
||||
DepthLimit // encode or decode; non-boolean flag
|
||||
|
||||
maxCoderFlag
|
||||
)
|
||||
|
||||
// Marshal and Unmarshal flags (for v2).
|
||||
const (
|
||||
_ Bools = (maxCoderFlag >> 1) << iota
|
||||
|
||||
StringifyNumbers // marshal or unmarshal
|
||||
Deterministic // marshal only
|
||||
FormatNilMapAsNull // marshal only
|
||||
FormatNilSliceAsNull // marshal only
|
||||
OmitZeroStructFields // marshal only
|
||||
MatchCaseInsensitiveNames // marshal or unmarshal
|
||||
DiscardUnknownMembers // marshal only
|
||||
RejectUnknownMembers // unmarshal only
|
||||
Marshalers // marshal only; non-boolean flag
|
||||
Unmarshalers // unmarshal only; non-boolean flag
|
||||
|
||||
maxArshalV2Flag
|
||||
)
|
||||
|
||||
// Marshal and Unmarshal flags (for v1).
|
||||
const (
|
||||
_ Bools = (maxArshalV2Flag >> 1) << iota
|
||||
|
||||
CallMethodsWithLegacySemantics // marshal or unmarshal
|
||||
FormatBytesWithLegacySemantics // marshal or unmarshal
|
||||
FormatTimeWithLegacySemantics // marshal or unmarshal
|
||||
MatchCaseSensitiveDelimiter // marshal or unmarshal
|
||||
MergeWithLegacySemantics // unmarshal
|
||||
OmitEmptyWithLegacyDefinition // marshal
|
||||
ReportErrorsWithLegacySemantics // marshal or unmarshal
|
||||
StringifyWithLegacySemantics // marshal or unmarshal
|
||||
StringifyBoolsAndStrings // marshal or unmarshal; for internal use by jsonv2.makeStructArshaler
|
||||
UnmarshalAnyWithRawNumber // unmarshal; for internal use by jsonv1.Decoder.UseNumber
|
||||
UnmarshalArrayFromAnyLength // unmarshal
|
||||
|
||||
maxArshalV1Flag
|
||||
)
|
||||
|
||||
// Flags is a set of boolean flags.
|
||||
// If the presence bit is zero, then the value bit must also be zero.
|
||||
// The least-significant bit of both fields is always zero.
|
||||
//
|
||||
// Unlike Bools, which can represent a set of bools that are all true or false,
|
||||
// Flags represents a set of bools, each individually may be true or false.
|
||||
type Flags struct{ Presence, Values uint64 }
|
||||
|
||||
// Join joins two sets of flags such that the latter takes precedence.
|
||||
func (dst *Flags) Join(src Flags) {
|
||||
// Copy over all source presence bits over to the destination (using OR),
|
||||
// then invert the source presence bits to clear out source value (using AND-NOT),
|
||||
// then copy over source value bits over to the destination (using OR).
|
||||
// e.g., dst := Flags{Presence: 0b_1100_0011, Value: 0b_1000_0011}
|
||||
// e.g., src := Flags{Presence: 0b_0101_1010, Value: 0b_1001_0010}
|
||||
dst.Presence |= src.Presence // e.g., 0b_1100_0011 | 0b_0101_1010 -> 0b_110_11011
|
||||
dst.Values &= ^src.Presence // e.g., 0b_1000_0011 & 0b_1010_0101 -> 0b_100_00001
|
||||
dst.Values |= src.Values // e.g., 0b_1000_0001 | 0b_1001_0010 -> 0b_100_10011
|
||||
}
|
||||
|
||||
// Set sets both the presence and value for the provided bool (or set of bools).
|
||||
func (fs *Flags) Set(f Bools) {
|
||||
// Select out the bits for the flag identifiers (everything except LSB),
|
||||
// then set the presence for all the identifier bits (using OR),
|
||||
// then invert the identifier bits to clear out the values (using AND-NOT),
|
||||
// then copy over all the identifier bits to the value if LSB is 1.
|
||||
// e.g., fs := Flags{Presence: 0b_0101_0010, Value: 0b_0001_0010}
|
||||
// e.g., f := 0b_1001_0001
|
||||
id := uint64(f) &^ uint64(1) // e.g., 0b_1001_0001 & 0b_1111_1110 -> 0b_1001_0000
|
||||
fs.Presence |= id // e.g., 0b_0101_0010 | 0b_1001_0000 -> 0b_1101_0011
|
||||
fs.Values &= ^id // e.g., 0b_0001_0010 & 0b_0110_1111 -> 0b_0000_0010
|
||||
fs.Values |= uint64(f&1) * id // e.g., 0b_0000_0010 | 0b_1001_0000 -> 0b_1001_0010
|
||||
}
|
||||
|
||||
// Get reports whether the bool (or any of the bools) is true.
|
||||
// This is generally only used with a singular bool.
|
||||
// The value bit of f (i.e., the LSB) is ignored.
|
||||
func (fs Flags) Get(f Bools) bool {
|
||||
return fs.Values&uint64(f) > 0
|
||||
}
|
||||
|
||||
// Has reports whether the bool (or any of the bools) is set.
|
||||
// The value bit of f (i.e., the LSB) is ignored.
|
||||
func (fs Flags) Has(f Bools) bool {
|
||||
return fs.Presence&uint64(f) > 0
|
||||
}
|
||||
|
||||
// Clear clears both the presence and value for the provided bool or bools.
|
||||
// The value bit of f (i.e., the LSB) is ignored.
|
||||
func (fs *Flags) Clear(f Bools) {
|
||||
// Invert f to produce a mask to clear all bits in f (using AND).
|
||||
// e.g., fs := Flags{Presence: 0b_0101_0010, Value: 0b_0001_0010}
|
||||
// e.g., f := 0b_0001_1000
|
||||
mask := uint64(^f) // e.g., 0b_0001_1000 -> 0b_1110_0111
|
||||
fs.Presence &= mask // e.g., 0b_0101_0010 & 0b_1110_0111 -> 0b_0100_0010
|
||||
fs.Values &= mask // e.g., 0b_0001_0010 & 0b_1110_0111 -> 0b_0000_0010
|
||||
}
|
200
vendor/github.com/go-json-experiment/json/internal/jsonopts/options.go
generated
vendored
Normal file
200
vendor/github.com/go-json-experiment/json/internal/jsonopts/options.go
generated
vendored
Normal file
@ -0,0 +1,200 @@
|
||||
// Copyright 2023 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package jsonopts
|
||||
|
||||
import (
|
||||
"github.com/go-json-experiment/json/internal"
|
||||
"github.com/go-json-experiment/json/internal/jsonflags"
|
||||
)
|
||||
|
||||
// Options is the common options type shared across json packages.
|
||||
type Options interface {
|
||||
// JSONOptions is exported so related json packages can implement Options.
|
||||
JSONOptions(internal.NotForPublicUse)
|
||||
}
|
||||
|
||||
// Struct is the combination of all options in struct form.
|
||||
// This is efficient to pass down the call stack and to query.
|
||||
type Struct struct {
|
||||
Flags jsonflags.Flags
|
||||
|
||||
CoderValues
|
||||
ArshalValues
|
||||
}
|
||||
|
||||
type CoderValues struct {
|
||||
Indent string // jsonflags.Indent
|
||||
IndentPrefix string // jsonflags.IndentPrefix
|
||||
ByteLimit int64 // jsonflags.ByteLimit
|
||||
DepthLimit int // jsonflags.DepthLimit
|
||||
}
|
||||
|
||||
type ArshalValues struct {
|
||||
// The Marshalers and Unmarshalers fields use the any type to avoid a
|
||||
// concrete dependency on *json.Marshalers and *json.Unmarshalers,
|
||||
// which would in turn create a dependency on the "reflect" package.
|
||||
|
||||
Marshalers any // jsonflags.Marshalers
|
||||
Unmarshalers any // jsonflags.Unmarshalers
|
||||
|
||||
Format string
|
||||
FormatDepth int
|
||||
}
|
||||
|
||||
// DefaultOptionsV2 is the set of all options that define default v2 behavior.
|
||||
var DefaultOptionsV2 = Struct{
|
||||
Flags: jsonflags.Flags{
|
||||
Presence: uint64(jsonflags.AllFlags & ^jsonflags.WhitespaceFlags),
|
||||
Values: uint64(0),
|
||||
},
|
||||
}
|
||||
|
||||
// DefaultOptionsV1 is the set of all options that define default v1 behavior.
|
||||
var DefaultOptionsV1 = Struct{
|
||||
Flags: jsonflags.Flags{
|
||||
Presence: uint64(jsonflags.AllFlags & ^jsonflags.WhitespaceFlags),
|
||||
Values: uint64(jsonflags.DefaultV1Flags),
|
||||
},
|
||||
}
|
||||
|
||||
func (*Struct) JSONOptions(internal.NotForPublicUse) {}
|
||||
|
||||
// GetUnknownOption is injected by the "json" package to handle Options
|
||||
// declared in that package so that "jsonopts" can handle them.
|
||||
var GetUnknownOption = func(*Struct, Options) (any, bool) { panic("unknown option") }
|
||||
|
||||
func GetOption[T any](opts Options, setter func(T) Options) (T, bool) {
|
||||
// Collapse the options to *Struct to simplify lookup.
|
||||
structOpts, ok := opts.(*Struct)
|
||||
if !ok {
|
||||
var structOpts2 Struct
|
||||
structOpts2.Join(opts)
|
||||
structOpts = &structOpts2
|
||||
}
|
||||
|
||||
// Lookup the option based on the return value of the setter.
|
||||
var zero T
|
||||
switch opt := setter(zero).(type) {
|
||||
case jsonflags.Bools:
|
||||
v := structOpts.Flags.Get(opt)
|
||||
ok := structOpts.Flags.Has(opt)
|
||||
return any(v).(T), ok
|
||||
case Indent:
|
||||
if !structOpts.Flags.Has(jsonflags.Indent) {
|
||||
return zero, false
|
||||
}
|
||||
return any(structOpts.Indent).(T), true
|
||||
case IndentPrefix:
|
||||
if !structOpts.Flags.Has(jsonflags.IndentPrefix) {
|
||||
return zero, false
|
||||
}
|
||||
return any(structOpts.IndentPrefix).(T), true
|
||||
case ByteLimit:
|
||||
if !structOpts.Flags.Has(jsonflags.ByteLimit) {
|
||||
return zero, false
|
||||
}
|
||||
return any(structOpts.ByteLimit).(T), true
|
||||
case DepthLimit:
|
||||
if !structOpts.Flags.Has(jsonflags.DepthLimit) {
|
||||
return zero, false
|
||||
}
|
||||
return any(structOpts.DepthLimit).(T), true
|
||||
default:
|
||||
v, ok := GetUnknownOption(structOpts, opt)
|
||||
return v.(T), ok
|
||||
}
|
||||
}
|
||||
|
||||
// JoinUnknownOption is injected by the "json" package to handle Options
|
||||
// declared in that package so that "jsonopts" can handle them.
|
||||
var JoinUnknownOption = func(*Struct, Options) { panic("unknown option") }
|
||||
|
||||
func (dst *Struct) Join(srcs ...Options) {
|
||||
dst.join(false, srcs...)
|
||||
}
|
||||
|
||||
func (dst *Struct) JoinWithoutCoderOptions(srcs ...Options) {
|
||||
dst.join(true, srcs...)
|
||||
}
|
||||
|
||||
func (dst *Struct) join(excludeCoderOptions bool, srcs ...Options) {
|
||||
for _, src := range srcs {
|
||||
switch src := src.(type) {
|
||||
case nil:
|
||||
continue
|
||||
case jsonflags.Bools:
|
||||
if excludeCoderOptions {
|
||||
src &= ^jsonflags.AllCoderFlags
|
||||
}
|
||||
dst.Flags.Set(src)
|
||||
case Indent:
|
||||
if excludeCoderOptions {
|
||||
continue
|
||||
}
|
||||
dst.Flags.Set(jsonflags.Multiline | jsonflags.Indent | 1)
|
||||
dst.Indent = string(src)
|
||||
case IndentPrefix:
|
||||
if excludeCoderOptions {
|
||||
continue
|
||||
}
|
||||
dst.Flags.Set(jsonflags.Multiline | jsonflags.IndentPrefix | 1)
|
||||
dst.IndentPrefix = string(src)
|
||||
case ByteLimit:
|
||||
if excludeCoderOptions {
|
||||
continue
|
||||
}
|
||||
dst.Flags.Set(jsonflags.ByteLimit | 1)
|
||||
dst.ByteLimit = int64(src)
|
||||
case DepthLimit:
|
||||
if excludeCoderOptions {
|
||||
continue
|
||||
}
|
||||
dst.Flags.Set(jsonflags.DepthLimit | 1)
|
||||
dst.DepthLimit = int(src)
|
||||
case *Struct:
|
||||
srcFlags := src.Flags // shallow copy the flags
|
||||
if excludeCoderOptions {
|
||||
srcFlags.Clear(jsonflags.AllCoderFlags)
|
||||
}
|
||||
dst.Flags.Join(srcFlags)
|
||||
if srcFlags.Has(jsonflags.NonBooleanFlags) {
|
||||
if srcFlags.Has(jsonflags.Indent) {
|
||||
dst.Indent = src.Indent
|
||||
}
|
||||
if srcFlags.Has(jsonflags.IndentPrefix) {
|
||||
dst.IndentPrefix = src.IndentPrefix
|
||||
}
|
||||
if srcFlags.Has(jsonflags.ByteLimit) {
|
||||
dst.ByteLimit = src.ByteLimit
|
||||
}
|
||||
if srcFlags.Has(jsonflags.DepthLimit) {
|
||||
dst.DepthLimit = src.DepthLimit
|
||||
}
|
||||
if srcFlags.Has(jsonflags.Marshalers) {
|
||||
dst.Marshalers = src.Marshalers
|
||||
}
|
||||
if srcFlags.Has(jsonflags.Unmarshalers) {
|
||||
dst.Unmarshalers = src.Unmarshalers
|
||||
}
|
||||
}
|
||||
default:
|
||||
JoinUnknownOption(dst, src)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type (
|
||||
Indent string // jsontext.WithIndent
|
||||
IndentPrefix string // jsontext.WithIndentPrefix
|
||||
ByteLimit int64 // jsontext.WithByteLimit
|
||||
DepthLimit int // jsontext.WithDepthLimit
|
||||
// type for jsonflags.Marshalers declared in "json" package
|
||||
// type for jsonflags.Unmarshalers declared in "json" package
|
||||
)
|
||||
|
||||
func (Indent) JSONOptions(internal.NotForPublicUse) {}
|
||||
func (IndentPrefix) JSONOptions(internal.NotForPublicUse) {}
|
||||
func (ByteLimit) JSONOptions(internal.NotForPublicUse) {}
|
||||
func (DepthLimit) JSONOptions(internal.NotForPublicUse) {}
|
627
vendor/github.com/go-json-experiment/json/internal/jsonwire/decode.go
generated
vendored
Normal file
627
vendor/github.com/go-json-experiment/json/internal/jsonwire/decode.go
generated
vendored
Normal file
@ -0,0 +1,627 @@
|
||||
// Copyright 2023 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package jsonwire
|
||||
|
||||
import (
|
||||
"io"
|
||||
"math"
|
||||
"slices"
|
||||
"strconv"
|
||||
"unicode/utf16"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type ValueFlags uint
|
||||
|
||||
const (
|
||||
_ ValueFlags = (1 << iota) / 2 // powers of two starting with zero
|
||||
|
||||
stringNonVerbatim // string cannot be naively treated as valid UTF-8
|
||||
stringNonCanonical // string not formatted according to RFC 8785, section 3.2.2.2.
|
||||
// TODO: Track whether a number is a non-integer?
|
||||
)
|
||||
|
||||
func (f *ValueFlags) Join(f2 ValueFlags) { *f |= f2 }
|
||||
func (f ValueFlags) IsVerbatim() bool { return f&stringNonVerbatim == 0 }
|
||||
func (f ValueFlags) IsCanonical() bool { return f&stringNonCanonical == 0 }
|
||||
|
||||
// ConsumeWhitespace consumes leading JSON whitespace per RFC 7159, section 2.
|
||||
func ConsumeWhitespace(b []byte) (n int) {
|
||||
// NOTE: The arguments and logic are kept simple to keep this inlinable.
|
||||
for len(b) > n && (b[n] == ' ' || b[n] == '\t' || b[n] == '\r' || b[n] == '\n') {
|
||||
n++
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// ConsumeNull consumes the next JSON null literal per RFC 7159, section 3.
|
||||
// It returns 0 if it is invalid, in which case consumeLiteral should be used.
|
||||
func ConsumeNull(b []byte) int {
|
||||
// NOTE: The arguments and logic are kept simple to keep this inlinable.
|
||||
const literal = "null"
|
||||
if len(b) >= len(literal) && string(b[:len(literal)]) == literal {
|
||||
return len(literal)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ConsumeFalse consumes the next JSON false literal per RFC 7159, section 3.
|
||||
// It returns 0 if it is invalid, in which case consumeLiteral should be used.
|
||||
func ConsumeFalse(b []byte) int {
|
||||
// NOTE: The arguments and logic are kept simple to keep this inlinable.
|
||||
const literal = "false"
|
||||
if len(b) >= len(literal) && string(b[:len(literal)]) == literal {
|
||||
return len(literal)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ConsumeTrue consumes the next JSON true literal per RFC 7159, section 3.
|
||||
// It returns 0 if it is invalid, in which case consumeLiteral should be used.
|
||||
func ConsumeTrue(b []byte) int {
|
||||
// NOTE: The arguments and logic are kept simple to keep this inlinable.
|
||||
const literal = "true"
|
||||
if len(b) >= len(literal) && string(b[:len(literal)]) == literal {
|
||||
return len(literal)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ConsumeLiteral consumes the next JSON literal per RFC 7159, section 3.
|
||||
// If the input appears truncated, it returns io.ErrUnexpectedEOF.
|
||||
func ConsumeLiteral(b []byte, lit string) (n int, err error) {
|
||||
for i := 0; i < len(b) && i < len(lit); i++ {
|
||||
if b[i] != lit[i] {
|
||||
return i, NewInvalidCharacterError(b[i:], "in literal "+lit+" (expecting "+strconv.QuoteRune(rune(lit[i]))+")")
|
||||
}
|
||||
}
|
||||
if len(b) < len(lit) {
|
||||
return len(b), io.ErrUnexpectedEOF
|
||||
}
|
||||
return len(lit), nil
|
||||
}
|
||||
|
||||
// ConsumeSimpleString consumes the next JSON string per RFC 7159, section 7
|
||||
// but is limited to the grammar for an ASCII string without escape sequences.
|
||||
// It returns 0 if it is invalid or more complicated than a simple string,
|
||||
// in which case consumeString should be called.
|
||||
//
|
||||
// It rejects '<', '>', and '&' for compatibility reasons since these were
|
||||
// always escaped in the v1 implementation. Thus, if this function reports
|
||||
// non-zero then we know that the string would be encoded the same way
|
||||
// under both v1 or v2 escape semantics.
|
||||
func ConsumeSimpleString(b []byte) (n int) {
|
||||
// NOTE: The arguments and logic are kept simple to keep this inlinable.
|
||||
if len(b) > 0 && b[0] == '"' {
|
||||
n++
|
||||
for len(b) > n && b[n] < utf8.RuneSelf && escapeASCII[b[n]] == 0 {
|
||||
n++
|
||||
}
|
||||
if uint(len(b)) > uint(n) && b[n] == '"' {
|
||||
n++
|
||||
return n
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ConsumeString consumes the next JSON string per RFC 7159, section 7.
|
||||
// If validateUTF8 is false, then this allows the presence of invalid UTF-8
|
||||
// characters within the string itself.
|
||||
// It reports the number of bytes consumed and whether an error was encountered.
|
||||
// If the input appears truncated, it returns io.ErrUnexpectedEOF.
|
||||
func ConsumeString(flags *ValueFlags, b []byte, validateUTF8 bool) (n int, err error) {
|
||||
return ConsumeStringResumable(flags, b, 0, validateUTF8)
|
||||
}
|
||||
|
||||
// ConsumeStringResumable is identical to consumeString but supports resuming
|
||||
// from a previous call that returned io.ErrUnexpectedEOF.
|
||||
func ConsumeStringResumable(flags *ValueFlags, b []byte, resumeOffset int, validateUTF8 bool) (n int, err error) {
|
||||
// Consume the leading double quote.
|
||||
switch {
|
||||
case resumeOffset > 0:
|
||||
n = resumeOffset // already handled the leading quote
|
||||
case uint(len(b)) == 0:
|
||||
return n, io.ErrUnexpectedEOF
|
||||
case b[0] == '"':
|
||||
n++
|
||||
default:
|
||||
return n, NewInvalidCharacterError(b[n:], `at start of string (expecting '"')`)
|
||||
}
|
||||
|
||||
// Consume every character in the string.
|
||||
for uint(len(b)) > uint(n) {
|
||||
// Optimize for long sequences of unescaped characters.
|
||||
noEscape := func(c byte) bool {
|
||||
return c < utf8.RuneSelf && ' ' <= c && c != '\\' && c != '"'
|
||||
}
|
||||
for uint(len(b)) > uint(n) && noEscape(b[n]) {
|
||||
n++
|
||||
}
|
||||
if uint(len(b)) <= uint(n) {
|
||||
return n, io.ErrUnexpectedEOF
|
||||
}
|
||||
|
||||
// Check for terminating double quote.
|
||||
if b[n] == '"' {
|
||||
n++
|
||||
return n, nil
|
||||
}
|
||||
|
||||
switch r, rn := utf8.DecodeRune(b[n:]); {
|
||||
// Handle UTF-8 encoded byte sequence.
|
||||
// Due to specialized handling of ASCII above, we know that
|
||||
// all normal sequences at this point must be 2 bytes or larger.
|
||||
case rn > 1:
|
||||
n += rn
|
||||
// Handle escape sequence.
|
||||
case r == '\\':
|
||||
flags.Join(stringNonVerbatim)
|
||||
resumeOffset = n
|
||||
if uint(len(b)) < uint(n+2) {
|
||||
return resumeOffset, io.ErrUnexpectedEOF
|
||||
}
|
||||
switch r := b[n+1]; r {
|
||||
case '/':
|
||||
// Forward slash is the only character with 3 representations.
|
||||
// Per RFC 8785, section 3.2.2.2., this must not be escaped.
|
||||
flags.Join(stringNonCanonical)
|
||||
n += 2
|
||||
case '"', '\\', 'b', 'f', 'n', 'r', 't':
|
||||
n += 2
|
||||
case 'u':
|
||||
if uint(len(b)) < uint(n+6) {
|
||||
if hasEscapedUTF16Prefix(b[n:], false) {
|
||||
return resumeOffset, io.ErrUnexpectedEOF
|
||||
}
|
||||
flags.Join(stringNonCanonical)
|
||||
return n, NewInvalidEscapeSequenceError(b[n:])
|
||||
}
|
||||
v1, ok := parseHexUint16(b[n+2 : n+6])
|
||||
if !ok {
|
||||
flags.Join(stringNonCanonical)
|
||||
return n, NewInvalidEscapeSequenceError(b[n : n+6])
|
||||
}
|
||||
// Only certain control characters can use the \uFFFF notation
|
||||
// for canonical formatting (per RFC 8785, section 3.2.2.2.).
|
||||
switch v1 {
|
||||
// \uFFFF notation not permitted for these characters.
|
||||
case '\b', '\f', '\n', '\r', '\t':
|
||||
flags.Join(stringNonCanonical)
|
||||
default:
|
||||
// \uFFFF notation only permitted for control characters.
|
||||
if v1 >= ' ' {
|
||||
flags.Join(stringNonCanonical)
|
||||
} else {
|
||||
// \uFFFF notation must be lower case.
|
||||
for _, c := range b[n+2 : n+6] {
|
||||
if 'A' <= c && c <= 'F' {
|
||||
flags.Join(stringNonCanonical)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
n += 6
|
||||
|
||||
r := rune(v1)
|
||||
if validateUTF8 && utf16.IsSurrogate(r) {
|
||||
if uint(len(b)) < uint(n+6) {
|
||||
if hasEscapedUTF16Prefix(b[n:], true) {
|
||||
return resumeOffset, io.ErrUnexpectedEOF
|
||||
}
|
||||
flags.Join(stringNonCanonical)
|
||||
return n - 6, NewInvalidEscapeSequenceError(b[n-6:])
|
||||
} else if v2, ok := parseHexUint16(b[n+2 : n+6]); b[n] != '\\' || b[n+1] != 'u' || !ok {
|
||||
flags.Join(stringNonCanonical)
|
||||
return n - 6, NewInvalidEscapeSequenceError(b[n-6 : n+6])
|
||||
} else if r = utf16.DecodeRune(rune(v1), rune(v2)); r == utf8.RuneError {
|
||||
flags.Join(stringNonCanonical)
|
||||
return n - 6, NewInvalidEscapeSequenceError(b[n-6 : n+6])
|
||||
} else {
|
||||
n += 6
|
||||
}
|
||||
}
|
||||
default:
|
||||
flags.Join(stringNonCanonical)
|
||||
return n, NewInvalidEscapeSequenceError(b[n : n+2])
|
||||
}
|
||||
// Handle invalid UTF-8.
|
||||
case r == utf8.RuneError:
|
||||
if !utf8.FullRune(b[n:]) {
|
||||
return n, io.ErrUnexpectedEOF
|
||||
}
|
||||
flags.Join(stringNonVerbatim | stringNonCanonical)
|
||||
if validateUTF8 {
|
||||
return n, ErrInvalidUTF8
|
||||
}
|
||||
n++
|
||||
// Handle invalid control characters.
|
||||
case r < ' ':
|
||||
flags.Join(stringNonVerbatim | stringNonCanonical)
|
||||
return n, NewInvalidCharacterError(b[n:], "in string (expecting non-control character)")
|
||||
default:
|
||||
panic("BUG: unhandled character " + QuoteRune(b[n:]))
|
||||
}
|
||||
}
|
||||
return n, io.ErrUnexpectedEOF
|
||||
}
|
||||
|
||||
// AppendUnquote appends the unescaped form of a JSON string in src to dst.
|
||||
// Any invalid UTF-8 within the string will be replaced with utf8.RuneError,
|
||||
// but the error will be specified as having encountered such an error.
|
||||
// The input must be an entire JSON string with no surrounding whitespace.
|
||||
func AppendUnquote[Bytes ~[]byte | ~string](dst []byte, src Bytes) (v []byte, err error) {
|
||||
dst = slices.Grow(dst, len(src))
|
||||
|
||||
// Consume the leading double quote.
|
||||
var i, n int
|
||||
switch {
|
||||
case uint(len(src)) == 0:
|
||||
return dst, io.ErrUnexpectedEOF
|
||||
case src[0] == '"':
|
||||
i, n = 1, 1
|
||||
default:
|
||||
return dst, NewInvalidCharacterError(src, `at start of string (expecting '"')`)
|
||||
}
|
||||
|
||||
// Consume every character in the string.
|
||||
for uint(len(src)) > uint(n) {
|
||||
// Optimize for long sequences of unescaped characters.
|
||||
noEscape := func(c byte) bool {
|
||||
return c < utf8.RuneSelf && ' ' <= c && c != '\\' && c != '"'
|
||||
}
|
||||
for uint(len(src)) > uint(n) && noEscape(src[n]) {
|
||||
n++
|
||||
}
|
||||
if uint(len(src)) <= uint(n) {
|
||||
dst = append(dst, src[i:n]...)
|
||||
return dst, io.ErrUnexpectedEOF
|
||||
}
|
||||
|
||||
// Check for terminating double quote.
|
||||
if src[n] == '"' {
|
||||
dst = append(dst, src[i:n]...)
|
||||
n++
|
||||
if n < len(src) {
|
||||
err = NewInvalidCharacterError(src[n:], "after string value")
|
||||
}
|
||||
return dst, err
|
||||
}
|
||||
|
||||
switch r, rn := utf8.DecodeRuneInString(string(truncateMaxUTF8(src[n:]))); {
|
||||
// Handle UTF-8 encoded byte sequence.
|
||||
// Due to specialized handling of ASCII above, we know that
|
||||
// all normal sequences at this point must be 2 bytes or larger.
|
||||
case rn > 1:
|
||||
n += rn
|
||||
// Handle escape sequence.
|
||||
case r == '\\':
|
||||
dst = append(dst, src[i:n]...)
|
||||
|
||||
// Handle escape sequence.
|
||||
if uint(len(src)) < uint(n+2) {
|
||||
return dst, io.ErrUnexpectedEOF
|
||||
}
|
||||
switch r := src[n+1]; r {
|
||||
case '"', '\\', '/':
|
||||
dst = append(dst, r)
|
||||
n += 2
|
||||
case 'b':
|
||||
dst = append(dst, '\b')
|
||||
n += 2
|
||||
case 'f':
|
||||
dst = append(dst, '\f')
|
||||
n += 2
|
||||
case 'n':
|
||||
dst = append(dst, '\n')
|
||||
n += 2
|
||||
case 'r':
|
||||
dst = append(dst, '\r')
|
||||
n += 2
|
||||
case 't':
|
||||
dst = append(dst, '\t')
|
||||
n += 2
|
||||
case 'u':
|
||||
if uint(len(src)) < uint(n+6) {
|
||||
if hasEscapedUTF16Prefix(src[n:], false) {
|
||||
return dst, io.ErrUnexpectedEOF
|
||||
}
|
||||
return dst, NewInvalidEscapeSequenceError(src[n:])
|
||||
}
|
||||
v1, ok := parseHexUint16(src[n+2 : n+6])
|
||||
if !ok {
|
||||
return dst, NewInvalidEscapeSequenceError(src[n : n+6])
|
||||
}
|
||||
n += 6
|
||||
|
||||
// Check whether this is a surrogate half.
|
||||
r := rune(v1)
|
||||
if utf16.IsSurrogate(r) {
|
||||
r = utf8.RuneError // assume failure unless the following succeeds
|
||||
if uint(len(src)) < uint(n+6) {
|
||||
if hasEscapedUTF16Prefix(src[n:], true) {
|
||||
return utf8.AppendRune(dst, r), io.ErrUnexpectedEOF
|
||||
}
|
||||
err = NewInvalidEscapeSequenceError(src[n-6:])
|
||||
} else if v2, ok := parseHexUint16(src[n+2 : n+6]); src[n] != '\\' || src[n+1] != 'u' || !ok {
|
||||
err = NewInvalidEscapeSequenceError(src[n-6 : n+6])
|
||||
} else if r = utf16.DecodeRune(rune(v1), rune(v2)); r == utf8.RuneError {
|
||||
err = NewInvalidEscapeSequenceError(src[n-6 : n+6])
|
||||
} else {
|
||||
n += 6
|
||||
}
|
||||
}
|
||||
|
||||
dst = utf8.AppendRune(dst, r)
|
||||
default:
|
||||
return dst, NewInvalidEscapeSequenceError(src[n : n+2])
|
||||
}
|
||||
i = n
|
||||
// Handle invalid UTF-8.
|
||||
case r == utf8.RuneError:
|
||||
dst = append(dst, src[i:n]...)
|
||||
if !utf8.FullRuneInString(string(truncateMaxUTF8(src[n:]))) {
|
||||
return dst, io.ErrUnexpectedEOF
|
||||
}
|
||||
// NOTE: An unescaped string may be longer than the escaped string
|
||||
// because invalid UTF-8 bytes are being replaced.
|
||||
dst = append(dst, "\uFFFD"...)
|
||||
n += rn
|
||||
i = n
|
||||
err = ErrInvalidUTF8
|
||||
// Handle invalid control characters.
|
||||
case r < ' ':
|
||||
dst = append(dst, src[i:n]...)
|
||||
return dst, NewInvalidCharacterError(src[n:], "in string (expecting non-control character)")
|
||||
default:
|
||||
panic("BUG: unhandled character " + QuoteRune(src[n:]))
|
||||
}
|
||||
}
|
||||
dst = append(dst, src[i:n]...)
|
||||
return dst, io.ErrUnexpectedEOF
|
||||
}
|
||||
|
||||
// hasEscapedUTF16Prefix reports whether b is possibly
|
||||
// the truncated prefix of a \uFFFF escape sequence.
|
||||
func hasEscapedUTF16Prefix[Bytes ~[]byte | ~string](b Bytes, lowerSurrogateHalf bool) bool {
|
||||
for i := range len(b) {
|
||||
switch c := b[i]; {
|
||||
case i == 0 && c != '\\':
|
||||
return false
|
||||
case i == 1 && c != 'u':
|
||||
return false
|
||||
case i == 2 && lowerSurrogateHalf && c != 'd' && c != 'D':
|
||||
return false // not within ['\uDC00':'\uDFFF']
|
||||
case i == 3 && lowerSurrogateHalf && !('c' <= c && c <= 'f') && !('C' <= c && c <= 'F'):
|
||||
return false // not within ['\uDC00':'\uDFFF']
|
||||
case i >= 2 && i < 6 && !('0' <= c && c <= '9') && !('a' <= c && c <= 'f') && !('A' <= c && c <= 'F'):
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// UnquoteMayCopy returns the unescaped form of b.
|
||||
// If there are no escaped characters, the output is simply a subslice of
|
||||
// the input with the surrounding quotes removed.
|
||||
// Otherwise, a new buffer is allocated for the output.
|
||||
// It assumes the input is valid.
|
||||
func UnquoteMayCopy(b []byte, isVerbatim bool) []byte {
|
||||
// NOTE: The arguments and logic are kept simple to keep this inlinable.
|
||||
if isVerbatim {
|
||||
return b[len(`"`) : len(b)-len(`"`)]
|
||||
}
|
||||
b, _ = AppendUnquote(nil, b)
|
||||
return b
|
||||
}
|
||||
|
||||
// ConsumeSimpleNumber consumes the next JSON number per RFC 7159, section 6
|
||||
// but is limited to the grammar for a positive integer.
|
||||
// It returns 0 if it is invalid or more complicated than a simple integer,
|
||||
// in which case consumeNumber should be called.
|
||||
func ConsumeSimpleNumber(b []byte) (n int) {
|
||||
// NOTE: The arguments and logic are kept simple to keep this inlinable.
|
||||
if len(b) > 0 {
|
||||
if b[0] == '0' {
|
||||
n++
|
||||
} else if '1' <= b[0] && b[0] <= '9' {
|
||||
n++
|
||||
for len(b) > n && ('0' <= b[n] && b[n] <= '9') {
|
||||
n++
|
||||
}
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
if uint(len(b)) <= uint(n) || (b[n] != '.' && b[n] != 'e' && b[n] != 'E') {
|
||||
return n
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type ConsumeNumberState uint
|
||||
|
||||
const (
|
||||
consumeNumberInit ConsumeNumberState = iota
|
||||
beforeIntegerDigits
|
||||
withinIntegerDigits
|
||||
beforeFractionalDigits
|
||||
withinFractionalDigits
|
||||
beforeExponentDigits
|
||||
withinExponentDigits
|
||||
)
|
||||
|
||||
// ConsumeNumber consumes the next JSON number per RFC 7159, section 6.
|
||||
// It reports the number of bytes consumed and whether an error was encountered.
|
||||
// If the input appears truncated, it returns io.ErrUnexpectedEOF.
|
||||
//
|
||||
// Note that JSON numbers are not self-terminating.
|
||||
// If the entire input is consumed, then the caller needs to consider whether
|
||||
// there may be subsequent unread data that may still be part of this number.
|
||||
func ConsumeNumber(b []byte) (n int, err error) {
|
||||
n, _, err = ConsumeNumberResumable(b, 0, consumeNumberInit)
|
||||
return n, err
|
||||
}
|
||||
|
||||
// ConsumeNumberResumable is identical to consumeNumber but supports resuming
|
||||
// from a previous call that returned io.ErrUnexpectedEOF.
|
||||
func ConsumeNumberResumable(b []byte, resumeOffset int, state ConsumeNumberState) (n int, _ ConsumeNumberState, err error) {
|
||||
// Jump to the right state when resuming from a partial consumption.
|
||||
n = resumeOffset
|
||||
if state > consumeNumberInit {
|
||||
switch state {
|
||||
case withinIntegerDigits, withinFractionalDigits, withinExponentDigits:
|
||||
// Consume leading digits.
|
||||
for uint(len(b)) > uint(n) && ('0' <= b[n] && b[n] <= '9') {
|
||||
n++
|
||||
}
|
||||
if uint(len(b)) <= uint(n) {
|
||||
return n, state, nil // still within the same state
|
||||
}
|
||||
state++ // switches "withinX" to "beforeY" where Y is the state after X
|
||||
}
|
||||
switch state {
|
||||
case beforeIntegerDigits:
|
||||
goto beforeInteger
|
||||
case beforeFractionalDigits:
|
||||
goto beforeFractional
|
||||
case beforeExponentDigits:
|
||||
goto beforeExponent
|
||||
default:
|
||||
return n, state, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Consume required integer component (with optional minus sign).
|
||||
beforeInteger:
|
||||
resumeOffset = n
|
||||
if uint(len(b)) > 0 && b[0] == '-' {
|
||||
n++
|
||||
}
|
||||
switch {
|
||||
case uint(len(b)) <= uint(n):
|
||||
return resumeOffset, beforeIntegerDigits, io.ErrUnexpectedEOF
|
||||
case b[n] == '0':
|
||||
n++
|
||||
state = beforeFractionalDigits
|
||||
case '1' <= b[n] && b[n] <= '9':
|
||||
n++
|
||||
for uint(len(b)) > uint(n) && ('0' <= b[n] && b[n] <= '9') {
|
||||
n++
|
||||
}
|
||||
state = withinIntegerDigits
|
||||
default:
|
||||
return n, state, NewInvalidCharacterError(b[n:], "in number (expecting digit)")
|
||||
}
|
||||
|
||||
// Consume optional fractional component.
|
||||
beforeFractional:
|
||||
if uint(len(b)) > uint(n) && b[n] == '.' {
|
||||
resumeOffset = n
|
||||
n++
|
||||
switch {
|
||||
case uint(len(b)) <= uint(n):
|
||||
return resumeOffset, beforeFractionalDigits, io.ErrUnexpectedEOF
|
||||
case '0' <= b[n] && b[n] <= '9':
|
||||
n++
|
||||
default:
|
||||
return n, state, NewInvalidCharacterError(b[n:], "in number (expecting digit)")
|
||||
}
|
||||
for uint(len(b)) > uint(n) && ('0' <= b[n] && b[n] <= '9') {
|
||||
n++
|
||||
}
|
||||
state = withinFractionalDigits
|
||||
}
|
||||
|
||||
// Consume optional exponent component.
|
||||
beforeExponent:
|
||||
if uint(len(b)) > uint(n) && (b[n] == 'e' || b[n] == 'E') {
|
||||
resumeOffset = n
|
||||
n++
|
||||
if uint(len(b)) > uint(n) && (b[n] == '-' || b[n] == '+') {
|
||||
n++
|
||||
}
|
||||
switch {
|
||||
case uint(len(b)) <= uint(n):
|
||||
return resumeOffset, beforeExponentDigits, io.ErrUnexpectedEOF
|
||||
case '0' <= b[n] && b[n] <= '9':
|
||||
n++
|
||||
default:
|
||||
return n, state, NewInvalidCharacterError(b[n:], "in number (expecting digit)")
|
||||
}
|
||||
for uint(len(b)) > uint(n) && ('0' <= b[n] && b[n] <= '9') {
|
||||
n++
|
||||
}
|
||||
state = withinExponentDigits
|
||||
}
|
||||
|
||||
return n, state, nil
|
||||
}
|
||||
|
||||
// parseHexUint16 is similar to strconv.ParseUint,
|
||||
// but operates directly on []byte and is optimized for base-16.
|
||||
// See https://go.dev/issue/42429.
|
||||
func parseHexUint16[Bytes ~[]byte | ~string](b Bytes) (v uint16, ok bool) {
|
||||
if len(b) != 4 {
|
||||
return 0, false
|
||||
}
|
||||
for i := range 4 {
|
||||
c := b[i]
|
||||
switch {
|
||||
case '0' <= c && c <= '9':
|
||||
c = c - '0'
|
||||
case 'a' <= c && c <= 'f':
|
||||
c = 10 + c - 'a'
|
||||
case 'A' <= c && c <= 'F':
|
||||
c = 10 + c - 'A'
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
v = v*16 + uint16(c)
|
||||
}
|
||||
return v, true
|
||||
}
|
||||
|
||||
// ParseUint parses b as a decimal unsigned integer according to
|
||||
// a strict subset of the JSON number grammar, returning the value if valid.
|
||||
// It returns (0, false) if there is a syntax error and
|
||||
// returns (math.MaxUint64, false) if there is an overflow.
|
||||
func ParseUint(b []byte) (v uint64, ok bool) {
|
||||
const unsafeWidth = 20 // len(fmt.Sprint(uint64(math.MaxUint64)))
|
||||
var n int
|
||||
for ; len(b) > n && ('0' <= b[n] && b[n] <= '9'); n++ {
|
||||
v = 10*v + uint64(b[n]-'0')
|
||||
}
|
||||
switch {
|
||||
case n == 0 || len(b) != n || (b[0] == '0' && string(b) != "0"):
|
||||
return 0, false
|
||||
case n >= unsafeWidth && (b[0] != '1' || v < 1e19 || n > unsafeWidth):
|
||||
return math.MaxUint64, false
|
||||
}
|
||||
return v, true
|
||||
}
|
||||
|
||||
// ParseFloat parses a floating point number according to the Go float grammar.
|
||||
// Note that the JSON number grammar is a strict subset.
|
||||
//
|
||||
// If the number overflows the finite representation of a float,
|
||||
// then we return MaxFloat since any finite value will always be infinitely
|
||||
// more accurate at representing another finite value than an infinite value.
|
||||
func ParseFloat(b []byte, bits int) (v float64, ok bool) {
|
||||
fv, err := strconv.ParseFloat(string(b), bits)
|
||||
if math.IsInf(fv, 0) {
|
||||
switch {
|
||||
case bits == 32 && math.IsInf(fv, +1):
|
||||
fv = +math.MaxFloat32
|
||||
case bits == 64 && math.IsInf(fv, +1):
|
||||
fv = +math.MaxFloat64
|
||||
case bits == 32 && math.IsInf(fv, -1):
|
||||
fv = -math.MaxFloat32
|
||||
case bits == 64 && math.IsInf(fv, -1):
|
||||
fv = -math.MaxFloat64
|
||||
}
|
||||
}
|
||||
return fv, err == nil
|
||||
}
|
292
vendor/github.com/go-json-experiment/json/internal/jsonwire/encode.go
generated
vendored
Normal file
292
vendor/github.com/go-json-experiment/json/internal/jsonwire/encode.go
generated
vendored
Normal file
@ -0,0 +1,292 @@
|
||||
// Copyright 2023 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package jsonwire
|
||||
|
||||
import (
|
||||
"math"
|
||||
"slices"
|
||||
"strconv"
|
||||
"unicode/utf16"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/go-json-experiment/json/internal/jsonflags"
|
||||
)
|
||||
|
||||
// escapeASCII reports whether the ASCII character needs to be escaped.
|
||||
// It conservatively assumes EscapeForHTML.
|
||||
var escapeASCII = [...]uint8{
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // escape control characters
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // escape control characters
|
||||
0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, // escape '"' and '&'
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, // escape '<' and '>'
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, // escape '\\'
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
}
|
||||
|
||||
// NeedEscape reports whether src needs escaping of any characters.
|
||||
// It conservatively assumes EscapeForHTML and EscapeForJS.
|
||||
// It reports true for inputs with invalid UTF-8.
|
||||
func NeedEscape[Bytes ~[]byte | ~string](src Bytes) bool {
|
||||
var i int
|
||||
for uint(len(src)) > uint(i) {
|
||||
if c := src[i]; c < utf8.RuneSelf {
|
||||
if escapeASCII[c] > 0 {
|
||||
return true
|
||||
}
|
||||
i++
|
||||
} else {
|
||||
r, rn := utf8.DecodeRuneInString(string(truncateMaxUTF8(src[i:])))
|
||||
if r == utf8.RuneError || r == '\u2028' || r == '\u2029' {
|
||||
return true
|
||||
}
|
||||
i += rn
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AppendQuote appends src to dst as a JSON string per RFC 7159, section 7.
|
||||
//
|
||||
// It takes in flags and respects the following:
|
||||
// - EscapeForHTML escapes '<', '>', and '&'.
|
||||
// - EscapeForJS escapes '\u2028' and '\u2029'.
|
||||
// - AllowInvalidUTF8 avoids reporting an error for invalid UTF-8.
|
||||
//
|
||||
// Regardless of whether AllowInvalidUTF8 is specified,
|
||||
// invalid bytes are replaced with the Unicode replacement character ('\ufffd').
|
||||
// If no escape flags are set, then the shortest representable form is used,
|
||||
// which is also the canonical form for strings (RFC 8785, section 3.2.2.2).
|
||||
func AppendQuote[Bytes ~[]byte | ~string](dst []byte, src Bytes, flags *jsonflags.Flags) ([]byte, error) {
|
||||
var i, n int
|
||||
var hasInvalidUTF8 bool
|
||||
dst = slices.Grow(dst, len(`"`)+len(src)+len(`"`))
|
||||
dst = append(dst, '"')
|
||||
for uint(len(src)) > uint(n) {
|
||||
if c := src[n]; c < utf8.RuneSelf {
|
||||
// Handle single-byte ASCII.
|
||||
n++
|
||||
if escapeASCII[c] == 0 {
|
||||
continue // no escaping possibly needed
|
||||
}
|
||||
// Handle escaping of single-byte ASCII.
|
||||
if !(c == '<' || c == '>' || c == '&') || flags.Get(jsonflags.EscapeForHTML) {
|
||||
dst = append(dst, src[i:n-1]...)
|
||||
dst = appendEscapedASCII(dst, c)
|
||||
i = n
|
||||
}
|
||||
} else {
|
||||
// Handle multi-byte Unicode.
|
||||
r, rn := utf8.DecodeRuneInString(string(truncateMaxUTF8(src[n:])))
|
||||
n += rn
|
||||
if r != utf8.RuneError && r != '\u2028' && r != '\u2029' {
|
||||
continue // no escaping possibly needed
|
||||
}
|
||||
// Handle escaping of multi-byte Unicode.
|
||||
switch {
|
||||
case isInvalidUTF8(r, rn):
|
||||
hasInvalidUTF8 = true
|
||||
dst = append(dst, src[i:n-rn]...)
|
||||
if flags.Get(jsonflags.EscapeInvalidUTF8) {
|
||||
dst = append(dst, `\ufffd`...)
|
||||
} else {
|
||||
dst = append(dst, "\ufffd"...)
|
||||
}
|
||||
i = n
|
||||
case (r == '\u2028' || r == '\u2029') && flags.Get(jsonflags.EscapeForJS):
|
||||
dst = append(dst, src[i:n-rn]...)
|
||||
dst = appendEscapedUnicode(dst, r)
|
||||
i = n
|
||||
}
|
||||
}
|
||||
}
|
||||
dst = append(dst, src[i:n]...)
|
||||
dst = append(dst, '"')
|
||||
if hasInvalidUTF8 && !flags.Get(jsonflags.AllowInvalidUTF8) {
|
||||
return dst, ErrInvalidUTF8
|
||||
}
|
||||
return dst, nil
|
||||
}
|
||||
|
||||
func appendEscapedASCII(dst []byte, c byte) []byte {
|
||||
switch c {
|
||||
case '"', '\\':
|
||||
dst = append(dst, '\\', c)
|
||||
case '\b':
|
||||
dst = append(dst, "\\b"...)
|
||||
case '\f':
|
||||
dst = append(dst, "\\f"...)
|
||||
case '\n':
|
||||
dst = append(dst, "\\n"...)
|
||||
case '\r':
|
||||
dst = append(dst, "\\r"...)
|
||||
case '\t':
|
||||
dst = append(dst, "\\t"...)
|
||||
default:
|
||||
dst = appendEscapedUTF16(dst, uint16(c))
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func appendEscapedUnicode(dst []byte, r rune) []byte {
|
||||
if r1, r2 := utf16.EncodeRune(r); r1 != '\ufffd' && r2 != '\ufffd' {
|
||||
dst = appendEscapedUTF16(dst, uint16(r1))
|
||||
dst = appendEscapedUTF16(dst, uint16(r2))
|
||||
} else {
|
||||
dst = appendEscapedUTF16(dst, uint16(r))
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func appendEscapedUTF16(dst []byte, x uint16) []byte {
|
||||
const hex = "0123456789abcdef"
|
||||
return append(dst, '\\', 'u', hex[(x>>12)&0xf], hex[(x>>8)&0xf], hex[(x>>4)&0xf], hex[(x>>0)&0xf])
|
||||
}
|
||||
|
||||
// ReformatString consumes a JSON string from src and appends it to dst,
|
||||
// reformatting it if necessary according to the specified flags.
|
||||
// It returns the appended output and the number of consumed input bytes.
|
||||
func ReformatString(dst, src []byte, flags *jsonflags.Flags) ([]byte, int, error) {
|
||||
// TODO: Should this update ValueFlags as input?
|
||||
var valFlags ValueFlags
|
||||
n, err := ConsumeString(&valFlags, src, !flags.Get(jsonflags.AllowInvalidUTF8))
|
||||
if err != nil {
|
||||
return dst, n, err
|
||||
}
|
||||
|
||||
// If the output requires no special escapes, and the input
|
||||
// is already in canonical form or should be preserved verbatim,
|
||||
// then directly copy the input to the output.
|
||||
if !flags.Get(jsonflags.AnyEscape) &&
|
||||
(valFlags.IsCanonical() || flags.Get(jsonflags.PreserveRawStrings)) {
|
||||
dst = append(dst, src[:n]...) // copy the string verbatim
|
||||
return dst, n, nil
|
||||
}
|
||||
|
||||
// Under [jsonflags.PreserveRawStrings], any pre-escaped sequences
|
||||
// remain escaped, however we still need to respect the
|
||||
// [jsonflags.EscapeForHTML] and [jsonflags.EscapeForJS] options.
|
||||
if flags.Get(jsonflags.PreserveRawStrings) {
|
||||
var i, lastAppendIndex int
|
||||
for i < n {
|
||||
if c := src[i]; c < utf8.RuneSelf {
|
||||
if (c == '<' || c == '>' || c == '&') && flags.Get(jsonflags.EscapeForHTML) {
|
||||
dst = append(dst, src[lastAppendIndex:i]...)
|
||||
dst = appendEscapedASCII(dst, c)
|
||||
lastAppendIndex = i + 1
|
||||
}
|
||||
i++
|
||||
} else {
|
||||
r, rn := utf8.DecodeRune(truncateMaxUTF8(src[i:]))
|
||||
if (r == '\u2028' || r == '\u2029') && flags.Get(jsonflags.EscapeForJS) {
|
||||
dst = append(dst, src[lastAppendIndex:i]...)
|
||||
dst = appendEscapedUnicode(dst, r)
|
||||
lastAppendIndex = i + rn
|
||||
}
|
||||
i += rn
|
||||
}
|
||||
}
|
||||
return append(dst, src[lastAppendIndex:n]...), n, nil
|
||||
}
|
||||
|
||||
// The input contains characters that might need escaping,
|
||||
// unnecessary escape sequences, or invalid UTF-8.
|
||||
// Perform a round-trip unquote and quote to properly reformat
|
||||
// these sequences according the current flags.
|
||||
b, _ := AppendUnquote(nil, src[:n])
|
||||
dst, _ = AppendQuote(dst, b, flags)
|
||||
return dst, n, nil
|
||||
}
|
||||
|
||||
// AppendFloat appends src to dst as a JSON number per RFC 7159, section 6.
|
||||
// It formats numbers similar to the ES6 number-to-string conversion.
|
||||
// See https://go.dev/issue/14135.
|
||||
//
|
||||
// The output is identical to ECMA-262, 6th edition, section 7.1.12.1 and with
|
||||
// RFC 8785, section 3.2.2.3 for 64-bit floating-point numbers except for -0,
|
||||
// which is formatted as -0 instead of just 0.
|
||||
//
|
||||
// For 32-bit floating-point numbers,
|
||||
// the output is a 32-bit equivalent of the algorithm.
|
||||
// Note that ECMA-262 specifies no algorithm for 32-bit numbers.
|
||||
func AppendFloat(dst []byte, src float64, bits int) []byte {
|
||||
if bits == 32 {
|
||||
src = float64(float32(src))
|
||||
}
|
||||
|
||||
abs := math.Abs(src)
|
||||
fmt := byte('f')
|
||||
if abs != 0 {
|
||||
if bits == 64 && (float64(abs) < 1e-6 || float64(abs) >= 1e21) ||
|
||||
bits == 32 && (float32(abs) < 1e-6 || float32(abs) >= 1e21) {
|
||||
fmt = 'e'
|
||||
}
|
||||
}
|
||||
dst = strconv.AppendFloat(dst, src, fmt, -1, bits)
|
||||
if fmt == 'e' {
|
||||
// Clean up e-09 to e-9.
|
||||
n := len(dst)
|
||||
if n >= 4 && dst[n-4] == 'e' && dst[n-3] == '-' && dst[n-2] == '0' {
|
||||
dst[n-2] = dst[n-1]
|
||||
dst = dst[:n-1]
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// ReformatNumber consumes a JSON string from src and appends it to dst,
|
||||
// canonicalizing it if specified.
|
||||
// It returns the appended output and the number of consumed input bytes.
|
||||
func ReformatNumber(dst, src []byte, flags *jsonflags.Flags) ([]byte, int, error) {
|
||||
n, err := ConsumeNumber(src)
|
||||
if err != nil {
|
||||
return dst, n, err
|
||||
}
|
||||
if !flags.Get(jsonflags.CanonicalizeNumbers) {
|
||||
dst = append(dst, src[:n]...) // copy the number verbatim
|
||||
return dst, n, nil
|
||||
}
|
||||
|
||||
// Identify the kind of number.
|
||||
var isFloat bool
|
||||
for _, c := range src[:n] {
|
||||
if c == '.' || c == 'e' || c == 'E' {
|
||||
isFloat = true // has fraction or exponent
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Check if need to canonicalize this kind of number.
|
||||
switch {
|
||||
case string(src[:n]) == "-0":
|
||||
break // canonicalize -0 as 0 regardless of kind
|
||||
case isFloat:
|
||||
if !flags.Get(jsonflags.CanonicalizeRawFloats) {
|
||||
dst = append(dst, src[:n]...) // copy the number verbatim
|
||||
return dst, n, nil
|
||||
}
|
||||
default:
|
||||
// As an optimization, we can copy integer numbers below 2⁵³ verbatim
|
||||
// since the canonical form is always identical.
|
||||
const maxExactIntegerDigits = 16 // len(strconv.AppendUint(nil, 1<<53, 10))
|
||||
if !flags.Get(jsonflags.CanonicalizeRawInts) || n < maxExactIntegerDigits {
|
||||
dst = append(dst, src[:n]...) // copy the number verbatim
|
||||
return dst, n, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Parse and reformat the number (which uses a canonical format).
|
||||
fv, _ := strconv.ParseFloat(string(src[:n]), 64)
|
||||
switch {
|
||||
case fv == 0:
|
||||
fv = 0 // normalize negative zero as just zero
|
||||
case math.IsInf(fv, +1):
|
||||
fv = +math.MaxFloat64
|
||||
case math.IsInf(fv, -1):
|
||||
fv = -math.MaxFloat64
|
||||
}
|
||||
return AppendFloat(dst, fv, 64), n, nil
|
||||
}
|
215
vendor/github.com/go-json-experiment/json/internal/jsonwire/wire.go
generated
vendored
Normal file
215
vendor/github.com/go-json-experiment/json/internal/jsonwire/wire.go
generated
vendored
Normal file
@ -0,0 +1,215 @@
|
||||
// Copyright 2023 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package jsonwire implements stateless functionality for handling JSON text.
|
||||
package jsonwire
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf16"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// TrimSuffixWhitespace trims JSON from the end of b.
|
||||
func TrimSuffixWhitespace(b []byte) []byte {
|
||||
// NOTE: The arguments and logic are kept simple to keep this inlinable.
|
||||
n := len(b) - 1
|
||||
for n >= 0 && (b[n] == ' ' || b[n] == '\t' || b[n] == '\r' || b[n] == '\n') {
|
||||
n--
|
||||
}
|
||||
return b[:n+1]
|
||||
}
|
||||
|
||||
// TrimSuffixString trims a valid JSON string at the end of b.
|
||||
// The behavior is undefined if there is not a valid JSON string present.
|
||||
func TrimSuffixString(b []byte) []byte {
|
||||
// NOTE: The arguments and logic are kept simple to keep this inlinable.
|
||||
if len(b) > 0 && b[len(b)-1] == '"' {
|
||||
b = b[:len(b)-1]
|
||||
}
|
||||
for len(b) >= 2 && !(b[len(b)-1] == '"' && b[len(b)-2] != '\\') {
|
||||
b = b[:len(b)-1] // trim all characters except an unescaped quote
|
||||
}
|
||||
if len(b) > 0 && b[len(b)-1] == '"' {
|
||||
b = b[:len(b)-1]
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// HasSuffixByte reports whether b ends with c.
|
||||
func HasSuffixByte(b []byte, c byte) bool {
|
||||
// NOTE: The arguments and logic are kept simple to keep this inlinable.
|
||||
return len(b) > 0 && b[len(b)-1] == c
|
||||
}
|
||||
|
||||
// TrimSuffixByte removes c from the end of b if it is present.
|
||||
func TrimSuffixByte(b []byte, c byte) []byte {
|
||||
// NOTE: The arguments and logic are kept simple to keep this inlinable.
|
||||
if len(b) > 0 && b[len(b)-1] == c {
|
||||
return b[:len(b)-1]
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// QuoteRune quotes the first rune in the input.
|
||||
func QuoteRune[Bytes ~[]byte | ~string](b Bytes) string {
|
||||
r, n := utf8.DecodeRuneInString(string(truncateMaxUTF8(b)))
|
||||
if r == utf8.RuneError && n == 1 {
|
||||
return `'\x` + strconv.FormatUint(uint64(b[0]), 16) + `'`
|
||||
}
|
||||
return strconv.QuoteRune(r)
|
||||
}
|
||||
|
||||
// CompareUTF16 lexicographically compares x to y according
|
||||
// to the UTF-16 codepoints of the UTF-8 encoded input strings.
|
||||
// This implements the ordering specified in RFC 8785, section 3.2.3.
|
||||
func CompareUTF16[Bytes ~[]byte | ~string](x, y Bytes) int {
|
||||
// NOTE: This is an optimized, mostly allocation-free implementation
|
||||
// of CompareUTF16Simple in wire_test.go. FuzzCompareUTF16 verifies that the
|
||||
// two implementations agree on the result of comparing any two strings.
|
||||
isUTF16Self := func(r rune) bool {
|
||||
return ('\u0000' <= r && r <= '\uD7FF') || ('\uE000' <= r && r <= '\uFFFF')
|
||||
}
|
||||
|
||||
for {
|
||||
if len(x) == 0 || len(y) == 0 {
|
||||
return cmp.Compare(len(x), len(y))
|
||||
}
|
||||
|
||||
// ASCII fast-path.
|
||||
if x[0] < utf8.RuneSelf || y[0] < utf8.RuneSelf {
|
||||
if x[0] != y[0] {
|
||||
return cmp.Compare(x[0], y[0])
|
||||
}
|
||||
x, y = x[1:], y[1:]
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode next pair of runes as UTF-8.
|
||||
rx, nx := utf8.DecodeRuneInString(string(truncateMaxUTF8(x)))
|
||||
ry, ny := utf8.DecodeRuneInString(string(truncateMaxUTF8(y)))
|
||||
|
||||
selfx := isUTF16Self(rx)
|
||||
selfy := isUTF16Self(ry)
|
||||
switch {
|
||||
// The x rune is a single UTF-16 codepoint, while
|
||||
// the y rune is a surrogate pair of UTF-16 codepoints.
|
||||
case selfx && !selfy:
|
||||
ry, _ = utf16.EncodeRune(ry)
|
||||
// The y rune is a single UTF-16 codepoint, while
|
||||
// the x rune is a surrogate pair of UTF-16 codepoints.
|
||||
case selfy && !selfx:
|
||||
rx, _ = utf16.EncodeRune(rx)
|
||||
}
|
||||
if rx != ry {
|
||||
return cmp.Compare(rx, ry)
|
||||
}
|
||||
|
||||
// Check for invalid UTF-8, in which case,
|
||||
// we just perform a byte-for-byte comparison.
|
||||
if isInvalidUTF8(rx, nx) || isInvalidUTF8(ry, ny) {
|
||||
if x[0] != y[0] {
|
||||
return cmp.Compare(x[0], y[0])
|
||||
}
|
||||
}
|
||||
x, y = x[nx:], y[ny:]
|
||||
}
|
||||
}
|
||||
|
||||
// truncateMaxUTF8 truncates b such it contains at least one rune.
|
||||
//
|
||||
// The utf8 package currently lacks generic variants, which complicates
|
||||
// generic functions that operates on either []byte or string.
|
||||
// As a hack, we always call the utf8 function operating on strings,
|
||||
// but always truncate the input such that the result is identical.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// utf8.DecodeRuneInString(string(truncateMaxUTF8(b)))
|
||||
//
|
||||
// Converting a []byte to a string is stack allocated since
|
||||
// truncateMaxUTF8 guarantees that the []byte is short.
|
||||
func truncateMaxUTF8[Bytes ~[]byte | ~string](b Bytes) Bytes {
|
||||
// TODO(https://go.dev/issue/56948): Remove this function and
|
||||
// instead directly call generic utf8 functions wherever used.
|
||||
if len(b) > utf8.UTFMax {
|
||||
return b[:utf8.UTFMax]
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// TODO(https://go.dev/issue/70547): Use utf8.ErrInvalid instead.
|
||||
var ErrInvalidUTF8 = errors.New("invalid UTF-8")
|
||||
|
||||
func NewInvalidCharacterError[Bytes ~[]byte | ~string](prefix Bytes, where string) error {
|
||||
what := QuoteRune(prefix)
|
||||
return errors.New("invalid character " + what + " " + where)
|
||||
}
|
||||
|
||||
func NewInvalidEscapeSequenceError[Bytes ~[]byte | ~string](what Bytes) error {
|
||||
label := "escape sequence"
|
||||
if len(what) > 6 {
|
||||
label = "surrogate pair"
|
||||
}
|
||||
needEscape := strings.IndexFunc(string(what), func(r rune) bool {
|
||||
return r == '`' || r == utf8.RuneError || unicode.IsSpace(r) || !unicode.IsPrint(r)
|
||||
}) >= 0
|
||||
if needEscape {
|
||||
return errors.New("invalid " + label + " " + strconv.Quote(string(what)) + " in string")
|
||||
} else {
|
||||
return errors.New("invalid " + label + " `" + string(what) + "` in string")
|
||||
}
|
||||
}
|
||||
|
||||
// TruncatePointer optionally truncates the JSON pointer,
|
||||
// enforcing that the length roughly does not exceed n.
|
||||
func TruncatePointer(s string, n int) string {
|
||||
if len(s) <= n {
|
||||
return s
|
||||
}
|
||||
i := n / 2
|
||||
j := len(s) - n/2
|
||||
|
||||
// Avoid truncating a name if there are multiple names present.
|
||||
if k := strings.LastIndexByte(s[:i], '/'); k > 0 {
|
||||
i = k
|
||||
}
|
||||
if k := strings.IndexByte(s[j:], '/'); k >= 0 {
|
||||
j += k + len("/")
|
||||
}
|
||||
|
||||
// Avoid truncation in the middle of a UTF-8 rune.
|
||||
for i > 0 && isInvalidUTF8(utf8.DecodeLastRuneInString(s[:i])) {
|
||||
i--
|
||||
}
|
||||
for j < len(s) && isInvalidUTF8(utf8.DecodeRuneInString(s[j:])) {
|
||||
j++
|
||||
}
|
||||
|
||||
// Determine the right middle fragment to use.
|
||||
var middle string
|
||||
switch strings.Count(s[i:j], "/") {
|
||||
case 0:
|
||||
middle = "…"
|
||||
case 1:
|
||||
middle = "…/…"
|
||||
default:
|
||||
middle = "…/…/…"
|
||||
}
|
||||
if strings.HasPrefix(s[i:j], "/") && middle != "…" {
|
||||
middle = strings.TrimPrefix(middle, "…")
|
||||
}
|
||||
if strings.HasSuffix(s[i:j], "/") && middle != "…" {
|
||||
middle = strings.TrimSuffix(middle, "…")
|
||||
}
|
||||
return s[:i] + middle + s[j:]
|
||||
}
|
||||
|
||||
func isInvalidUTF8(r rune, rn int) bool {
|
||||
return r == utf8.RuneError && rn == 1
|
||||
}
|
1166
vendor/github.com/go-json-experiment/json/jsontext/decode.go
generated
vendored
Normal file
1166
vendor/github.com/go-json-experiment/json/jsontext/decode.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
105
vendor/github.com/go-json-experiment/json/jsontext/doc.go
generated
vendored
Normal file
105
vendor/github.com/go-json-experiment/json/jsontext/doc.go
generated
vendored
Normal file
@ -0,0 +1,105 @@
|
||||
// Copyright 2023 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package jsontext implements syntactic processing of JSON
|
||||
// as specified in RFC 4627, RFC 7159, RFC 7493, RFC 8259, and RFC 8785.
|
||||
// JSON is a simple data interchange format that can represent
|
||||
// primitive data types such as booleans, strings, and numbers,
|
||||
// in addition to structured data types such as objects and arrays.
|
||||
//
|
||||
// The [Encoder] and [Decoder] types are used to encode or decode
|
||||
// a stream of JSON tokens or values.
|
||||
//
|
||||
// # Tokens and Values
|
||||
//
|
||||
// A JSON token refers to the basic structural elements of JSON:
|
||||
//
|
||||
// - a JSON literal (i.e., null, true, or false)
|
||||
// - a JSON string (e.g., "hello, world!")
|
||||
// - a JSON number (e.g., 123.456)
|
||||
// - a start or end delimiter for a JSON object (i.e., '{' or '}')
|
||||
// - a start or end delimiter for a JSON array (i.e., '[' or ']')
|
||||
//
|
||||
// A JSON token is represented by the [Token] type in Go. Technically,
|
||||
// there are two additional structural characters (i.e., ':' and ','),
|
||||
// but there is no [Token] representation for them since their presence
|
||||
// can be inferred by the structure of the JSON grammar itself.
|
||||
// For example, there must always be an implicit colon between
|
||||
// the name and value of a JSON object member.
|
||||
//
|
||||
// A JSON value refers to a complete unit of JSON data:
|
||||
//
|
||||
// - a JSON literal, string, or number
|
||||
// - a JSON object (e.g., `{"name":"value"}`)
|
||||
// - a JSON array (e.g., `[1,2,3,]`)
|
||||
//
|
||||
// A JSON value is represented by the [Value] type in Go and is a []byte
|
||||
// containing the raw textual representation of the value. There is some overlap
|
||||
// between tokens and values as both contain literals, strings, and numbers.
|
||||
// However, only a value can represent the entirety of a JSON object or array.
|
||||
//
|
||||
// The [Encoder] and [Decoder] types contain methods to read or write the next
|
||||
// [Token] or [Value] in a sequence. They maintain a state machine to validate
|
||||
// whether the sequence of JSON tokens and/or values produces a valid JSON.
|
||||
// [Options] may be passed to the [NewEncoder] or [NewDecoder] constructors
|
||||
// to configure the syntactic behavior of encoding and decoding.
|
||||
//
|
||||
// # Terminology
|
||||
//
|
||||
// The terms "encode" and "decode" are used for syntactic functionality
|
||||
// that is concerned with processing JSON based on its grammar, and
|
||||
// the terms "marshal" and "unmarshal" are used for semantic functionality
|
||||
// that determines the meaning of JSON values as Go values and vice-versa.
|
||||
// This package (i.e., [jsontext]) deals with JSON at a syntactic layer,
|
||||
// while [encoding/json/v2] deals with JSON at a semantic layer.
|
||||
// The goal is to provide a clear distinction between functionality that
|
||||
// is purely concerned with encoding versus that of marshaling.
|
||||
// For example, one can directly encode a stream of JSON tokens without
|
||||
// needing to marshal a concrete Go value representing them.
|
||||
// Similarly, one can decode a stream of JSON tokens without
|
||||
// needing to unmarshal them into a concrete Go value.
|
||||
//
|
||||
// This package uses JSON terminology when discussing JSON, which may differ
|
||||
// from related concepts in Go or elsewhere in computing literature.
|
||||
//
|
||||
// - a JSON "object" refers to an unordered collection of name/value members.
|
||||
// - a JSON "array" refers to an ordered sequence of elements.
|
||||
// - a JSON "value" refers to either a literal (i.e., null, false, or true),
|
||||
// string, number, object, or array.
|
||||
//
|
||||
// See RFC 8259 for more information.
|
||||
//
|
||||
// # Specifications
|
||||
//
|
||||
// Relevant specifications include RFC 4627, RFC 7159, RFC 7493, RFC 8259,
|
||||
// and RFC 8785. Each RFC is generally a stricter subset of another RFC.
|
||||
// In increasing order of strictness:
|
||||
//
|
||||
// - RFC 4627 and RFC 7159 do not require (but recommend) the use of UTF-8
|
||||
// and also do not require (but recommend) that object names be unique.
|
||||
// - RFC 8259 requires the use of UTF-8,
|
||||
// but does not require (but recommends) that object names be unique.
|
||||
// - RFC 7493 requires the use of UTF-8
|
||||
// and also requires that object names be unique.
|
||||
// - RFC 8785 defines a canonical representation. It requires the use of UTF-8
|
||||
// and also requires that object names be unique and in a specific ordering.
|
||||
// It specifies exactly how strings and numbers must be formatted.
|
||||
//
|
||||
// The primary difference between RFC 4627 and RFC 7159 is that the former
|
||||
// restricted top-level values to only JSON objects and arrays, while
|
||||
// RFC 7159 and subsequent RFCs permit top-level values to additionally be
|
||||
// JSON nulls, booleans, strings, or numbers.
|
||||
//
|
||||
// By default, this package operates on RFC 7493, but can be configured
|
||||
// to operate according to the other RFC specifications.
|
||||
// RFC 7493 is a stricter subset of RFC 8259 and fully compliant with it.
|
||||
// In particular, it makes specific choices about behavior that RFC 8259
|
||||
// leaves as undefined in order to ensure greater interoperability.
|
||||
package jsontext
|
||||
|
||||
// requireKeyedLiterals can be embedded in a struct to require keyed literals.
|
||||
type requireKeyedLiterals struct{}
|
||||
|
||||
// nonComparable can be embedded in a struct to prevent comparability.
|
||||
type nonComparable [0]func()
|
970
vendor/github.com/go-json-experiment/json/jsontext/encode.go
generated
vendored
Normal file
970
vendor/github.com/go-json-experiment/json/jsontext/encode.go
generated
vendored
Normal file
@ -0,0 +1,970 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package jsontext
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"math/bits"
|
||||
|
||||
"github.com/go-json-experiment/json/internal/jsonflags"
|
||||
"github.com/go-json-experiment/json/internal/jsonopts"
|
||||
"github.com/go-json-experiment/json/internal/jsonwire"
|
||||
)
|
||||
|
||||
// Encoder is a streaming encoder from raw JSON tokens and values.
|
||||
// It is used to write a stream of top-level JSON values,
|
||||
// each terminated with a newline character.
|
||||
//
|
||||
// [Encoder.WriteToken] and [Encoder.WriteValue] calls may be interleaved.
|
||||
// For example, the following JSON value:
|
||||
//
|
||||
// {"name":"value","array":[null,false,true,3.14159],"object":{"k":"v"}}
|
||||
//
|
||||
// can be composed with the following calls (ignoring errors for brevity):
|
||||
//
|
||||
// e.WriteToken(BeginObject) // {
|
||||
// e.WriteToken(String("name")) // "name"
|
||||
// e.WriteToken(String("value")) // "value"
|
||||
// e.WriteValue(Value(`"array"`)) // "array"
|
||||
// e.WriteToken(BeginArray) // [
|
||||
// e.WriteToken(Null) // null
|
||||
// e.WriteToken(False) // false
|
||||
// e.WriteValue(Value("true")) // true
|
||||
// e.WriteToken(Float(3.14159)) // 3.14159
|
||||
// e.WriteToken(EndArray) // ]
|
||||
// e.WriteValue(Value(`"object"`)) // "object"
|
||||
// e.WriteValue(Value(`{"k":"v"}`)) // {"k":"v"}
|
||||
// e.WriteToken(EndObject) // }
|
||||
//
|
||||
// The above is one of many possible sequence of calls and
|
||||
// may not represent the most sensible method to call for any given token/value.
|
||||
// For example, it is probably more common to call [Encoder.WriteToken] with a string
|
||||
// for object names.
|
||||
type Encoder struct {
|
||||
s encoderState
|
||||
}
|
||||
|
||||
// encoderState is the low-level state of Encoder.
|
||||
// It has exported fields and method for use by the "json" package.
|
||||
type encoderState struct {
|
||||
state
|
||||
encodeBuffer
|
||||
jsonopts.Struct
|
||||
|
||||
SeenPointers map[any]struct{} // only used when marshaling; identical to json.seenPointers
|
||||
}
|
||||
|
||||
// encodeBuffer is a buffer split into 2 segments:
|
||||
//
|
||||
// - buf[0:len(buf)] // written (but unflushed) portion of the buffer
|
||||
// - buf[len(buf):cap(buf)] // unused portion of the buffer
|
||||
type encodeBuffer struct {
|
||||
Buf []byte // may alias wr if it is a bytes.Buffer
|
||||
|
||||
// baseOffset is added to len(buf) to obtain the absolute offset
|
||||
// relative to the start of io.Writer stream.
|
||||
baseOffset int64
|
||||
|
||||
wr io.Writer
|
||||
|
||||
// maxValue is the approximate maximum Value size passed to WriteValue.
|
||||
maxValue int
|
||||
// unusedCache is the buffer returned by the UnusedBuffer method.
|
||||
unusedCache []byte
|
||||
// bufStats is statistics about buffer utilization.
|
||||
// It is only used with pooled encoders in pools.go.
|
||||
bufStats bufferStatistics
|
||||
}
|
||||
|
||||
// NewEncoder constructs a new streaming encoder writing to w
|
||||
// configured with the provided options.
|
||||
// It flushes the internal buffer when the buffer is sufficiently full or
|
||||
// when a top-level value has been written.
|
||||
//
|
||||
// If w is a [bytes.Buffer], then the encoder appends directly into the buffer
|
||||
// without copying the contents from an intermediate buffer.
|
||||
func NewEncoder(w io.Writer, opts ...Options) *Encoder {
|
||||
e := new(Encoder)
|
||||
e.Reset(w, opts...)
|
||||
return e
|
||||
}
|
||||
|
||||
// Reset resets an encoder such that it is writing afresh to w and
|
||||
// configured with the provided options. Reset must not be called on
|
||||
// a Encoder passed to the [encoding/json/v2.MarshalerTo.MarshalJSONTo] method
|
||||
// or the [encoding/json/v2.MarshalToFunc] function.
|
||||
func (e *Encoder) Reset(w io.Writer, opts ...Options) {
|
||||
switch {
|
||||
case e == nil:
|
||||
panic("jsontext: invalid nil Encoder")
|
||||
case w == nil:
|
||||
panic("jsontext: invalid nil io.Writer")
|
||||
case e.s.Flags.Get(jsonflags.WithinArshalCall):
|
||||
panic("jsontext: cannot reset Encoder passed to json.MarshalerTo")
|
||||
}
|
||||
e.s.reset(nil, w, opts...)
|
||||
}
|
||||
|
||||
func (e *encoderState) reset(b []byte, w io.Writer, opts ...Options) {
|
||||
e.state.reset()
|
||||
e.encodeBuffer = encodeBuffer{Buf: b, wr: w, bufStats: e.bufStats}
|
||||
if bb, ok := w.(*bytes.Buffer); ok && bb != nil {
|
||||
e.Buf = bb.Bytes()[bb.Len():] // alias the unused buffer of bb
|
||||
}
|
||||
opts2 := jsonopts.Struct{} // avoid mutating e.Struct in case it is part of opts
|
||||
opts2.Join(opts...)
|
||||
e.Struct = opts2
|
||||
if e.Flags.Get(jsonflags.Multiline) {
|
||||
if !e.Flags.Has(jsonflags.SpaceAfterColon) {
|
||||
e.Flags.Set(jsonflags.SpaceAfterColon | 1)
|
||||
}
|
||||
if !e.Flags.Has(jsonflags.SpaceAfterComma) {
|
||||
e.Flags.Set(jsonflags.SpaceAfterComma | 0)
|
||||
}
|
||||
if !e.Flags.Has(jsonflags.Indent) {
|
||||
e.Flags.Set(jsonflags.Indent | 1)
|
||||
e.Indent = "\t"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Options returns the options used to construct the decoder and
|
||||
// may additionally contain semantic options passed to a
|
||||
// [encoding/json/v2.MarshalEncode] call.
|
||||
//
|
||||
// If operating within
|
||||
// a [encoding/json/v2.MarshalerTo.MarshalJSONTo] method call or
|
||||
// a [encoding/json/v2.MarshalToFunc] function call,
|
||||
// then the returned options are only valid within the call.
|
||||
func (e *Encoder) Options() Options {
|
||||
return &e.s.Struct
|
||||
}
|
||||
|
||||
// NeedFlush determines whether to flush at this point.
|
||||
func (e *encoderState) NeedFlush() bool {
|
||||
// NOTE: This function is carefully written to be inlinable.
|
||||
|
||||
// Avoid flushing if e.wr is nil since there is no underlying writer.
|
||||
// Flush if less than 25% of the capacity remains.
|
||||
// Flushing at some constant fraction ensures that the buffer stops growing
|
||||
// so long as the largest Token or Value fits within that unused capacity.
|
||||
return e.wr != nil && (e.Tokens.Depth() == 1 || len(e.Buf) > 3*cap(e.Buf)/4)
|
||||
}
|
||||
|
||||
// Flush flushes the buffer to the underlying io.Writer.
|
||||
// It may append a trailing newline after the top-level value.
|
||||
func (e *encoderState) Flush() error {
|
||||
if e.wr == nil || e.avoidFlush() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// In streaming mode, always emit a newline after the top-level value.
|
||||
if e.Tokens.Depth() == 1 && !e.Flags.Get(jsonflags.OmitTopLevelNewline) {
|
||||
e.Buf = append(e.Buf, '\n')
|
||||
}
|
||||
|
||||
// Inform objectNameStack that we are about to flush the buffer content.
|
||||
e.Names.copyQuotedBuffer(e.Buf)
|
||||
|
||||
// Specialize bytes.Buffer for better performance.
|
||||
if bb, ok := e.wr.(*bytes.Buffer); ok {
|
||||
// If e.buf already aliases the internal buffer of bb,
|
||||
// then the Write call simply increments the internal offset,
|
||||
// otherwise Write operates as expected.
|
||||
// See https://go.dev/issue/42986.
|
||||
n, _ := bb.Write(e.Buf) // never fails unless bb is nil
|
||||
e.baseOffset += int64(n)
|
||||
|
||||
// If the internal buffer of bytes.Buffer is too small,
|
||||
// append operations elsewhere in the Encoder may grow the buffer.
|
||||
// This would be semantically correct, but hurts performance.
|
||||
// As such, ensure 25% of the current length is always available
|
||||
// to reduce the probability that other appends must allocate.
|
||||
if avail := bb.Available(); avail < bb.Len()/4 {
|
||||
bb.Grow(avail + 1)
|
||||
}
|
||||
|
||||
e.Buf = bb.AvailableBuffer()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Flush the internal buffer to the underlying io.Writer.
|
||||
n, err := e.wr.Write(e.Buf)
|
||||
e.baseOffset += int64(n)
|
||||
if err != nil {
|
||||
// In the event of an error, preserve the unflushed portion.
|
||||
// Thus, write errors aren't fatal so long as the io.Writer
|
||||
// maintains consistent state after errors.
|
||||
if n > 0 {
|
||||
e.Buf = e.Buf[:copy(e.Buf, e.Buf[n:])]
|
||||
}
|
||||
return &ioError{action: "write", err: err}
|
||||
}
|
||||
e.Buf = e.Buf[:0]
|
||||
|
||||
// Check whether to grow the buffer.
|
||||
// Note that cap(e.buf) may already exceed maxBufferSize since
|
||||
// an append elsewhere already grew it to store a large token.
|
||||
const maxBufferSize = 4 << 10
|
||||
const growthSizeFactor = 2 // higher value is faster
|
||||
const growthRateFactor = 2 // higher value is slower
|
||||
// By default, grow if below the maximum buffer size.
|
||||
grow := cap(e.Buf) <= maxBufferSize/growthSizeFactor
|
||||
// Growing can be expensive, so only grow
|
||||
// if a sufficient number of bytes have been processed.
|
||||
grow = grow && int64(cap(e.Buf)) < e.previousOffsetEnd()/growthRateFactor
|
||||
if grow {
|
||||
e.Buf = make([]byte, 0, cap(e.Buf)*growthSizeFactor)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func (d *encodeBuffer) offsetAt(pos int) int64 { return d.baseOffset + int64(pos) }
|
||||
func (e *encodeBuffer) previousOffsetEnd() int64 { return e.baseOffset + int64(len(e.Buf)) }
|
||||
func (e *encodeBuffer) unflushedBuffer() []byte { return e.Buf }
|
||||
|
||||
// avoidFlush indicates whether to avoid flushing to ensure there is always
|
||||
// enough in the buffer to unwrite the last object member if it were empty.
|
||||
func (e *encoderState) avoidFlush() bool {
|
||||
switch {
|
||||
case e.Tokens.Last.Length() == 0:
|
||||
// Never flush after BeginObject or BeginArray since we don't know yet
|
||||
// if the object or array will end up being empty.
|
||||
return true
|
||||
case e.Tokens.Last.needObjectValue():
|
||||
// Never flush before the object value since we don't know yet
|
||||
// if the object value will end up being empty.
|
||||
return true
|
||||
case e.Tokens.Last.NeedObjectName() && len(e.Buf) >= 2:
|
||||
// Never flush after the object value if it does turn out to be empty.
|
||||
switch string(e.Buf[len(e.Buf)-2:]) {
|
||||
case `ll`, `""`, `{}`, `[]`: // last two bytes of every empty value
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// UnwriteEmptyObjectMember unwrites the last object member if it is empty
|
||||
// and reports whether it performed an unwrite operation.
|
||||
func (e *encoderState) UnwriteEmptyObjectMember(prevName *string) bool {
|
||||
if last := e.Tokens.Last; !last.isObject() || !last.NeedObjectName() || last.Length() == 0 {
|
||||
panic("BUG: must be called on an object after writing a value")
|
||||
}
|
||||
|
||||
// The flushing logic is modified to never flush a trailing empty value.
|
||||
// The encoder never writes trailing whitespace eagerly.
|
||||
b := e.unflushedBuffer()
|
||||
|
||||
// Detect whether the last value was empty.
|
||||
var n int
|
||||
if len(b) >= 3 {
|
||||
switch string(b[len(b)-2:]) {
|
||||
case "ll": // last two bytes of `null`
|
||||
n = len(`null`)
|
||||
case `""`:
|
||||
// It is possible for a non-empty string to have `""` as a suffix
|
||||
// if the second to the last quote was escaped.
|
||||
if b[len(b)-3] == '\\' {
|
||||
return false // e.g., `"\""` is not empty
|
||||
}
|
||||
n = len(`""`)
|
||||
case `{}`:
|
||||
n = len(`{}`)
|
||||
case `[]`:
|
||||
n = len(`[]`)
|
||||
}
|
||||
}
|
||||
if n == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Unwrite the value, whitespace, colon, name, whitespace, and comma.
|
||||
b = b[:len(b)-n]
|
||||
b = jsonwire.TrimSuffixWhitespace(b)
|
||||
b = jsonwire.TrimSuffixByte(b, ':')
|
||||
b = jsonwire.TrimSuffixString(b)
|
||||
b = jsonwire.TrimSuffixWhitespace(b)
|
||||
b = jsonwire.TrimSuffixByte(b, ',')
|
||||
e.Buf = b // store back truncated unflushed buffer
|
||||
|
||||
// Undo state changes.
|
||||
e.Tokens.Last.decrement() // for object member value
|
||||
e.Tokens.Last.decrement() // for object member name
|
||||
if !e.Flags.Get(jsonflags.AllowDuplicateNames) {
|
||||
if e.Tokens.Last.isActiveNamespace() {
|
||||
e.Namespaces.Last().removeLast()
|
||||
}
|
||||
}
|
||||
e.Names.clearLast()
|
||||
if prevName != nil {
|
||||
e.Names.copyQuotedBuffer(e.Buf) // required by objectNameStack.replaceLastUnquotedName
|
||||
e.Names.replaceLastUnquotedName(*prevName)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// UnwriteOnlyObjectMemberName unwrites the only object member name
|
||||
// and returns the unquoted name.
|
||||
func (e *encoderState) UnwriteOnlyObjectMemberName() string {
|
||||
if last := e.Tokens.Last; !last.isObject() || last.Length() != 1 {
|
||||
panic("BUG: must be called on an object after writing first name")
|
||||
}
|
||||
|
||||
// Unwrite the name and whitespace.
|
||||
b := jsonwire.TrimSuffixString(e.Buf)
|
||||
isVerbatim := bytes.IndexByte(e.Buf[len(b):], '\\') < 0
|
||||
name := string(jsonwire.UnquoteMayCopy(e.Buf[len(b):], isVerbatim))
|
||||
e.Buf = jsonwire.TrimSuffixWhitespace(b)
|
||||
|
||||
// Undo state changes.
|
||||
e.Tokens.Last.decrement()
|
||||
if !e.Flags.Get(jsonflags.AllowDuplicateNames) {
|
||||
if e.Tokens.Last.isActiveNamespace() {
|
||||
e.Namespaces.Last().removeLast()
|
||||
}
|
||||
}
|
||||
e.Names.clearLast()
|
||||
return name
|
||||
}
|
||||
|
||||
// WriteToken writes the next token and advances the internal write offset.
|
||||
//
|
||||
// The provided token kind must be consistent with the JSON grammar.
|
||||
// For example, it is an error to provide a number when the encoder
|
||||
// is expecting an object name (which is always a string), or
|
||||
// to provide an end object delimiter when the encoder is finishing an array.
|
||||
// If the provided token is invalid, then it reports a [SyntacticError] and
|
||||
// the internal state remains unchanged. The offset reported
|
||||
// in [SyntacticError] will be relative to the [Encoder.OutputOffset].
|
||||
func (e *Encoder) WriteToken(t Token) error {
|
||||
return e.s.WriteToken(t)
|
||||
}
|
||||
func (e *encoderState) WriteToken(t Token) error {
|
||||
k := t.Kind()
|
||||
b := e.Buf // use local variable to avoid mutating e in case of error
|
||||
|
||||
// Append any delimiters or optional whitespace.
|
||||
b = e.Tokens.MayAppendDelim(b, k)
|
||||
if e.Flags.Get(jsonflags.AnyWhitespace) {
|
||||
b = e.appendWhitespace(b, k)
|
||||
}
|
||||
pos := len(b) // offset before the token
|
||||
|
||||
// Append the token to the output and to the state machine.
|
||||
var err error
|
||||
switch k {
|
||||
case 'n':
|
||||
b = append(b, "null"...)
|
||||
err = e.Tokens.appendLiteral()
|
||||
case 'f':
|
||||
b = append(b, "false"...)
|
||||
err = e.Tokens.appendLiteral()
|
||||
case 't':
|
||||
b = append(b, "true"...)
|
||||
err = e.Tokens.appendLiteral()
|
||||
case '"':
|
||||
if b, err = t.appendString(b, &e.Flags); err != nil {
|
||||
break
|
||||
}
|
||||
if e.Tokens.Last.NeedObjectName() {
|
||||
if !e.Flags.Get(jsonflags.AllowDuplicateNames) {
|
||||
if !e.Tokens.Last.isValidNamespace() {
|
||||
err = errInvalidNamespace
|
||||
break
|
||||
}
|
||||
if e.Tokens.Last.isActiveNamespace() && !e.Namespaces.Last().insertQuoted(b[pos:], false) {
|
||||
err = wrapWithObjectName(ErrDuplicateName, b[pos:])
|
||||
break
|
||||
}
|
||||
}
|
||||
e.Names.ReplaceLastQuotedOffset(pos) // only replace if insertQuoted succeeds
|
||||
}
|
||||
err = e.Tokens.appendString()
|
||||
case '0':
|
||||
if b, err = t.appendNumber(b, &e.Flags); err != nil {
|
||||
break
|
||||
}
|
||||
err = e.Tokens.appendNumber()
|
||||
case '{':
|
||||
b = append(b, '{')
|
||||
if err = e.Tokens.pushObject(); err != nil {
|
||||
break
|
||||
}
|
||||
e.Names.push()
|
||||
if !e.Flags.Get(jsonflags.AllowDuplicateNames) {
|
||||
e.Namespaces.push()
|
||||
}
|
||||
case '}':
|
||||
b = append(b, '}')
|
||||
if err = e.Tokens.popObject(); err != nil {
|
||||
break
|
||||
}
|
||||
e.Names.pop()
|
||||
if !e.Flags.Get(jsonflags.AllowDuplicateNames) {
|
||||
e.Namespaces.pop()
|
||||
}
|
||||
case '[':
|
||||
b = append(b, '[')
|
||||
err = e.Tokens.pushArray()
|
||||
case ']':
|
||||
b = append(b, ']')
|
||||
err = e.Tokens.popArray()
|
||||
default:
|
||||
err = errInvalidToken
|
||||
}
|
||||
if err != nil {
|
||||
return wrapSyntacticError(e, err, pos, +1)
|
||||
}
|
||||
|
||||
// Finish off the buffer and store it back into e.
|
||||
e.Buf = b
|
||||
if e.NeedFlush() {
|
||||
return e.Flush()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AppendRaw appends either a raw string (without double quotes) or number.
|
||||
// Specify safeASCII if the string output is guaranteed to be ASCII
|
||||
// without any characters (including '<', '>', and '&') that need escaping,
|
||||
// otherwise this will validate whether the string needs escaping.
|
||||
// The appended bytes for a JSON number must be valid.
|
||||
//
|
||||
// This is a specialized implementation of Encoder.WriteValue
|
||||
// that allows appending directly into the buffer.
|
||||
// It is only called from marshal logic in the "json" package.
|
||||
func (e *encoderState) AppendRaw(k Kind, safeASCII bool, appendFn func([]byte) ([]byte, error)) error {
|
||||
b := e.Buf // use local variable to avoid mutating e in case of error
|
||||
|
||||
// Append any delimiters or optional whitespace.
|
||||
b = e.Tokens.MayAppendDelim(b, k)
|
||||
if e.Flags.Get(jsonflags.AnyWhitespace) {
|
||||
b = e.appendWhitespace(b, k)
|
||||
}
|
||||
pos := len(b) // offset before the token
|
||||
|
||||
var err error
|
||||
switch k {
|
||||
case '"':
|
||||
// Append directly into the encoder buffer by assuming that
|
||||
// most of the time none of the characters need escaping.
|
||||
b = append(b, '"')
|
||||
if b, err = appendFn(b); err != nil {
|
||||
return err
|
||||
}
|
||||
b = append(b, '"')
|
||||
|
||||
// Check whether we need to escape the string and if necessary
|
||||
// copy it to a scratch buffer and then escape it back.
|
||||
isVerbatim := safeASCII || !jsonwire.NeedEscape(b[pos+len(`"`):len(b)-len(`"`)])
|
||||
if !isVerbatim {
|
||||
var err error
|
||||
b2 := append(e.unusedCache, b[pos+len(`"`):len(b)-len(`"`)]...)
|
||||
b, err = jsonwire.AppendQuote(b[:pos], string(b2), &e.Flags)
|
||||
e.unusedCache = b2[:0]
|
||||
if err != nil {
|
||||
return wrapSyntacticError(e, err, pos, +1)
|
||||
}
|
||||
}
|
||||
|
||||
// Update the state machine.
|
||||
if e.Tokens.Last.NeedObjectName() {
|
||||
if !e.Flags.Get(jsonflags.AllowDuplicateNames) {
|
||||
if !e.Tokens.Last.isValidNamespace() {
|
||||
return wrapSyntacticError(e, err, pos, +1)
|
||||
}
|
||||
if e.Tokens.Last.isActiveNamespace() && !e.Namespaces.Last().insertQuoted(b[pos:], isVerbatim) {
|
||||
err = wrapWithObjectName(ErrDuplicateName, b[pos:])
|
||||
return wrapSyntacticError(e, err, pos, +1)
|
||||
}
|
||||
}
|
||||
e.Names.ReplaceLastQuotedOffset(pos) // only replace if insertQuoted succeeds
|
||||
}
|
||||
if err := e.Tokens.appendString(); err != nil {
|
||||
return wrapSyntacticError(e, err, pos, +1)
|
||||
}
|
||||
case '0':
|
||||
if b, err = appendFn(b); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := e.Tokens.appendNumber(); err != nil {
|
||||
return wrapSyntacticError(e, err, pos, +1)
|
||||
}
|
||||
default:
|
||||
panic("BUG: invalid kind")
|
||||
}
|
||||
|
||||
// Finish off the buffer and store it back into e.
|
||||
e.Buf = b
|
||||
if e.NeedFlush() {
|
||||
return e.Flush()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteValue writes the next raw value and advances the internal write offset.
|
||||
// The Encoder does not simply copy the provided value verbatim, but
|
||||
// parses it to ensure that it is syntactically valid and reformats it
|
||||
// according to how the Encoder is configured to format whitespace and strings.
|
||||
// If [AllowInvalidUTF8] is specified, then any invalid UTF-8 is mangled
|
||||
// as the Unicode replacement character, U+FFFD.
|
||||
//
|
||||
// The provided value kind must be consistent with the JSON grammar
|
||||
// (see examples on [Encoder.WriteToken]). If the provided value is invalid,
|
||||
// then it reports a [SyntacticError] and the internal state remains unchanged.
|
||||
// The offset reported in [SyntacticError] will be relative to the
|
||||
// [Encoder.OutputOffset] plus the offset into v of any encountered syntax error.
|
||||
func (e *Encoder) WriteValue(v Value) error {
|
||||
return e.s.WriteValue(v)
|
||||
}
|
||||
func (e *encoderState) WriteValue(v Value) error {
|
||||
e.maxValue |= len(v) // bitwise OR is a fast approximation of max
|
||||
|
||||
k := v.Kind()
|
||||
b := e.Buf // use local variable to avoid mutating e in case of error
|
||||
|
||||
// Append any delimiters or optional whitespace.
|
||||
b = e.Tokens.MayAppendDelim(b, k)
|
||||
if e.Flags.Get(jsonflags.AnyWhitespace) {
|
||||
b = e.appendWhitespace(b, k)
|
||||
}
|
||||
pos := len(b) // offset before the value
|
||||
|
||||
// Append the value the output.
|
||||
var n int
|
||||
n += jsonwire.ConsumeWhitespace(v[n:])
|
||||
b, m, err := e.reformatValue(b, v[n:], e.Tokens.Depth())
|
||||
if err != nil {
|
||||
return wrapSyntacticError(e, err, pos+n+m, +1)
|
||||
}
|
||||
n += m
|
||||
n += jsonwire.ConsumeWhitespace(v[n:])
|
||||
if len(v) > n {
|
||||
err = jsonwire.NewInvalidCharacterError(v[n:], "after top-level value")
|
||||
return wrapSyntacticError(e, err, pos+n, 0)
|
||||
}
|
||||
|
||||
// Append the kind to the state machine.
|
||||
switch k {
|
||||
case 'n', 'f', 't':
|
||||
err = e.Tokens.appendLiteral()
|
||||
case '"':
|
||||
if e.Tokens.Last.NeedObjectName() {
|
||||
if !e.Flags.Get(jsonflags.AllowDuplicateNames) {
|
||||
if !e.Tokens.Last.isValidNamespace() {
|
||||
err = errInvalidNamespace
|
||||
break
|
||||
}
|
||||
if e.Tokens.Last.isActiveNamespace() && !e.Namespaces.Last().insertQuoted(b[pos:], false) {
|
||||
err = wrapWithObjectName(ErrDuplicateName, b[pos:])
|
||||
break
|
||||
}
|
||||
}
|
||||
e.Names.ReplaceLastQuotedOffset(pos) // only replace if insertQuoted succeeds
|
||||
}
|
||||
err = e.Tokens.appendString()
|
||||
case '0':
|
||||
err = e.Tokens.appendNumber()
|
||||
case '{':
|
||||
if err = e.Tokens.pushObject(); err != nil {
|
||||
break
|
||||
}
|
||||
if err = e.Tokens.popObject(); err != nil {
|
||||
panic("BUG: popObject should never fail immediately after pushObject: " + err.Error())
|
||||
}
|
||||
if e.Flags.Get(jsonflags.ReorderRawObjects) {
|
||||
mustReorderObjects(b[pos:])
|
||||
}
|
||||
case '[':
|
||||
if err = e.Tokens.pushArray(); err != nil {
|
||||
break
|
||||
}
|
||||
if err = e.Tokens.popArray(); err != nil {
|
||||
panic("BUG: popArray should never fail immediately after pushArray: " + err.Error())
|
||||
}
|
||||
if e.Flags.Get(jsonflags.ReorderRawObjects) {
|
||||
mustReorderObjects(b[pos:])
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return wrapSyntacticError(e, err, pos, +1)
|
||||
}
|
||||
|
||||
// Finish off the buffer and store it back into e.
|
||||
e.Buf = b
|
||||
if e.NeedFlush() {
|
||||
return e.Flush()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CountNextDelimWhitespace counts the number of bytes of delimiter and
|
||||
// whitespace bytes assuming the upcoming token is a JSON value.
|
||||
// This method is used for error reporting at the semantic layer.
|
||||
func (e *encoderState) CountNextDelimWhitespace() (n int) {
|
||||
const next = Kind('"') // arbitrary kind as next JSON value
|
||||
delim := e.Tokens.needDelim(next)
|
||||
if delim > 0 {
|
||||
n += len(",") | len(":")
|
||||
}
|
||||
if delim == ':' {
|
||||
if e.Flags.Get(jsonflags.SpaceAfterColon) {
|
||||
n += len(" ")
|
||||
}
|
||||
} else {
|
||||
if delim == ',' && e.Flags.Get(jsonflags.SpaceAfterComma) {
|
||||
n += len(" ")
|
||||
}
|
||||
if e.Flags.Get(jsonflags.Multiline) {
|
||||
if m := e.Tokens.NeedIndent(next); m > 0 {
|
||||
n += len("\n") + len(e.IndentPrefix) + (m-1)*len(e.Indent)
|
||||
}
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// appendWhitespace appends whitespace that immediately precedes the next token.
|
||||
func (e *encoderState) appendWhitespace(b []byte, next Kind) []byte {
|
||||
if delim := e.Tokens.needDelim(next); delim == ':' {
|
||||
if e.Flags.Get(jsonflags.SpaceAfterColon) {
|
||||
b = append(b, ' ')
|
||||
}
|
||||
} else {
|
||||
if delim == ',' && e.Flags.Get(jsonflags.SpaceAfterComma) {
|
||||
b = append(b, ' ')
|
||||
}
|
||||
if e.Flags.Get(jsonflags.Multiline) {
|
||||
b = e.AppendIndent(b, e.Tokens.NeedIndent(next))
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// AppendIndent appends the appropriate number of indentation characters
|
||||
// for the current nested level, n.
|
||||
func (e *encoderState) AppendIndent(b []byte, n int) []byte {
|
||||
if n == 0 {
|
||||
return b
|
||||
}
|
||||
b = append(b, '\n')
|
||||
b = append(b, e.IndentPrefix...)
|
||||
for ; n > 1; n-- {
|
||||
b = append(b, e.Indent...)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// reformatValue parses a JSON value from the start of src and
|
||||
// appends it to the end of dst, reformatting whitespace and strings as needed.
|
||||
// It returns the extended dst buffer and the number of consumed input bytes.
|
||||
func (e *encoderState) reformatValue(dst []byte, src Value, depth int) ([]byte, int, error) {
|
||||
// TODO: Should this update ValueFlags as input?
|
||||
if len(src) == 0 {
|
||||
return dst, 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
switch k := Kind(src[0]).normalize(); k {
|
||||
case 'n':
|
||||
if jsonwire.ConsumeNull(src) == 0 {
|
||||
n, err := jsonwire.ConsumeLiteral(src, "null")
|
||||
return dst, n, err
|
||||
}
|
||||
return append(dst, "null"...), len("null"), nil
|
||||
case 'f':
|
||||
if jsonwire.ConsumeFalse(src) == 0 {
|
||||
n, err := jsonwire.ConsumeLiteral(src, "false")
|
||||
return dst, n, err
|
||||
}
|
||||
return append(dst, "false"...), len("false"), nil
|
||||
case 't':
|
||||
if jsonwire.ConsumeTrue(src) == 0 {
|
||||
n, err := jsonwire.ConsumeLiteral(src, "true")
|
||||
return dst, n, err
|
||||
}
|
||||
return append(dst, "true"...), len("true"), nil
|
||||
case '"':
|
||||
if n := jsonwire.ConsumeSimpleString(src); n > 0 {
|
||||
dst = append(dst, src[:n]...) // copy simple strings verbatim
|
||||
return dst, n, nil
|
||||
}
|
||||
return jsonwire.ReformatString(dst, src, &e.Flags)
|
||||
case '0':
|
||||
if n := jsonwire.ConsumeSimpleNumber(src); n > 0 && !e.Flags.Get(jsonflags.CanonicalizeNumbers) {
|
||||
dst = append(dst, src[:n]...) // copy simple numbers verbatim
|
||||
return dst, n, nil
|
||||
}
|
||||
return jsonwire.ReformatNumber(dst, src, &e.Flags)
|
||||
case '{':
|
||||
return e.reformatObject(dst, src, depth)
|
||||
case '[':
|
||||
return e.reformatArray(dst, src, depth)
|
||||
default:
|
||||
return dst, 0, jsonwire.NewInvalidCharacterError(src, "at start of value")
|
||||
}
|
||||
}
|
||||
|
||||
// reformatObject parses a JSON object from the start of src and
|
||||
// appends it to the end of src, reformatting whitespace and strings as needed.
|
||||
// It returns the extended dst buffer and the number of consumed input bytes.
|
||||
func (e *encoderState) reformatObject(dst []byte, src Value, depth int) ([]byte, int, error) {
|
||||
// Append object start.
|
||||
if len(src) == 0 || src[0] != '{' {
|
||||
panic("BUG: reformatObject must be called with a buffer that starts with '{'")
|
||||
} else if depth == maxNestingDepth+1 {
|
||||
return dst, 0, errMaxDepth
|
||||
}
|
||||
dst = append(dst, '{')
|
||||
n := len("{")
|
||||
|
||||
// Append (possible) object end.
|
||||
n += jsonwire.ConsumeWhitespace(src[n:])
|
||||
if uint(len(src)) <= uint(n) {
|
||||
return dst, n, io.ErrUnexpectedEOF
|
||||
}
|
||||
if src[n] == '}' {
|
||||
dst = append(dst, '}')
|
||||
n += len("}")
|
||||
return dst, n, nil
|
||||
}
|
||||
|
||||
var err error
|
||||
var names *objectNamespace
|
||||
if !e.Flags.Get(jsonflags.AllowDuplicateNames) {
|
||||
e.Namespaces.push()
|
||||
defer e.Namespaces.pop()
|
||||
names = e.Namespaces.Last()
|
||||
}
|
||||
depth++
|
||||
for {
|
||||
// Append optional newline and indentation.
|
||||
if e.Flags.Get(jsonflags.Multiline) {
|
||||
dst = e.AppendIndent(dst, depth)
|
||||
}
|
||||
|
||||
// Append object name.
|
||||
n += jsonwire.ConsumeWhitespace(src[n:])
|
||||
if uint(len(src)) <= uint(n) {
|
||||
return dst, n, io.ErrUnexpectedEOF
|
||||
}
|
||||
m := jsonwire.ConsumeSimpleString(src[n:])
|
||||
isVerbatim := m > 0
|
||||
if isVerbatim {
|
||||
dst = append(dst, src[n:n+m]...)
|
||||
} else {
|
||||
dst, m, err = jsonwire.ReformatString(dst, src[n:], &e.Flags)
|
||||
if err != nil {
|
||||
return dst, n + m, err
|
||||
}
|
||||
}
|
||||
quotedName := src[n : n+m]
|
||||
if !e.Flags.Get(jsonflags.AllowDuplicateNames) && !names.insertQuoted(quotedName, isVerbatim) {
|
||||
return dst, n, wrapWithObjectName(ErrDuplicateName, quotedName)
|
||||
}
|
||||
n += m
|
||||
|
||||
// Append colon.
|
||||
n += jsonwire.ConsumeWhitespace(src[n:])
|
||||
if uint(len(src)) <= uint(n) {
|
||||
return dst, n, wrapWithObjectName(io.ErrUnexpectedEOF, quotedName)
|
||||
}
|
||||
if src[n] != ':' {
|
||||
err = jsonwire.NewInvalidCharacterError(src[n:], "after object name (expecting ':')")
|
||||
return dst, n, wrapWithObjectName(err, quotedName)
|
||||
}
|
||||
dst = append(dst, ':')
|
||||
n += len(":")
|
||||
if e.Flags.Get(jsonflags.SpaceAfterColon) {
|
||||
dst = append(dst, ' ')
|
||||
}
|
||||
|
||||
// Append object value.
|
||||
n += jsonwire.ConsumeWhitespace(src[n:])
|
||||
if uint(len(src)) <= uint(n) {
|
||||
return dst, n, wrapWithObjectName(io.ErrUnexpectedEOF, quotedName)
|
||||
}
|
||||
dst, m, err = e.reformatValue(dst, src[n:], depth)
|
||||
if err != nil {
|
||||
return dst, n + m, wrapWithObjectName(err, quotedName)
|
||||
}
|
||||
n += m
|
||||
|
||||
// Append comma or object end.
|
||||
n += jsonwire.ConsumeWhitespace(src[n:])
|
||||
if uint(len(src)) <= uint(n) {
|
||||
return dst, n, io.ErrUnexpectedEOF
|
||||
}
|
||||
switch src[n] {
|
||||
case ',':
|
||||
dst = append(dst, ',')
|
||||
if e.Flags.Get(jsonflags.SpaceAfterComma) {
|
||||
dst = append(dst, ' ')
|
||||
}
|
||||
n += len(",")
|
||||
continue
|
||||
case '}':
|
||||
if e.Flags.Get(jsonflags.Multiline) {
|
||||
dst = e.AppendIndent(dst, depth-1)
|
||||
}
|
||||
dst = append(dst, '}')
|
||||
n += len("}")
|
||||
return dst, n, nil
|
||||
default:
|
||||
return dst, n, jsonwire.NewInvalidCharacterError(src[n:], "after object value (expecting ',' or '}')")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// reformatArray parses a JSON array from the start of src and
|
||||
// appends it to the end of dst, reformatting whitespace and strings as needed.
|
||||
// It returns the extended dst buffer and the number of consumed input bytes.
|
||||
func (e *encoderState) reformatArray(dst []byte, src Value, depth int) ([]byte, int, error) {
|
||||
// Append array start.
|
||||
if len(src) == 0 || src[0] != '[' {
|
||||
panic("BUG: reformatArray must be called with a buffer that starts with '['")
|
||||
} else if depth == maxNestingDepth+1 {
|
||||
return dst, 0, errMaxDepth
|
||||
}
|
||||
dst = append(dst, '[')
|
||||
n := len("[")
|
||||
|
||||
// Append (possible) array end.
|
||||
n += jsonwire.ConsumeWhitespace(src[n:])
|
||||
if uint(len(src)) <= uint(n) {
|
||||
return dst, n, io.ErrUnexpectedEOF
|
||||
}
|
||||
if src[n] == ']' {
|
||||
dst = append(dst, ']')
|
||||
n += len("]")
|
||||
return dst, n, nil
|
||||
}
|
||||
|
||||
var idx int64
|
||||
var err error
|
||||
depth++
|
||||
for {
|
||||
// Append optional newline and indentation.
|
||||
if e.Flags.Get(jsonflags.Multiline) {
|
||||
dst = e.AppendIndent(dst, depth)
|
||||
}
|
||||
|
||||
// Append array value.
|
||||
n += jsonwire.ConsumeWhitespace(src[n:])
|
||||
if uint(len(src)) <= uint(n) {
|
||||
return dst, n, io.ErrUnexpectedEOF
|
||||
}
|
||||
var m int
|
||||
dst, m, err = e.reformatValue(dst, src[n:], depth)
|
||||
if err != nil {
|
||||
return dst, n + m, wrapWithArrayIndex(err, idx)
|
||||
}
|
||||
n += m
|
||||
|
||||
// Append comma or array end.
|
||||
n += jsonwire.ConsumeWhitespace(src[n:])
|
||||
if uint(len(src)) <= uint(n) {
|
||||
return dst, n, io.ErrUnexpectedEOF
|
||||
}
|
||||
switch src[n] {
|
||||
case ',':
|
||||
dst = append(dst, ',')
|
||||
if e.Flags.Get(jsonflags.SpaceAfterComma) {
|
||||
dst = append(dst, ' ')
|
||||
}
|
||||
n += len(",")
|
||||
idx++
|
||||
continue
|
||||
case ']':
|
||||
if e.Flags.Get(jsonflags.Multiline) {
|
||||
dst = e.AppendIndent(dst, depth-1)
|
||||
}
|
||||
dst = append(dst, ']')
|
||||
n += len("]")
|
||||
return dst, n, nil
|
||||
default:
|
||||
return dst, n, jsonwire.NewInvalidCharacterError(src[n:], "after array value (expecting ',' or ']')")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OutputOffset returns the current output byte offset. It gives the location
|
||||
// of the next byte immediately after the most recently written token or value.
|
||||
// The number of bytes actually written to the underlying [io.Writer] may be less
|
||||
// than this offset due to internal buffering effects.
|
||||
func (e *Encoder) OutputOffset() int64 {
|
||||
return e.s.previousOffsetEnd()
|
||||
}
|
||||
|
||||
// UnusedBuffer returns a zero-length buffer with a possible non-zero capacity.
|
||||
// This buffer is intended to be used to populate a [Value]
|
||||
// being passed to an immediately succeeding [Encoder.WriteValue] call.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// b := d.UnusedBuffer()
|
||||
// b = append(b, '"')
|
||||
// b = appendString(b, v) // append the string formatting of v
|
||||
// b = append(b, '"')
|
||||
// ... := d.WriteValue(b)
|
||||
//
|
||||
// It is the user's responsibility to ensure that the value is valid JSON.
|
||||
func (e *Encoder) UnusedBuffer() []byte {
|
||||
// NOTE: We don't return e.buf[len(e.buf):cap(e.buf)] since WriteValue would
|
||||
// need to take special care to avoid mangling the data while reformatting.
|
||||
// WriteValue can't easily identify whether the input Value aliases e.buf
|
||||
// without using unsafe.Pointer. Thus, we just return a different buffer.
|
||||
// Should this ever alias e.buf, we need to consider how it operates with
|
||||
// the specialized performance optimization for bytes.Buffer.
|
||||
n := 1 << bits.Len(uint(e.s.maxValue|63)) // fast approximation for max length
|
||||
if cap(e.s.unusedCache) < n {
|
||||
e.s.unusedCache = make([]byte, 0, n)
|
||||
}
|
||||
return e.s.unusedCache
|
||||
}
|
||||
|
||||
// StackDepth returns the depth of the state machine for written JSON data.
|
||||
// Each level on the stack represents a nested JSON object or array.
|
||||
// It is incremented whenever an [BeginObject] or [BeginArray] token is encountered
|
||||
// and decremented whenever an [EndObject] or [EndArray] token is encountered.
|
||||
// The depth is zero-indexed, where zero represents the top-level JSON value.
|
||||
func (e *Encoder) StackDepth() int {
|
||||
// NOTE: Keep in sync with Decoder.StackDepth.
|
||||
return e.s.Tokens.Depth() - 1
|
||||
}
|
||||
|
||||
// StackIndex returns information about the specified stack level.
|
||||
// It must be a number between 0 and [Encoder.StackDepth], inclusive.
|
||||
// For each level, it reports the kind:
|
||||
//
|
||||
// - 0 for a level of zero,
|
||||
// - '{' for a level representing a JSON object, and
|
||||
// - '[' for a level representing a JSON array.
|
||||
//
|
||||
// It also reports the length of that JSON object or array.
|
||||
// Each name and value in a JSON object is counted separately,
|
||||
// so the effective number of members would be half the length.
|
||||
// A complete JSON object must have an even length.
|
||||
func (e *Encoder) StackIndex(i int) (Kind, int64) {
|
||||
// NOTE: Keep in sync with Decoder.StackIndex.
|
||||
switch s := e.s.Tokens.index(i); {
|
||||
case i > 0 && s.isObject():
|
||||
return '{', s.Length()
|
||||
case i > 0 && s.isArray():
|
||||
return '[', s.Length()
|
||||
default:
|
||||
return 0, s.Length()
|
||||
}
|
||||
}
|
||||
|
||||
// StackPointer returns a JSON Pointer (RFC 6901) to the most recently written value.
|
||||
func (e *Encoder) StackPointer() Pointer {
|
||||
return Pointer(e.s.AppendStackPointer(nil, -1))
|
||||
}
|
||||
|
||||
func (e *encoderState) AppendStackPointer(b []byte, where int) []byte {
|
||||
e.Names.copyQuotedBuffer(e.Buf)
|
||||
return e.state.appendStackPointer(b, where)
|
||||
}
|
180
vendor/github.com/go-json-experiment/json/jsontext/errors.go
generated
vendored
Normal file
180
vendor/github.com/go-json-experiment/json/jsontext/errors.go
generated
vendored
Normal file
@ -0,0 +1,180 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package jsontext
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-json-experiment/json/internal/jsonwire"
|
||||
)
|
||||
|
||||
const errorPrefix = "jsontext: "
|
||||
|
||||
type ioError struct {
|
||||
action string // either "read" or "write"
|
||||
err error
|
||||
}
|
||||
|
||||
func (e *ioError) Error() string {
|
||||
return errorPrefix + e.action + " error: " + e.err.Error()
|
||||
}
|
||||
func (e *ioError) Unwrap() error {
|
||||
return e.err
|
||||
}
|
||||
|
||||
// SyntacticError is a description of a syntactic error that occurred when
|
||||
// encoding or decoding JSON according to the grammar.
|
||||
//
|
||||
// The contents of this error as produced by this package may change over time.
|
||||
type SyntacticError struct {
|
||||
requireKeyedLiterals
|
||||
nonComparable
|
||||
|
||||
// ByteOffset indicates that an error occurred after this byte offset.
|
||||
ByteOffset int64
|
||||
// JSONPointer indicates that an error occurred within this JSON value
|
||||
// as indicated using the JSON Pointer notation (see RFC 6901).
|
||||
JSONPointer Pointer
|
||||
|
||||
// Err is the underlying error.
|
||||
Err error
|
||||
}
|
||||
|
||||
// wrapSyntacticError wraps an error and annotates it with a precise location
|
||||
// using the provided [encoderState] or [decoderState].
|
||||
// If err is an [ioError] or [io.EOF], then it is not wrapped.
|
||||
//
|
||||
// It takes a relative offset pos that can be resolved into
|
||||
// an absolute offset using state.offsetAt.
|
||||
//
|
||||
// It takes a where that specify how the JSON pointer is derived.
|
||||
// If the underlying error is a [pointerSuffixError],
|
||||
// then the suffix is appended to the derived pointer.
|
||||
func wrapSyntacticError(state interface {
|
||||
offsetAt(pos int) int64
|
||||
AppendStackPointer(b []byte, where int) []byte
|
||||
}, err error, pos, where int) error {
|
||||
if _, ok := err.(*ioError); err == io.EOF || ok {
|
||||
return err
|
||||
}
|
||||
offset := state.offsetAt(pos)
|
||||
ptr := state.AppendStackPointer(nil, where)
|
||||
if serr, ok := err.(*pointerSuffixError); ok {
|
||||
ptr = serr.appendPointer(ptr)
|
||||
err = serr.error
|
||||
}
|
||||
if d, ok := state.(*decoderState); ok && err == errMismatchDelim {
|
||||
where := "at start of value"
|
||||
if len(d.Tokens.Stack) > 0 && d.Tokens.Last.Length() > 0 {
|
||||
switch {
|
||||
case d.Tokens.Last.isArray():
|
||||
where = "after array element (expecting ',' or ']')"
|
||||
ptr = []byte(Pointer(ptr).Parent()) // problem is with parent array
|
||||
case d.Tokens.Last.isObject():
|
||||
where = "after object value (expecting ',' or '}')"
|
||||
ptr = []byte(Pointer(ptr).Parent()) // problem is with parent object
|
||||
}
|
||||
}
|
||||
err = jsonwire.NewInvalidCharacterError(d.buf[pos:], where)
|
||||
}
|
||||
return &SyntacticError{ByteOffset: offset, JSONPointer: Pointer(ptr), Err: err}
|
||||
}
|
||||
|
||||
func (e *SyntacticError) Error() string {
|
||||
pointer := e.JSONPointer
|
||||
offset := e.ByteOffset
|
||||
b := []byte(errorPrefix)
|
||||
if e.Err != nil {
|
||||
b = append(b, e.Err.Error()...)
|
||||
if e.Err == ErrDuplicateName {
|
||||
b = strconv.AppendQuote(append(b, ' '), pointer.LastToken())
|
||||
pointer = pointer.Parent()
|
||||
offset = 0 // not useful to print offset for duplicate names
|
||||
}
|
||||
} else {
|
||||
b = append(b, "syntactic error"...)
|
||||
}
|
||||
if pointer != "" {
|
||||
b = strconv.AppendQuote(append(b, " within "...), jsonwire.TruncatePointer(string(pointer), 100))
|
||||
}
|
||||
if offset > 0 {
|
||||
b = strconv.AppendInt(append(b, " after offset "...), offset, 10)
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func (e *SyntacticError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// pointerSuffixError represents a JSON pointer suffix to be appended
|
||||
// to [SyntacticError.JSONPointer]. It is an internal error type
|
||||
// used within this package and does not appear in the public API.
|
||||
//
|
||||
// This type is primarily used to annotate errors in Encoder.WriteValue
|
||||
// and Decoder.ReadValue with precise positions.
|
||||
// At the time WriteValue or ReadValue is called, a JSON pointer to the
|
||||
// upcoming value can be constructed using the Encoder/Decoder state.
|
||||
// However, tracking pointers within values during normal operation
|
||||
// would incur a performance penalty in the error-free case.
|
||||
//
|
||||
// To provide precise error locations without this overhead,
|
||||
// the error is wrapped with object names or array indices
|
||||
// as the call stack is popped when an error occurs.
|
||||
// Since this happens in reverse order, pointerSuffixError holds
|
||||
// the pointer in reverse and is only later reversed when appending to
|
||||
// the pointer prefix.
|
||||
//
|
||||
// For example, if the encoder is at "/alpha/bravo/charlie"
|
||||
// and an error occurs in WriteValue at "/xray/yankee/zulu", then
|
||||
// the final pointer should be "/alpha/bravo/charlie/xray/yankee/zulu".
|
||||
//
|
||||
// As pointerSuffixError is populated during the error return path,
|
||||
// it first contains "/zulu", then "/zulu/yankee",
|
||||
// and finally "/zulu/yankee/xray".
|
||||
// These tokens are reversed and concatenated to "/alpha/bravo/charlie"
|
||||
// to form the full pointer.
|
||||
type pointerSuffixError struct {
|
||||
error
|
||||
|
||||
// reversePointer is a JSON pointer, but with each token in reverse order.
|
||||
reversePointer []byte
|
||||
}
|
||||
|
||||
// wrapWithObjectName wraps err with a JSON object name access,
|
||||
// which must be a valid quoted JSON string.
|
||||
func wrapWithObjectName(err error, quotedName []byte) error {
|
||||
serr, _ := err.(*pointerSuffixError)
|
||||
if serr == nil {
|
||||
serr = &pointerSuffixError{error: err}
|
||||
}
|
||||
name := jsonwire.UnquoteMayCopy(quotedName, false)
|
||||
serr.reversePointer = appendEscapePointerName(append(serr.reversePointer, '/'), name)
|
||||
return serr
|
||||
}
|
||||
|
||||
// wrapWithArrayIndex wraps err with a JSON array index access.
|
||||
func wrapWithArrayIndex(err error, index int64) error {
|
||||
serr, _ := err.(*pointerSuffixError)
|
||||
if serr == nil {
|
||||
serr = &pointerSuffixError{error: err}
|
||||
}
|
||||
serr.reversePointer = strconv.AppendUint(append(serr.reversePointer, '/'), uint64(index), 10)
|
||||
return serr
|
||||
}
|
||||
|
||||
// appendPointer appends the path encoded in e to the end of pointer.
|
||||
func (e *pointerSuffixError) appendPointer(pointer []byte) []byte {
|
||||
// Copy each token in reversePointer to the end of pointer in reverse order.
|
||||
// Double reversal means that the appended suffix is now in forward order.
|
||||
bi, bo := e.reversePointer, pointer
|
||||
for len(bi) > 0 {
|
||||
i := bytes.LastIndexByte(bi, '/')
|
||||
bi, bo = bi[:i], append(bo, bi[i:]...)
|
||||
}
|
||||
return bo
|
||||
}
|
75
vendor/github.com/go-json-experiment/json/jsontext/export.go
generated
vendored
Normal file
75
vendor/github.com/go-json-experiment/json/jsontext/export.go
generated
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
// Copyright 2023 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package jsontext
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/go-json-experiment/json/internal"
|
||||
)
|
||||
|
||||
// Internal is for internal use only.
|
||||
// This is exempt from the Go compatibility agreement.
|
||||
var Internal exporter
|
||||
|
||||
type exporter struct{}
|
||||
|
||||
// Export exposes internal functionality from "jsontext" to "json".
|
||||
// This cannot be dynamically called by other packages since
|
||||
// they cannot obtain a reference to the internal.AllowInternalUse value.
|
||||
func (exporter) Export(p *internal.NotForPublicUse) export {
|
||||
if p != &internal.AllowInternalUse {
|
||||
panic("unauthorized call to Export")
|
||||
}
|
||||
return export{}
|
||||
}
|
||||
|
||||
// The export type exposes functionality to packages with visibility to
|
||||
// the internal.AllowInternalUse variable. The "json" package uses this
|
||||
// to modify low-level state in the Encoder and Decoder types.
|
||||
// It mutates the state directly instead of calling ReadToken or WriteToken
|
||||
// since this is more performant. The public APIs need to track state to ensure
|
||||
// that users are constructing a valid JSON value, but the "json" implementation
|
||||
// guarantees that it emits valid JSON by the structure of the code itself.
|
||||
type export struct{}
|
||||
|
||||
// Encoder returns a pointer to the underlying encoderState.
|
||||
func (export) Encoder(e *Encoder) *encoderState { return &e.s }
|
||||
|
||||
// Decoder returns a pointer to the underlying decoderState.
|
||||
func (export) Decoder(d *Decoder) *decoderState { return &d.s }
|
||||
|
||||
func (export) GetBufferedEncoder(o ...Options) *Encoder {
|
||||
return getBufferedEncoder(o...)
|
||||
}
|
||||
func (export) PutBufferedEncoder(e *Encoder) {
|
||||
putBufferedEncoder(e)
|
||||
}
|
||||
|
||||
func (export) GetStreamingEncoder(w io.Writer, o ...Options) *Encoder {
|
||||
return getStreamingEncoder(w, o...)
|
||||
}
|
||||
func (export) PutStreamingEncoder(e *Encoder) {
|
||||
putStreamingEncoder(e)
|
||||
}
|
||||
|
||||
func (export) GetBufferedDecoder(b []byte, o ...Options) *Decoder {
|
||||
return getBufferedDecoder(b, o...)
|
||||
}
|
||||
func (export) PutBufferedDecoder(d *Decoder) {
|
||||
putBufferedDecoder(d)
|
||||
}
|
||||
|
||||
func (export) GetStreamingDecoder(r io.Reader, o ...Options) *Decoder {
|
||||
return getStreamingDecoder(r, o...)
|
||||
}
|
||||
func (export) PutStreamingDecoder(d *Decoder) {
|
||||
putStreamingDecoder(d)
|
||||
}
|
||||
|
||||
func (export) IsIOError(err error) bool {
|
||||
_, ok := err.(*ioError)
|
||||
return ok
|
||||
}
|
301
vendor/github.com/go-json-experiment/json/jsontext/options.go
generated
vendored
Normal file
301
vendor/github.com/go-json-experiment/json/jsontext/options.go
generated
vendored
Normal file
@ -0,0 +1,301 @@
|
||||
// Copyright 2023 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package jsontext
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-json-experiment/json/internal/jsonflags"
|
||||
"github.com/go-json-experiment/json/internal/jsonopts"
|
||||
"github.com/go-json-experiment/json/internal/jsonwire"
|
||||
)
|
||||
|
||||
// Options configures [NewEncoder], [Encoder.Reset], [NewDecoder],
|
||||
// and [Decoder.Reset] with specific features.
|
||||
// Each function takes in a variadic list of options, where properties
|
||||
// set in latter options override the value of previously set properties.
|
||||
//
|
||||
// There is a single Options type, which is used with both encoding and decoding.
|
||||
// Some options affect both operations, while others only affect one operation:
|
||||
//
|
||||
// - [AllowDuplicateNames] affects encoding and decoding
|
||||
// - [AllowInvalidUTF8] affects encoding and decoding
|
||||
// - [EscapeForHTML] affects encoding only
|
||||
// - [EscapeForJS] affects encoding only
|
||||
// - [PreserveRawStrings] affects encoding only
|
||||
// - [CanonicalizeRawInts] affects encoding only
|
||||
// - [CanonicalizeRawFloats] affects encoding only
|
||||
// - [ReorderRawObjects] affects encoding only
|
||||
// - [SpaceAfterColon] affects encoding only
|
||||
// - [SpaceAfterComma] affects encoding only
|
||||
// - [Multiline] affects encoding only
|
||||
// - [WithIndent] affects encoding only
|
||||
// - [WithIndentPrefix] affects encoding only
|
||||
//
|
||||
// Options that do not affect a particular operation are ignored.
|
||||
//
|
||||
// The Options type is identical to [encoding/json.Options] and
|
||||
// [encoding/json/v2.Options]. Options from the other packages may
|
||||
// be passed to functionality in this package, but are ignored.
|
||||
// Options from this package may be used with the other packages.
|
||||
type Options = jsonopts.Options
|
||||
|
||||
// AllowDuplicateNames specifies that JSON objects may contain
|
||||
// duplicate member names. Disabling the duplicate name check may provide
|
||||
// performance benefits, but breaks compliance with RFC 7493, section 2.3.
|
||||
// The input or output will still be compliant with RFC 8259,
|
||||
// which leaves the handling of duplicate names as unspecified behavior.
|
||||
//
|
||||
// This affects either encoding or decoding.
|
||||
func AllowDuplicateNames(v bool) Options {
|
||||
if v {
|
||||
return jsonflags.AllowDuplicateNames | 1
|
||||
} else {
|
||||
return jsonflags.AllowDuplicateNames | 0
|
||||
}
|
||||
}
|
||||
|
||||
// AllowInvalidUTF8 specifies that JSON strings may contain invalid UTF-8,
|
||||
// which will be mangled as the Unicode replacement character, U+FFFD.
|
||||
// This causes the encoder or decoder to break compliance with
|
||||
// RFC 7493, section 2.1, and RFC 8259, section 8.1.
|
||||
//
|
||||
// This affects either encoding or decoding.
|
||||
func AllowInvalidUTF8(v bool) Options {
|
||||
if v {
|
||||
return jsonflags.AllowInvalidUTF8 | 1
|
||||
} else {
|
||||
return jsonflags.AllowInvalidUTF8 | 0
|
||||
}
|
||||
}
|
||||
|
||||
// EscapeForHTML specifies that '<', '>', and '&' characters within JSON strings
|
||||
// should be escaped as a hexadecimal Unicode codepoint (e.g., \u003c) so that
|
||||
// the output is safe to embed within HTML.
|
||||
//
|
||||
// This only affects encoding and is ignored when decoding.
|
||||
func EscapeForHTML(v bool) Options {
|
||||
if v {
|
||||
return jsonflags.EscapeForHTML | 1
|
||||
} else {
|
||||
return jsonflags.EscapeForHTML | 0
|
||||
}
|
||||
}
|
||||
|
||||
// EscapeForJS specifies that U+2028 and U+2029 characters within JSON strings
|
||||
// should be escaped as a hexadecimal Unicode codepoint (e.g., \u2028) so that
|
||||
// the output is valid to embed within JavaScript. See RFC 8259, section 12.
|
||||
//
|
||||
// This only affects encoding and is ignored when decoding.
|
||||
func EscapeForJS(v bool) Options {
|
||||
if v {
|
||||
return jsonflags.EscapeForJS | 1
|
||||
} else {
|
||||
return jsonflags.EscapeForJS | 0
|
||||
}
|
||||
}
|
||||
|
||||
// PreserveRawStrings specifies that when encoding a raw JSON string in a
|
||||
// [Token] or [Value], pre-escaped sequences
|
||||
// in a JSON string are preserved to the output.
|
||||
// However, raw strings still respect [EscapeForHTML] and [EscapeForJS]
|
||||
// such that the relevant characters are escaped.
|
||||
// If [AllowInvalidUTF8] is enabled, bytes of invalid UTF-8
|
||||
// are preserved to the output.
|
||||
//
|
||||
// This only affects encoding and is ignored when decoding.
|
||||
func PreserveRawStrings(v bool) Options {
|
||||
if v {
|
||||
return jsonflags.PreserveRawStrings | 1
|
||||
} else {
|
||||
return jsonflags.PreserveRawStrings | 0
|
||||
}
|
||||
}
|
||||
|
||||
// CanonicalizeRawInts specifies that when encoding a raw JSON
|
||||
// integer number (i.e., a number without a fraction and exponent) in a
|
||||
// [Token] or [Value], the number is canonicalized
|
||||
// according to RFC 8785, section 3.2.2.3. As a special case,
|
||||
// the number -0 is canonicalized as 0.
|
||||
//
|
||||
// JSON numbers are treated as IEEE 754 double precision numbers.
|
||||
// Any numbers with precision beyond what is representable by that form
|
||||
// will lose their precision when canonicalized. For example,
|
||||
// integer values beyond ±2⁵³ will lose their precision.
|
||||
// For example, 1234567890123456789 is formatted as 1234567890123456800.
|
||||
//
|
||||
// This only affects encoding and is ignored when decoding.
|
||||
func CanonicalizeRawInts(v bool) Options {
|
||||
if v {
|
||||
return jsonflags.CanonicalizeRawInts | 1
|
||||
} else {
|
||||
return jsonflags.CanonicalizeRawInts | 0
|
||||
}
|
||||
}
|
||||
|
||||
// CanonicalizeRawFloats specifies that when encoding a raw JSON
|
||||
// floating-point number (i.e., a number with a fraction or exponent) in a
|
||||
// [Token] or [Value], the number is canonicalized
|
||||
// according to RFC 8785, section 3.2.2.3. As a special case,
|
||||
// the number -0 is canonicalized as 0.
|
||||
//
|
||||
// JSON numbers are treated as IEEE 754 double precision numbers.
|
||||
// It is safe to canonicalize a serialized single precision number and
|
||||
// parse it back as a single precision number and expect the same value.
|
||||
// If a number exceeds ±1.7976931348623157e+308, which is the maximum
|
||||
// finite number, then it saturated at that value and formatted as such.
|
||||
//
|
||||
// This only affects encoding and is ignored when decoding.
|
||||
func CanonicalizeRawFloats(v bool) Options {
|
||||
if v {
|
||||
return jsonflags.CanonicalizeRawFloats | 1
|
||||
} else {
|
||||
return jsonflags.CanonicalizeRawFloats | 0
|
||||
}
|
||||
}
|
||||
|
||||
// ReorderRawObjects specifies that when encoding a raw JSON object in a
|
||||
// [Value], the object members are reordered according to
|
||||
// RFC 8785, section 3.2.3.
|
||||
//
|
||||
// This only affects encoding and is ignored when decoding.
|
||||
func ReorderRawObjects(v bool) Options {
|
||||
if v {
|
||||
return jsonflags.ReorderRawObjects | 1
|
||||
} else {
|
||||
return jsonflags.ReorderRawObjects | 0
|
||||
}
|
||||
}
|
||||
|
||||
// SpaceAfterColon specifies that the JSON output should emit a space character
|
||||
// after each colon separator following a JSON object name.
|
||||
// If false, then no space character appears after the colon separator.
|
||||
//
|
||||
// This only affects encoding and is ignored when decoding.
|
||||
func SpaceAfterColon(v bool) Options {
|
||||
if v {
|
||||
return jsonflags.SpaceAfterColon | 1
|
||||
} else {
|
||||
return jsonflags.SpaceAfterColon | 0
|
||||
}
|
||||
}
|
||||
|
||||
// SpaceAfterComma specifies that the JSON output should emit a space character
|
||||
// after each comma separator following a JSON object value or array element.
|
||||
// If false, then no space character appears after the comma separator.
|
||||
//
|
||||
// This only affects encoding and is ignored when decoding.
|
||||
func SpaceAfterComma(v bool) Options {
|
||||
if v {
|
||||
return jsonflags.SpaceAfterComma | 1
|
||||
} else {
|
||||
return jsonflags.SpaceAfterComma | 0
|
||||
}
|
||||
}
|
||||
|
||||
// Multiline specifies that the JSON output should expand to multiple lines,
|
||||
// where every JSON object member or JSON array element appears on
|
||||
// a new, indented line according to the nesting depth.
|
||||
//
|
||||
// If [SpaceAfterColon] is not specified, then the default is true.
|
||||
// If [SpaceAfterComma] is not specified, then the default is false.
|
||||
// If [WithIndent] is not specified, then the default is "\t".
|
||||
//
|
||||
// If set to false, then the output is a single-line,
|
||||
// where the only whitespace emitted is determined by the current
|
||||
// values of [SpaceAfterColon] and [SpaceAfterComma].
|
||||
//
|
||||
// This only affects encoding and is ignored when decoding.
|
||||
func Multiline(v bool) Options {
|
||||
if v {
|
||||
return jsonflags.Multiline | 1
|
||||
} else {
|
||||
return jsonflags.Multiline | 0
|
||||
}
|
||||
}
|
||||
|
||||
// WithIndent specifies that the encoder should emit multiline output
|
||||
// where each element in a JSON object or array begins on a new, indented line
|
||||
// beginning with the indent prefix (see [WithIndentPrefix])
|
||||
// followed by one or more copies of indent according to the nesting depth.
|
||||
// The indent must only be composed of space or tab characters.
|
||||
//
|
||||
// If the intent to emit indented output without a preference for
|
||||
// the particular indent string, then use [Multiline] instead.
|
||||
//
|
||||
// This only affects encoding and is ignored when decoding.
|
||||
// Use of this option implies [Multiline] being set to true.
|
||||
func WithIndent(indent string) Options {
|
||||
// Fast-path: Return a constant for common indents, which avoids allocating.
|
||||
// These are derived from analyzing the Go module proxy on 2023-07-01.
|
||||
switch indent {
|
||||
case "\t":
|
||||
return jsonopts.Indent("\t") // ~14k usages
|
||||
case " ":
|
||||
return jsonopts.Indent(" ") // ~18k usages
|
||||
case " ":
|
||||
return jsonopts.Indent(" ") // ~1.7k usages
|
||||
case " ":
|
||||
return jsonopts.Indent(" ") // ~52k usages
|
||||
case " ":
|
||||
return jsonopts.Indent(" ") // ~12k usages
|
||||
case "":
|
||||
return jsonopts.Indent("") // ~1.5k usages
|
||||
}
|
||||
|
||||
// Otherwise, allocate for this unique value.
|
||||
if s := strings.Trim(indent, " \t"); len(s) > 0 {
|
||||
panic("json: invalid character " + jsonwire.QuoteRune(s) + " in indent")
|
||||
}
|
||||
return jsonopts.Indent(indent)
|
||||
}
|
||||
|
||||
// WithIndentPrefix specifies that the encoder should emit multiline output
|
||||
// where each element in a JSON object or array begins on a new, indented line
|
||||
// beginning with the indent prefix followed by one or more copies of indent
|
||||
// (see [WithIndent]) according to the nesting depth.
|
||||
// The prefix must only be composed of space or tab characters.
|
||||
//
|
||||
// This only affects encoding and is ignored when decoding.
|
||||
// Use of this option implies [Multiline] being set to true.
|
||||
func WithIndentPrefix(prefix string) Options {
|
||||
if s := strings.Trim(prefix, " \t"); len(s) > 0 {
|
||||
panic("json: invalid character " + jsonwire.QuoteRune(s) + " in indent prefix")
|
||||
}
|
||||
return jsonopts.IndentPrefix(prefix)
|
||||
}
|
||||
|
||||
/*
|
||||
// TODO(https://go.dev/issue/56733): Implement WithByteLimit and WithDepthLimit.
|
||||
|
||||
// WithByteLimit sets a limit on the number of bytes of input or output bytes
|
||||
// that may be consumed or produced for each top-level JSON value.
|
||||
// If a [Decoder] or [Encoder] method call would need to consume/produce
|
||||
// more than a total of n bytes to make progress on the top-level JSON value,
|
||||
// then the call will report an error.
|
||||
// Whitespace before and within the top-level value are counted against the limit.
|
||||
// Whitespace after a top-level value are counted against the limit
|
||||
// for the next top-level value.
|
||||
//
|
||||
// A non-positive limit is equivalent to no limit at all.
|
||||
// If unspecified, the default limit is no limit at all.
|
||||
// This affects either encoding or decoding.
|
||||
func WithByteLimit(n int64) Options {
|
||||
return jsonopts.ByteLimit(max(n, 0))
|
||||
}
|
||||
|
||||
// WithDepthLimit sets a limit on the maximum depth of JSON nesting
|
||||
// that may be consumed or produced for each top-level JSON value.
|
||||
// If a [Decoder] or [Encoder] method call would need to consume or produce
|
||||
// a depth greater than n to make progress on the top-level JSON value,
|
||||
// then the call will report an error.
|
||||
//
|
||||
// A non-positive limit is equivalent to no limit at all.
|
||||
// If unspecified, the default limit is 10000.
|
||||
// This affects either encoding or decoding.
|
||||
func WithDepthLimit(n int) Options {
|
||||
return jsonopts.DepthLimit(max(n, 0))
|
||||
}
|
||||
*/
|
150
vendor/github.com/go-json-experiment/json/jsontext/pools.go
generated
vendored
Normal file
150
vendor/github.com/go-json-experiment/json/jsontext/pools.go
generated
vendored
Normal file
@ -0,0 +1,150 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package jsontext
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"math/bits"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// TODO(https://go.dev/issue/47657): Use sync.PoolOf.
|
||||
|
||||
var (
|
||||
// This owns the internal buffer since there is no io.Writer to output to.
|
||||
// Since the buffer can get arbitrarily large in normal usage,
|
||||
// there is statistical tracking logic to determine whether to recycle
|
||||
// the internal buffer or not based on a history of utilization.
|
||||
bufferedEncoderPool = &sync.Pool{New: func() any { return new(Encoder) }}
|
||||
|
||||
// This owns the internal buffer, but it is only used to temporarily store
|
||||
// buffered JSON before flushing it to the underlying io.Writer.
|
||||
// In a sufficiently efficient streaming mode, we do not expect the buffer
|
||||
// to grow arbitrarily large. Thus, we avoid recycling large buffers.
|
||||
streamingEncoderPool = &sync.Pool{New: func() any { return new(Encoder) }}
|
||||
|
||||
// This does not own the internal buffer since
|
||||
// it is taken directly from the provided bytes.Buffer.
|
||||
bytesBufferEncoderPool = &sync.Pool{New: func() any { return new(Encoder) }}
|
||||
)
|
||||
|
||||
// bufferStatistics is statistics to track buffer utilization.
|
||||
// It is used to determine whether to recycle a buffer or not
|
||||
// to avoid https://go.dev/issue/23199.
|
||||
type bufferStatistics struct {
|
||||
strikes int // number of times the buffer was under-utilized
|
||||
prevLen int // length of previous buffer
|
||||
}
|
||||
|
||||
func getBufferedEncoder(opts ...Options) *Encoder {
|
||||
e := bufferedEncoderPool.Get().(*Encoder)
|
||||
if e.s.Buf == nil {
|
||||
// Round up to nearest 2ⁿ to make best use of malloc size classes.
|
||||
// See runtime/sizeclasses.go on Go1.15.
|
||||
// Logical OR with 63 to ensure 64 as the minimum buffer size.
|
||||
n := 1 << bits.Len(uint(e.s.bufStats.prevLen|63))
|
||||
e.s.Buf = make([]byte, 0, n)
|
||||
}
|
||||
e.s.reset(e.s.Buf[:0], nil, opts...)
|
||||
return e
|
||||
}
|
||||
func putBufferedEncoder(e *Encoder) {
|
||||
// Recycle large buffers only if sufficiently utilized.
|
||||
// If a buffer is under-utilized enough times sequentially,
|
||||
// then it is discarded, ensuring that a single large buffer
|
||||
// won't be kept alive by a continuous stream of small usages.
|
||||
//
|
||||
// The worst case utilization is computed as:
|
||||
// MIN_UTILIZATION_THRESHOLD / (1 + MAX_NUM_STRIKES)
|
||||
//
|
||||
// For the constants chosen below, this is (25%)/(1+4) ⇒ 5%.
|
||||
// This may seem low, but it ensures a lower bound on
|
||||
// the absolute worst-case utilization. Without this check,
|
||||
// this would be theoretically 0%, which is infinitely worse.
|
||||
//
|
||||
// See https://go.dev/issue/27735.
|
||||
switch {
|
||||
case cap(e.s.Buf) <= 4<<10: // always recycle buffers smaller than 4KiB
|
||||
e.s.bufStats.strikes = 0
|
||||
case cap(e.s.Buf)/4 <= len(e.s.Buf): // at least 25% utilization
|
||||
e.s.bufStats.strikes = 0
|
||||
case e.s.bufStats.strikes < 4: // at most 4 strikes
|
||||
e.s.bufStats.strikes++
|
||||
default: // discard the buffer; too large and too often under-utilized
|
||||
e.s.bufStats.strikes = 0
|
||||
e.s.bufStats.prevLen = len(e.s.Buf) // heuristic for size to allocate next time
|
||||
e.s.Buf = nil
|
||||
}
|
||||
bufferedEncoderPool.Put(e)
|
||||
}
|
||||
|
||||
func getStreamingEncoder(w io.Writer, opts ...Options) *Encoder {
|
||||
if _, ok := w.(*bytes.Buffer); ok {
|
||||
e := bytesBufferEncoderPool.Get().(*Encoder)
|
||||
e.s.reset(nil, w, opts...) // buffer taken from bytes.Buffer
|
||||
return e
|
||||
} else {
|
||||
e := streamingEncoderPool.Get().(*Encoder)
|
||||
e.s.reset(e.s.Buf[:0], w, opts...) // preserve existing buffer
|
||||
return e
|
||||
}
|
||||
}
|
||||
func putStreamingEncoder(e *Encoder) {
|
||||
if _, ok := e.s.wr.(*bytes.Buffer); ok {
|
||||
bytesBufferEncoderPool.Put(e)
|
||||
} else {
|
||||
if cap(e.s.Buf) > 64<<10 {
|
||||
e.s.Buf = nil // avoid pinning arbitrarily large amounts of memory
|
||||
}
|
||||
streamingEncoderPool.Put(e)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
// This does not own the internal buffer since it is externally provided.
|
||||
bufferedDecoderPool = &sync.Pool{New: func() any { return new(Decoder) }}
|
||||
|
||||
// This owns the internal buffer, but it is only used to temporarily store
|
||||
// buffered JSON fetched from the underlying io.Reader.
|
||||
// In a sufficiently efficient streaming mode, we do not expect the buffer
|
||||
// to grow arbitrarily large. Thus, we avoid recycling large buffers.
|
||||
streamingDecoderPool = &sync.Pool{New: func() any { return new(Decoder) }}
|
||||
|
||||
// This does not own the internal buffer since
|
||||
// it is taken directly from the provided bytes.Buffer.
|
||||
bytesBufferDecoderPool = bufferedDecoderPool
|
||||
)
|
||||
|
||||
func getBufferedDecoder(b []byte, opts ...Options) *Decoder {
|
||||
d := bufferedDecoderPool.Get().(*Decoder)
|
||||
d.s.reset(b, nil, opts...)
|
||||
return d
|
||||
}
|
||||
func putBufferedDecoder(d *Decoder) {
|
||||
bufferedDecoderPool.Put(d)
|
||||
}
|
||||
|
||||
func getStreamingDecoder(r io.Reader, opts ...Options) *Decoder {
|
||||
if _, ok := r.(*bytes.Buffer); ok {
|
||||
d := bytesBufferDecoderPool.Get().(*Decoder)
|
||||
d.s.reset(nil, r, opts...) // buffer taken from bytes.Buffer
|
||||
return d
|
||||
} else {
|
||||
d := streamingDecoderPool.Get().(*Decoder)
|
||||
d.s.reset(d.s.buf[:0], r, opts...) // preserve existing buffer
|
||||
return d
|
||||
}
|
||||
}
|
||||
func putStreamingDecoder(d *Decoder) {
|
||||
if _, ok := d.s.rd.(*bytes.Buffer); ok {
|
||||
bytesBufferDecoderPool.Put(d)
|
||||
} else {
|
||||
if cap(d.s.buf) > 64<<10 {
|
||||
d.s.buf = nil // avoid pinning arbitrarily large amounts of memory
|
||||
}
|
||||
streamingDecoderPool.Put(d)
|
||||
}
|
||||
}
|
39
vendor/github.com/go-json-experiment/json/jsontext/quote.go
generated
vendored
Normal file
39
vendor/github.com/go-json-experiment/json/jsontext/quote.go
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright 2023 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package jsontext
|
||||
|
||||
import (
|
||||
"github.com/go-json-experiment/json/internal/jsonflags"
|
||||
"github.com/go-json-experiment/json/internal/jsonwire"
|
||||
)
|
||||
|
||||
// AppendQuote appends a double-quoted JSON string literal representing src
|
||||
// to dst and returns the extended buffer.
|
||||
// It uses the minimal string representation per RFC 8785, section 3.2.2.2.
|
||||
// Invalid UTF-8 bytes are replaced with the Unicode replacement character
|
||||
// and an error is returned at the end indicating the presence of invalid UTF-8.
|
||||
// The dst must not overlap with the src.
|
||||
func AppendQuote[Bytes ~[]byte | ~string](dst []byte, src Bytes) ([]byte, error) {
|
||||
dst, err := jsonwire.AppendQuote(dst, src, &jsonflags.Flags{})
|
||||
if err != nil {
|
||||
err = &SyntacticError{Err: err}
|
||||
}
|
||||
return dst, err
|
||||
}
|
||||
|
||||
// AppendUnquote appends the decoded interpretation of src as a
|
||||
// double-quoted JSON string literal to dst and returns the extended buffer.
|
||||
// The input src must be a JSON string without any surrounding whitespace.
|
||||
// Invalid UTF-8 bytes are replaced with the Unicode replacement character
|
||||
// and an error is returned at the end indicating the presence of invalid UTF-8.
|
||||
// Any trailing bytes after the JSON string literal results in an error.
|
||||
// The dst must not overlap with the src.
|
||||
func AppendUnquote[Bytes ~[]byte | ~string](dst []byte, src Bytes) ([]byte, error) {
|
||||
dst, err := jsonwire.AppendUnquote(dst, src)
|
||||
if err != nil {
|
||||
err = &SyntacticError{Err: err}
|
||||
}
|
||||
return dst, err
|
||||
}
|
826
vendor/github.com/go-json-experiment/json/jsontext/state.go
generated
vendored
Normal file
826
vendor/github.com/go-json-experiment/json/jsontext/state.go
generated
vendored
Normal file
@ -0,0 +1,826 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package jsontext
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"iter"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/go-json-experiment/json/internal/jsonwire"
|
||||
)
|
||||
|
||||
// ErrDuplicateName indicates that a JSON token could not be
|
||||
// encoded or decoded because it results in a duplicate JSON object name.
|
||||
// This error is directly wrapped within a [SyntacticError] when produced.
|
||||
//
|
||||
// The name of a duplicate JSON object member can be extracted as:
|
||||
//
|
||||
// err := ...
|
||||
// var serr jsontext.SyntacticError
|
||||
// if errors.As(err, &serr) && serr.Err == jsontext.ErrDuplicateName {
|
||||
// ptr := serr.JSONPointer // JSON pointer to duplicate name
|
||||
// name := ptr.LastToken() // duplicate name itself
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
// This error is only returned if [AllowDuplicateNames] is false.
|
||||
var ErrDuplicateName = errors.New("duplicate object member name")
|
||||
|
||||
// ErrNonStringName indicates that a JSON token could not be
|
||||
// encoded or decoded because it is not a string,
|
||||
// as required for JSON object names according to RFC 8259, section 4.
|
||||
// This error is directly wrapped within a [SyntacticError] when produced.
|
||||
var ErrNonStringName = errors.New("object member name must be a string")
|
||||
|
||||
var (
|
||||
errMissingValue = errors.New("missing value after object name")
|
||||
errMismatchDelim = errors.New("mismatching structural token for object or array")
|
||||
errMaxDepth = errors.New("exceeded max depth")
|
||||
|
||||
errInvalidNamespace = errors.New("object namespace is in an invalid state")
|
||||
)
|
||||
|
||||
// Per RFC 8259, section 9, implementations may enforce a maximum depth.
|
||||
// Such a limit is necessary to prevent stack overflows.
|
||||
const maxNestingDepth = 10000
|
||||
|
||||
type state struct {
|
||||
// Tokens validates whether the next token kind is valid.
|
||||
Tokens stateMachine
|
||||
|
||||
// Names is a stack of object names.
|
||||
Names objectNameStack
|
||||
|
||||
// Namespaces is a stack of object namespaces.
|
||||
// For performance reasons, Encoder or Decoder may not update this
|
||||
// if Marshal or Unmarshal is able to track names in a more efficient way.
|
||||
// See makeMapArshaler and makeStructArshaler.
|
||||
// Not used if AllowDuplicateNames is true.
|
||||
Namespaces objectNamespaceStack
|
||||
}
|
||||
|
||||
// needObjectValue reports whether the next token should be an object value.
|
||||
// This method is used by [wrapSyntacticError].
|
||||
func (s *state) needObjectValue() bool {
|
||||
return s.Tokens.Last.needObjectValue()
|
||||
}
|
||||
|
||||
func (s *state) reset() {
|
||||
s.Tokens.reset()
|
||||
s.Names.reset()
|
||||
s.Namespaces.reset()
|
||||
}
|
||||
|
||||
// Pointer is a JSON Pointer (RFC 6901) that references a particular JSON value
|
||||
// relative to the root of the top-level JSON value.
|
||||
//
|
||||
// A Pointer is a slash-separated list of tokens, where each token is
|
||||
// either a JSON object name or an index to a JSON array element
|
||||
// encoded as a base-10 integer value.
|
||||
// It is impossible to distinguish between an array index and an object name
|
||||
// (that happens to be an base-10 encoded integer) without also knowing
|
||||
// the structure of the top-level JSON value that the pointer refers to.
|
||||
//
|
||||
// There is exactly one representation of a pointer to a particular value,
|
||||
// so comparability of Pointer values is equivalent to checking whether
|
||||
// they both point to the exact same value.
|
||||
type Pointer string
|
||||
|
||||
// IsValid reports whether p is a valid JSON Pointer according to RFC 6901.
|
||||
// Note that the concatenation of two valid pointers produces a valid pointer.
|
||||
func (p Pointer) IsValid() bool {
|
||||
for i, r := range p {
|
||||
switch {
|
||||
case r == '~' && (i+1 == len(p) || (p[i+1] != '0' && p[i+1] != '1')):
|
||||
return false // invalid escape
|
||||
case r == '\ufffd' && !strings.HasPrefix(string(p[i:]), "\ufffd"):
|
||||
return false // invalid UTF-8
|
||||
}
|
||||
}
|
||||
return len(p) == 0 || p[0] == '/'
|
||||
}
|
||||
|
||||
// Contains reports whether the JSON value that p points to
|
||||
// is equal to or contains the JSON value that pc points to.
|
||||
func (p Pointer) Contains(pc Pointer) bool {
|
||||
// Invariant: len(p) <= len(pc) if p.Contains(pc)
|
||||
suffix, ok := strings.CutPrefix(string(pc), string(p))
|
||||
return ok && (suffix == "" || suffix[0] == '/')
|
||||
}
|
||||
|
||||
// Parent strips off the last token and returns the remaining pointer.
|
||||
// The parent of an empty p is an empty string.
|
||||
func (p Pointer) Parent() Pointer {
|
||||
return p[:max(strings.LastIndexByte(string(p), '/'), 0)]
|
||||
}
|
||||
|
||||
// LastToken returns the last token in the pointer.
|
||||
// The last token of an empty p is an empty string.
|
||||
func (p Pointer) LastToken() string {
|
||||
last := p[max(strings.LastIndexByte(string(p), '/'), 0):]
|
||||
return unescapePointerToken(strings.TrimPrefix(string(last), "/"))
|
||||
}
|
||||
|
||||
// AppendToken appends a token to the end of p and returns the full pointer.
|
||||
func (p Pointer) AppendToken(tok string) Pointer {
|
||||
return Pointer(appendEscapePointerName([]byte(p+"/"), tok))
|
||||
}
|
||||
|
||||
// TODO: Add Pointer.AppendTokens,
|
||||
// but should this take in a ...string or an iter.Seq[string]?
|
||||
|
||||
// Tokens returns an iterator over the reference tokens in the JSON pointer,
|
||||
// starting from the first token until the last token (unless stopped early).
|
||||
func (p Pointer) Tokens() iter.Seq[string] {
|
||||
return func(yield func(string) bool) {
|
||||
for len(p) > 0 {
|
||||
p = Pointer(strings.TrimPrefix(string(p), "/"))
|
||||
i := min(uint(strings.IndexByte(string(p), '/')), uint(len(p)))
|
||||
if !yield(unescapePointerToken(string(p)[:i])) {
|
||||
return
|
||||
}
|
||||
p = p[i:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func unescapePointerToken(token string) string {
|
||||
if strings.Contains(token, "~") {
|
||||
// Per RFC 6901, section 3, unescape '~' and '/' characters.
|
||||
token = strings.ReplaceAll(token, "~1", "/")
|
||||
token = strings.ReplaceAll(token, "~0", "~")
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
// appendStackPointer appends a JSON Pointer (RFC 6901) to the current value.
|
||||
//
|
||||
// - If where is -1, then it points to the previously processed token.
|
||||
//
|
||||
// - If where is 0, then it points to the parent JSON object or array,
|
||||
// or an object member if in-between an object member key and value.
|
||||
// This is useful when the position is ambiguous whether
|
||||
// we are interested in the previous or next token, or
|
||||
// when we are uncertain whether the next token
|
||||
// continues or terminates the current object or array.
|
||||
//
|
||||
// - If where is +1, then it points to the next expected value,
|
||||
// assuming that it continues the current JSON object or array.
|
||||
// As a special case, if the next token is a JSON object name,
|
||||
// then it points to the parent JSON object.
|
||||
//
|
||||
// Invariant: Must call s.names.copyQuotedBuffer beforehand.
|
||||
func (s state) appendStackPointer(b []byte, where int) []byte {
|
||||
var objectDepth int
|
||||
for i := 1; i < s.Tokens.Depth(); i++ {
|
||||
e := s.Tokens.index(i)
|
||||
arrayDelta := -1 // by default point to previous array element
|
||||
if isLast := i == s.Tokens.Depth()-1; isLast {
|
||||
switch {
|
||||
case where < 0 && e.Length() == 0 || where == 0 && !e.needObjectValue() || where > 0 && e.NeedObjectName():
|
||||
return b
|
||||
case where > 0 && e.isArray():
|
||||
arrayDelta = 0 // point to next array element
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case e.isObject():
|
||||
b = appendEscapePointerName(append(b, '/'), s.Names.getUnquoted(objectDepth))
|
||||
objectDepth++
|
||||
case e.isArray():
|
||||
b = strconv.AppendUint(append(b, '/'), uint64(e.Length()+int64(arrayDelta)), 10)
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func appendEscapePointerName[Bytes ~[]byte | ~string](b []byte, name Bytes) []byte {
|
||||
for _, r := range string(name) {
|
||||
// Per RFC 6901, section 3, escape '~' and '/' characters.
|
||||
switch r {
|
||||
case '~':
|
||||
b = append(b, "~0"...)
|
||||
case '/':
|
||||
b = append(b, "~1"...)
|
||||
default:
|
||||
b = utf8.AppendRune(b, r)
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// stateMachine is a push-down automaton that validates whether
|
||||
// a sequence of tokens is valid or not according to the JSON grammar.
|
||||
// It is useful for both encoding and decoding.
|
||||
//
|
||||
// It is a stack where each entry represents a nested JSON object or array.
|
||||
// The stack has a minimum depth of 1 where the first level is a
|
||||
// virtual JSON array to handle a stream of top-level JSON values.
|
||||
// The top-level virtual JSON array is special in that it doesn't require commas
|
||||
// between each JSON value.
|
||||
//
|
||||
// For performance, most methods are carefully written to be inlinable.
|
||||
// The zero value is a valid state machine ready for use.
|
||||
type stateMachine struct {
|
||||
Stack []stateEntry
|
||||
Last stateEntry
|
||||
}
|
||||
|
||||
// reset resets the state machine.
|
||||
// The machine always starts with a minimum depth of 1.
|
||||
func (m *stateMachine) reset() {
|
||||
m.Stack = m.Stack[:0]
|
||||
if cap(m.Stack) > 1<<10 {
|
||||
m.Stack = nil
|
||||
}
|
||||
m.Last = stateTypeArray
|
||||
}
|
||||
|
||||
// Depth is the current nested depth of JSON objects and arrays.
|
||||
// It is one-indexed (i.e., top-level values have a depth of 1).
|
||||
func (m stateMachine) Depth() int {
|
||||
return len(m.Stack) + 1
|
||||
}
|
||||
|
||||
// index returns a reference to the ith entry.
|
||||
// It is only valid until the next push method call.
|
||||
func (m *stateMachine) index(i int) *stateEntry {
|
||||
if i == len(m.Stack) {
|
||||
return &m.Last
|
||||
}
|
||||
return &m.Stack[i]
|
||||
}
|
||||
|
||||
// DepthLength reports the current nested depth and
|
||||
// the length of the last JSON object or array.
|
||||
func (m stateMachine) DepthLength() (int, int64) {
|
||||
return m.Depth(), m.Last.Length()
|
||||
}
|
||||
|
||||
// appendLiteral appends a JSON literal as the next token in the sequence.
|
||||
// If an error is returned, the state is not mutated.
|
||||
func (m *stateMachine) appendLiteral() error {
|
||||
switch {
|
||||
case m.Last.NeedObjectName():
|
||||
return ErrNonStringName
|
||||
case !m.Last.isValidNamespace():
|
||||
return errInvalidNamespace
|
||||
default:
|
||||
m.Last.Increment()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// appendString appends a JSON string as the next token in the sequence.
|
||||
// If an error is returned, the state is not mutated.
|
||||
func (m *stateMachine) appendString() error {
|
||||
switch {
|
||||
case !m.Last.isValidNamespace():
|
||||
return errInvalidNamespace
|
||||
default:
|
||||
m.Last.Increment()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// appendNumber appends a JSON number as the next token in the sequence.
|
||||
// If an error is returned, the state is not mutated.
|
||||
func (m *stateMachine) appendNumber() error {
|
||||
return m.appendLiteral()
|
||||
}
|
||||
|
||||
// pushObject appends a JSON start object token as next in the sequence.
|
||||
// If an error is returned, the state is not mutated.
|
||||
func (m *stateMachine) pushObject() error {
|
||||
switch {
|
||||
case m.Last.NeedObjectName():
|
||||
return ErrNonStringName
|
||||
case !m.Last.isValidNamespace():
|
||||
return errInvalidNamespace
|
||||
case len(m.Stack) == maxNestingDepth:
|
||||
return errMaxDepth
|
||||
default:
|
||||
m.Last.Increment()
|
||||
m.Stack = append(m.Stack, m.Last)
|
||||
m.Last = stateTypeObject
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// popObject appends a JSON end object token as next in the sequence.
|
||||
// If an error is returned, the state is not mutated.
|
||||
func (m *stateMachine) popObject() error {
|
||||
switch {
|
||||
case !m.Last.isObject():
|
||||
return errMismatchDelim
|
||||
case m.Last.needObjectValue():
|
||||
return errMissingValue
|
||||
case !m.Last.isValidNamespace():
|
||||
return errInvalidNamespace
|
||||
default:
|
||||
m.Last = m.Stack[len(m.Stack)-1]
|
||||
m.Stack = m.Stack[:len(m.Stack)-1]
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// pushArray appends a JSON start array token as next in the sequence.
|
||||
// If an error is returned, the state is not mutated.
|
||||
func (m *stateMachine) pushArray() error {
|
||||
switch {
|
||||
case m.Last.NeedObjectName():
|
||||
return ErrNonStringName
|
||||
case !m.Last.isValidNamespace():
|
||||
return errInvalidNamespace
|
||||
case len(m.Stack) == maxNestingDepth:
|
||||
return errMaxDepth
|
||||
default:
|
||||
m.Last.Increment()
|
||||
m.Stack = append(m.Stack, m.Last)
|
||||
m.Last = stateTypeArray
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// popArray appends a JSON end array token as next in the sequence.
|
||||
// If an error is returned, the state is not mutated.
|
||||
func (m *stateMachine) popArray() error {
|
||||
switch {
|
||||
case !m.Last.isArray() || len(m.Stack) == 0: // forbid popping top-level virtual JSON array
|
||||
return errMismatchDelim
|
||||
case !m.Last.isValidNamespace():
|
||||
return errInvalidNamespace
|
||||
default:
|
||||
m.Last = m.Stack[len(m.Stack)-1]
|
||||
m.Stack = m.Stack[:len(m.Stack)-1]
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// NeedIndent reports whether indent whitespace should be injected.
|
||||
// A zero value means that no whitespace should be injected.
|
||||
// A positive value means '\n', indentPrefix, and (n-1) copies of indentBody
|
||||
// should be appended to the output immediately before the next token.
|
||||
func (m stateMachine) NeedIndent(next Kind) (n int) {
|
||||
willEnd := next == '}' || next == ']'
|
||||
switch {
|
||||
case m.Depth() == 1:
|
||||
return 0 // top-level values are never indented
|
||||
case m.Last.Length() == 0 && willEnd:
|
||||
return 0 // an empty object or array is never indented
|
||||
case m.Last.Length() == 0 || m.Last.needImplicitComma(next):
|
||||
return m.Depth()
|
||||
case willEnd:
|
||||
return m.Depth() - 1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// MayAppendDelim appends a colon or comma that may precede the next token.
|
||||
func (m stateMachine) MayAppendDelim(b []byte, next Kind) []byte {
|
||||
switch {
|
||||
case m.Last.needImplicitColon():
|
||||
return append(b, ':')
|
||||
case m.Last.needImplicitComma(next) && len(m.Stack) != 0: // comma not needed for top-level values
|
||||
return append(b, ',')
|
||||
default:
|
||||
return b
|
||||
}
|
||||
}
|
||||
|
||||
// needDelim reports whether a colon or comma token should be implicitly emitted
|
||||
// before the next token of the specified kind.
|
||||
// A zero value means no delimiter should be emitted.
|
||||
func (m stateMachine) needDelim(next Kind) (delim byte) {
|
||||
switch {
|
||||
case m.Last.needImplicitColon():
|
||||
return ':'
|
||||
case m.Last.needImplicitComma(next) && len(m.Stack) != 0: // comma not needed for top-level values
|
||||
return ','
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// InvalidateDisabledNamespaces marks all disabled namespaces as invalid.
|
||||
//
|
||||
// For efficiency, Marshal and Unmarshal may disable namespaces since there are
|
||||
// more efficient ways to track duplicate names. However, if an error occurs,
|
||||
// the namespaces in Encoder or Decoder will be left in an inconsistent state.
|
||||
// Mark the namespaces as invalid so that future method calls on
|
||||
// Encoder or Decoder will return an error.
|
||||
func (m *stateMachine) InvalidateDisabledNamespaces() {
|
||||
for i := range m.Depth() {
|
||||
e := m.index(i)
|
||||
if !e.isActiveNamespace() {
|
||||
e.invalidateNamespace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// stateEntry encodes several artifacts within a single unsigned integer:
|
||||
// - whether this represents a JSON object or array,
|
||||
// - whether this object should check for duplicate names, and
|
||||
// - how many elements are in this JSON object or array.
|
||||
type stateEntry uint64
|
||||
|
||||
const (
|
||||
// The type mask (1 bit) records whether this is a JSON object or array.
|
||||
stateTypeMask stateEntry = 0x8000_0000_0000_0000
|
||||
stateTypeObject stateEntry = 0x8000_0000_0000_0000
|
||||
stateTypeArray stateEntry = 0x0000_0000_0000_0000
|
||||
|
||||
// The name check mask (2 bit) records whether to update
|
||||
// the namespaces for the current JSON object and
|
||||
// whether the namespace is valid.
|
||||
stateNamespaceMask stateEntry = 0x6000_0000_0000_0000
|
||||
stateDisableNamespace stateEntry = 0x4000_0000_0000_0000
|
||||
stateInvalidNamespace stateEntry = 0x2000_0000_0000_0000
|
||||
|
||||
// The count mask (61 bits) records the number of elements.
|
||||
stateCountMask stateEntry = 0x1fff_ffff_ffff_ffff
|
||||
stateCountLSBMask stateEntry = 0x0000_0000_0000_0001
|
||||
stateCountOdd stateEntry = 0x0000_0000_0000_0001
|
||||
stateCountEven stateEntry = 0x0000_0000_0000_0000
|
||||
)
|
||||
|
||||
// Length reports the number of elements in the JSON object or array.
|
||||
// Each name and value in an object entry is treated as a separate element.
|
||||
func (e stateEntry) Length() int64 {
|
||||
return int64(e & stateCountMask)
|
||||
}
|
||||
|
||||
// isObject reports whether this is a JSON object.
|
||||
func (e stateEntry) isObject() bool {
|
||||
return e&stateTypeMask == stateTypeObject
|
||||
}
|
||||
|
||||
// isArray reports whether this is a JSON array.
|
||||
func (e stateEntry) isArray() bool {
|
||||
return e&stateTypeMask == stateTypeArray
|
||||
}
|
||||
|
||||
// NeedObjectName reports whether the next token must be a JSON string,
|
||||
// which is necessary for JSON object names.
|
||||
func (e stateEntry) NeedObjectName() bool {
|
||||
return e&(stateTypeMask|stateCountLSBMask) == stateTypeObject|stateCountEven
|
||||
}
|
||||
|
||||
// needImplicitColon reports whether an colon should occur next,
|
||||
// which always occurs after JSON object names.
|
||||
func (e stateEntry) needImplicitColon() bool {
|
||||
return e.needObjectValue()
|
||||
}
|
||||
|
||||
// needObjectValue reports whether the next token must be a JSON value,
|
||||
// which is necessary after every JSON object name.
|
||||
func (e stateEntry) needObjectValue() bool {
|
||||
return e&(stateTypeMask|stateCountLSBMask) == stateTypeObject|stateCountOdd
|
||||
}
|
||||
|
||||
// needImplicitComma reports whether an comma should occur next,
|
||||
// which always occurs after a value in a JSON object or array
|
||||
// before the next value (or name).
|
||||
func (e stateEntry) needImplicitComma(next Kind) bool {
|
||||
return !e.needObjectValue() && e.Length() > 0 && next != '}' && next != ']'
|
||||
}
|
||||
|
||||
// Increment increments the number of elements for the current object or array.
|
||||
// This assumes that overflow won't practically be an issue since
|
||||
// 1<<bits.OnesCount(stateCountMask) is sufficiently large.
|
||||
func (e *stateEntry) Increment() {
|
||||
(*e)++
|
||||
}
|
||||
|
||||
// decrement decrements the number of elements for the current object or array.
|
||||
// It is the callers responsibility to ensure that e.length > 0.
|
||||
func (e *stateEntry) decrement() {
|
||||
(*e)--
|
||||
}
|
||||
|
||||
// DisableNamespace disables the JSON object namespace such that the
|
||||
// Encoder or Decoder no longer updates the namespace.
|
||||
func (e *stateEntry) DisableNamespace() {
|
||||
*e |= stateDisableNamespace
|
||||
}
|
||||
|
||||
// isActiveNamespace reports whether the JSON object namespace is actively
|
||||
// being updated and used for duplicate name checks.
|
||||
func (e stateEntry) isActiveNamespace() bool {
|
||||
return e&(stateDisableNamespace) == 0
|
||||
}
|
||||
|
||||
// invalidateNamespace marks the JSON object namespace as being invalid.
|
||||
func (e *stateEntry) invalidateNamespace() {
|
||||
*e |= stateInvalidNamespace
|
||||
}
|
||||
|
||||
// isValidNamespace reports whether the JSON object namespace is valid.
|
||||
func (e stateEntry) isValidNamespace() bool {
|
||||
return e&(stateInvalidNamespace) == 0
|
||||
}
|
||||
|
||||
// objectNameStack is a stack of names when descending into a JSON object.
|
||||
// In contrast to objectNamespaceStack, this only has to remember a single name
|
||||
// per JSON object.
|
||||
//
|
||||
// This data structure may contain offsets to encodeBuffer or decodeBuffer.
|
||||
// It violates clean abstraction of layers, but is significantly more efficient.
|
||||
// This ensures that popping and pushing in the common case is a trivial
|
||||
// push/pop of an offset integer.
|
||||
//
|
||||
// The zero value is an empty names stack ready for use.
|
||||
type objectNameStack struct {
|
||||
// offsets is a stack of offsets for each name.
|
||||
// A non-negative offset is the ending offset into the local names buffer.
|
||||
// A negative offset is the bit-wise inverse of a starting offset into
|
||||
// a remote buffer (e.g., encodeBuffer or decodeBuffer).
|
||||
// A math.MinInt offset at the end implies that the last object is empty.
|
||||
// Invariant: Positive offsets always occur before negative offsets.
|
||||
offsets []int
|
||||
// unquotedNames is a back-to-back concatenation of names.
|
||||
unquotedNames []byte
|
||||
}
|
||||
|
||||
func (ns *objectNameStack) reset() {
|
||||
ns.offsets = ns.offsets[:0]
|
||||
ns.unquotedNames = ns.unquotedNames[:0]
|
||||
if cap(ns.offsets) > 1<<6 {
|
||||
ns.offsets = nil // avoid pinning arbitrarily large amounts of memory
|
||||
}
|
||||
if cap(ns.unquotedNames) > 1<<10 {
|
||||
ns.unquotedNames = nil // avoid pinning arbitrarily large amounts of memory
|
||||
}
|
||||
}
|
||||
|
||||
func (ns *objectNameStack) length() int {
|
||||
return len(ns.offsets)
|
||||
}
|
||||
|
||||
// getUnquoted retrieves the ith unquoted name in the stack.
|
||||
// It returns an empty string if the last object is empty.
|
||||
//
|
||||
// Invariant: Must call copyQuotedBuffer beforehand.
|
||||
func (ns *objectNameStack) getUnquoted(i int) []byte {
|
||||
ns.ensureCopiedBuffer()
|
||||
if i == 0 {
|
||||
return ns.unquotedNames[:ns.offsets[0]]
|
||||
} else {
|
||||
return ns.unquotedNames[ns.offsets[i-1]:ns.offsets[i-0]]
|
||||
}
|
||||
}
|
||||
|
||||
// invalidOffset indicates that the last JSON object currently has no name.
|
||||
const invalidOffset = math.MinInt
|
||||
|
||||
// push descends into a nested JSON object.
|
||||
func (ns *objectNameStack) push() {
|
||||
ns.offsets = append(ns.offsets, invalidOffset)
|
||||
}
|
||||
|
||||
// ReplaceLastQuotedOffset replaces the last name with the starting offset
|
||||
// to the quoted name in some remote buffer. All offsets provided must be
|
||||
// relative to the same buffer until copyQuotedBuffer is called.
|
||||
func (ns *objectNameStack) ReplaceLastQuotedOffset(i int) {
|
||||
// Use bit-wise inversion instead of naive multiplication by -1 to avoid
|
||||
// ambiguity regarding zero (which is a valid offset into the names field).
|
||||
// Bit-wise inversion is mathematically equivalent to -i-1,
|
||||
// such that 0 becomes -1, 1 becomes -2, and so forth.
|
||||
// This ensures that remote offsets are always negative.
|
||||
ns.offsets[len(ns.offsets)-1] = ^i
|
||||
}
|
||||
|
||||
// replaceLastUnquotedName replaces the last name with the provided name.
|
||||
//
|
||||
// Invariant: Must call copyQuotedBuffer beforehand.
|
||||
func (ns *objectNameStack) replaceLastUnquotedName(s string) {
|
||||
ns.ensureCopiedBuffer()
|
||||
var startOffset int
|
||||
if len(ns.offsets) > 1 {
|
||||
startOffset = ns.offsets[len(ns.offsets)-2]
|
||||
}
|
||||
ns.unquotedNames = append(ns.unquotedNames[:startOffset], s...)
|
||||
ns.offsets[len(ns.offsets)-1] = len(ns.unquotedNames)
|
||||
}
|
||||
|
||||
// clearLast removes any name in the last JSON object.
|
||||
// It is semantically equivalent to ns.push followed by ns.pop.
|
||||
func (ns *objectNameStack) clearLast() {
|
||||
ns.offsets[len(ns.offsets)-1] = invalidOffset
|
||||
}
|
||||
|
||||
// pop ascends out of a nested JSON object.
|
||||
func (ns *objectNameStack) pop() {
|
||||
ns.offsets = ns.offsets[:len(ns.offsets)-1]
|
||||
}
|
||||
|
||||
// copyQuotedBuffer copies names from the remote buffer into the local names
|
||||
// buffer so that there are no more offset references into the remote buffer.
|
||||
// This allows the remote buffer to change contents without affecting
|
||||
// the names that this data structure is trying to remember.
|
||||
func (ns *objectNameStack) copyQuotedBuffer(b []byte) {
|
||||
// Find the first negative offset.
|
||||
var i int
|
||||
for i = len(ns.offsets) - 1; i >= 0 && ns.offsets[i] < 0; i-- {
|
||||
continue
|
||||
}
|
||||
|
||||
// Copy each name from the remote buffer into the local buffer.
|
||||
for i = i + 1; i < len(ns.offsets); i++ {
|
||||
if i == len(ns.offsets)-1 && ns.offsets[i] == invalidOffset {
|
||||
if i == 0 {
|
||||
ns.offsets[i] = 0
|
||||
} else {
|
||||
ns.offsets[i] = ns.offsets[i-1]
|
||||
}
|
||||
break // last JSON object had a push without any names
|
||||
}
|
||||
|
||||
// As a form of Hyrum proofing, we write an invalid character into the
|
||||
// buffer to make misuse of Decoder.ReadToken more obvious.
|
||||
// We need to undo that mutation here.
|
||||
quotedName := b[^ns.offsets[i]:]
|
||||
if quotedName[0] == invalidateBufferByte {
|
||||
quotedName[0] = '"'
|
||||
}
|
||||
|
||||
// Append the unquoted name to the local buffer.
|
||||
var startOffset int
|
||||
if i > 0 {
|
||||
startOffset = ns.offsets[i-1]
|
||||
}
|
||||
if n := jsonwire.ConsumeSimpleString(quotedName); n > 0 {
|
||||
ns.unquotedNames = append(ns.unquotedNames[:startOffset], quotedName[len(`"`):n-len(`"`)]...)
|
||||
} else {
|
||||
ns.unquotedNames, _ = jsonwire.AppendUnquote(ns.unquotedNames[:startOffset], quotedName)
|
||||
}
|
||||
ns.offsets[i] = len(ns.unquotedNames)
|
||||
}
|
||||
}
|
||||
|
||||
func (ns *objectNameStack) ensureCopiedBuffer() {
|
||||
if len(ns.offsets) > 0 && ns.offsets[len(ns.offsets)-1] < 0 {
|
||||
panic("BUG: copyQuotedBuffer not called beforehand")
|
||||
}
|
||||
}
|
||||
|
||||
// objectNamespaceStack is a stack of object namespaces.
|
||||
// This data structure assists in detecting duplicate names.
|
||||
type objectNamespaceStack []objectNamespace
|
||||
|
||||
// reset resets the object namespace stack.
|
||||
func (nss *objectNamespaceStack) reset() {
|
||||
if cap(*nss) > 1<<10 {
|
||||
*nss = nil
|
||||
}
|
||||
*nss = (*nss)[:0]
|
||||
}
|
||||
|
||||
// push starts a new namespace for a nested JSON object.
|
||||
func (nss *objectNamespaceStack) push() {
|
||||
if cap(*nss) > len(*nss) {
|
||||
*nss = (*nss)[:len(*nss)+1]
|
||||
nss.Last().reset()
|
||||
} else {
|
||||
*nss = append(*nss, objectNamespace{})
|
||||
}
|
||||
}
|
||||
|
||||
// Last returns a pointer to the last JSON object namespace.
|
||||
func (nss objectNamespaceStack) Last() *objectNamespace {
|
||||
return &nss[len(nss)-1]
|
||||
}
|
||||
|
||||
// pop terminates the namespace for a nested JSON object.
|
||||
func (nss *objectNamespaceStack) pop() {
|
||||
*nss = (*nss)[:len(*nss)-1]
|
||||
}
|
||||
|
||||
// objectNamespace is the namespace for a JSON object.
|
||||
// In contrast to objectNameStack, this needs to remember a all names
|
||||
// per JSON object.
|
||||
//
|
||||
// The zero value is an empty namespace ready for use.
|
||||
type objectNamespace struct {
|
||||
// It relies on a linear search over all the names before switching
|
||||
// to use a Go map for direct lookup.
|
||||
|
||||
// endOffsets is a list of offsets to the end of each name in buffers.
|
||||
// The length of offsets is the number of names in the namespace.
|
||||
endOffsets []uint
|
||||
// allUnquotedNames is a back-to-back concatenation of every name in the namespace.
|
||||
allUnquotedNames []byte
|
||||
// mapNames is a Go map containing every name in the namespace.
|
||||
// Only valid if non-nil.
|
||||
mapNames map[string]struct{}
|
||||
}
|
||||
|
||||
// reset resets the namespace to be empty.
|
||||
func (ns *objectNamespace) reset() {
|
||||
ns.endOffsets = ns.endOffsets[:0]
|
||||
ns.allUnquotedNames = ns.allUnquotedNames[:0]
|
||||
ns.mapNames = nil
|
||||
if cap(ns.endOffsets) > 1<<6 {
|
||||
ns.endOffsets = nil // avoid pinning arbitrarily large amounts of memory
|
||||
}
|
||||
if cap(ns.allUnquotedNames) > 1<<10 {
|
||||
ns.allUnquotedNames = nil // avoid pinning arbitrarily large amounts of memory
|
||||
}
|
||||
}
|
||||
|
||||
// length reports the number of names in the namespace.
|
||||
func (ns *objectNamespace) length() int {
|
||||
return len(ns.endOffsets)
|
||||
}
|
||||
|
||||
// getUnquoted retrieves the ith unquoted name in the namespace.
|
||||
func (ns *objectNamespace) getUnquoted(i int) []byte {
|
||||
if i == 0 {
|
||||
return ns.allUnquotedNames[:ns.endOffsets[0]]
|
||||
} else {
|
||||
return ns.allUnquotedNames[ns.endOffsets[i-1]:ns.endOffsets[i-0]]
|
||||
}
|
||||
}
|
||||
|
||||
// lastUnquoted retrieves the last name in the namespace.
|
||||
func (ns *objectNamespace) lastUnquoted() []byte {
|
||||
return ns.getUnquoted(ns.length() - 1)
|
||||
}
|
||||
|
||||
// insertQuoted inserts a name and reports whether it was inserted,
|
||||
// which only occurs if name is not already in the namespace.
|
||||
// The provided name must be a valid JSON string.
|
||||
func (ns *objectNamespace) insertQuoted(name []byte, isVerbatim bool) bool {
|
||||
if isVerbatim {
|
||||
name = name[len(`"`) : len(name)-len(`"`)]
|
||||
}
|
||||
return ns.insert(name, !isVerbatim)
|
||||
}
|
||||
func (ns *objectNamespace) InsertUnquoted(name []byte) bool {
|
||||
return ns.insert(name, false)
|
||||
}
|
||||
func (ns *objectNamespace) insert(name []byte, quoted bool) bool {
|
||||
var allNames []byte
|
||||
if quoted {
|
||||
allNames, _ = jsonwire.AppendUnquote(ns.allUnquotedNames, name)
|
||||
} else {
|
||||
allNames = append(ns.allUnquotedNames, name...)
|
||||
}
|
||||
name = allNames[len(ns.allUnquotedNames):]
|
||||
|
||||
// Switch to a map if the buffer is too large for linear search.
|
||||
// This does not add the current name to the map.
|
||||
if ns.mapNames == nil && (ns.length() > 64 || len(ns.allUnquotedNames) > 1024) {
|
||||
ns.mapNames = make(map[string]struct{})
|
||||
var startOffset uint
|
||||
for _, endOffset := range ns.endOffsets {
|
||||
name := ns.allUnquotedNames[startOffset:endOffset]
|
||||
ns.mapNames[string(name)] = struct{}{} // allocates a new string
|
||||
startOffset = endOffset
|
||||
}
|
||||
}
|
||||
|
||||
if ns.mapNames == nil {
|
||||
// Perform linear search over the buffer to find matching names.
|
||||
// It provides O(n) lookup, but does not require any allocations.
|
||||
var startOffset uint
|
||||
for _, endOffset := range ns.endOffsets {
|
||||
if string(ns.allUnquotedNames[startOffset:endOffset]) == string(name) {
|
||||
return false
|
||||
}
|
||||
startOffset = endOffset
|
||||
}
|
||||
} else {
|
||||
// Use the map if it is populated.
|
||||
// It provides O(1) lookup, but requires a string allocation per name.
|
||||
if _, ok := ns.mapNames[string(name)]; ok {
|
||||
return false
|
||||
}
|
||||
ns.mapNames[string(name)] = struct{}{} // allocates a new string
|
||||
}
|
||||
|
||||
ns.allUnquotedNames = allNames
|
||||
ns.endOffsets = append(ns.endOffsets, uint(len(ns.allUnquotedNames)))
|
||||
return true
|
||||
}
|
||||
|
||||
// removeLast removes the last name in the namespace.
|
||||
func (ns *objectNamespace) removeLast() {
|
||||
if ns.mapNames != nil {
|
||||
delete(ns.mapNames, string(ns.lastUnquoted()))
|
||||
}
|
||||
if ns.length()-1 == 0 {
|
||||
ns.endOffsets = ns.endOffsets[:0]
|
||||
ns.allUnquotedNames = ns.allUnquotedNames[:0]
|
||||
} else {
|
||||
ns.endOffsets = ns.endOffsets[:ns.length()-1]
|
||||
ns.allUnquotedNames = ns.allUnquotedNames[:ns.endOffsets[ns.length()-1]]
|
||||
}
|
||||
}
|
525
vendor/github.com/go-json-experiment/json/jsontext/token.go
generated
vendored
Normal file
525
vendor/github.com/go-json-experiment/json/jsontext/token.go
generated
vendored
Normal file
@ -0,0 +1,525 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package jsontext
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-json-experiment/json/internal/jsonflags"
|
||||
"github.com/go-json-experiment/json/internal/jsonwire"
|
||||
)
|
||||
|
||||
// NOTE: Token is analogous to v1 json.Token.
|
||||
|
||||
const (
|
||||
maxInt64 = math.MaxInt64
|
||||
minInt64 = math.MinInt64
|
||||
maxUint64 = math.MaxUint64
|
||||
minUint64 = 0 // for consistency and readability purposes
|
||||
|
||||
invalidTokenPanic = "invalid jsontext.Token; it has been voided by a subsequent json.Decoder call"
|
||||
)
|
||||
|
||||
var errInvalidToken = errors.New("invalid jsontext.Token")
|
||||
|
||||
// Token represents a lexical JSON token, which may be one of the following:
|
||||
// - a JSON literal (i.e., null, true, or false)
|
||||
// - a JSON string (e.g., "hello, world!")
|
||||
// - a JSON number (e.g., 123.456)
|
||||
// - a start or end delimiter for a JSON object (i.e., { or } )
|
||||
// - a start or end delimiter for a JSON array (i.e., [ or ] )
|
||||
//
|
||||
// A Token cannot represent entire array or object values, while a [Value] can.
|
||||
// There is no Token to represent commas and colons since
|
||||
// these structural tokens can be inferred from the surrounding context.
|
||||
type Token struct {
|
||||
nonComparable
|
||||
|
||||
// Tokens can exist in either a "raw" or an "exact" form.
|
||||
// Tokens produced by the Decoder are in the "raw" form.
|
||||
// Tokens returned by constructors are usually in the "exact" form.
|
||||
// The Encoder accepts Tokens in either the "raw" or "exact" form.
|
||||
//
|
||||
// The following chart shows the possible values for each Token type:
|
||||
// ╔═════════════════╦════════════╤════════════╤════════════╗
|
||||
// ║ Token type ║ raw field │ str field │ num field ║
|
||||
// ╠═════════════════╬════════════╪════════════╪════════════╣
|
||||
// ║ null (raw) ║ "null" │ "" │ 0 ║
|
||||
// ║ false (raw) ║ "false" │ "" │ 0 ║
|
||||
// ║ true (raw) ║ "true" │ "" │ 0 ║
|
||||
// ║ string (raw) ║ non-empty │ "" │ offset ║
|
||||
// ║ string (string) ║ nil │ non-empty │ 0 ║
|
||||
// ║ number (raw) ║ non-empty │ "" │ offset ║
|
||||
// ║ number (float) ║ nil │ "f" │ non-zero ║
|
||||
// ║ number (int64) ║ nil │ "i" │ non-zero ║
|
||||
// ║ number (uint64) ║ nil │ "u" │ non-zero ║
|
||||
// ║ object (delim) ║ "{" or "}" │ "" │ 0 ║
|
||||
// ║ array (delim) ║ "[" or "]" │ "" │ 0 ║
|
||||
// ╚═════════════════╩════════════╧════════════╧════════════╝
|
||||
//
|
||||
// Notes:
|
||||
// - For tokens stored in "raw" form, the num field contains the
|
||||
// absolute offset determined by raw.previousOffsetStart().
|
||||
// The buffer itself is stored in raw.previousBuffer().
|
||||
// - JSON literals and structural characters are always in the "raw" form.
|
||||
// - JSON strings and numbers can be in either "raw" or "exact" forms.
|
||||
// - The exact zero value of JSON strings and numbers in the "exact" forms
|
||||
// have ambiguous representation. Thus, they are always represented
|
||||
// in the "raw" form.
|
||||
|
||||
// raw contains a reference to the raw decode buffer.
|
||||
// If non-nil, then its value takes precedence over str and num.
|
||||
// It is only valid if num == raw.previousOffsetStart().
|
||||
raw *decodeBuffer
|
||||
|
||||
// str is the unescaped JSON string if num is zero.
|
||||
// Otherwise, it is "f", "i", or "u" if num should be interpreted
|
||||
// as a float64, int64, or uint64, respectively.
|
||||
str string
|
||||
|
||||
// num is a float64, int64, or uint64 stored as a uint64 value.
|
||||
// It is non-zero for any JSON number in the "exact" form.
|
||||
num uint64
|
||||
}
|
||||
|
||||
// TODO: Does representing 1-byte delimiters as *decodeBuffer cause performance issues?
|
||||
|
||||
var (
|
||||
Null Token = rawToken("null")
|
||||
False Token = rawToken("false")
|
||||
True Token = rawToken("true")
|
||||
|
||||
BeginObject Token = rawToken("{")
|
||||
EndObject Token = rawToken("}")
|
||||
BeginArray Token = rawToken("[")
|
||||
EndArray Token = rawToken("]")
|
||||
|
||||
zeroString Token = rawToken(`""`)
|
||||
zeroNumber Token = rawToken(`0`)
|
||||
|
||||
nanString Token = String("NaN")
|
||||
pinfString Token = String("Infinity")
|
||||
ninfString Token = String("-Infinity")
|
||||
)
|
||||
|
||||
func rawToken(s string) Token {
|
||||
return Token{raw: &decodeBuffer{buf: []byte(s), prevStart: 0, prevEnd: len(s)}}
|
||||
}
|
||||
|
||||
// Bool constructs a Token representing a JSON boolean.
|
||||
func Bool(b bool) Token {
|
||||
if b {
|
||||
return True
|
||||
}
|
||||
return False
|
||||
}
|
||||
|
||||
// String constructs a Token representing a JSON string.
|
||||
// The provided string should contain valid UTF-8, otherwise invalid characters
|
||||
// may be mangled as the Unicode replacement character.
|
||||
func String(s string) Token {
|
||||
if len(s) == 0 {
|
||||
return zeroString
|
||||
}
|
||||
return Token{str: s}
|
||||
}
|
||||
|
||||
// Float constructs a Token representing a JSON number.
|
||||
// The values NaN, +Inf, and -Inf will be represented
|
||||
// as a JSON string with the values "NaN", "Infinity", and "-Infinity".
|
||||
func Float(n float64) Token {
|
||||
switch {
|
||||
case math.Float64bits(n) == 0:
|
||||
return zeroNumber
|
||||
case math.IsNaN(n):
|
||||
return nanString
|
||||
case math.IsInf(n, +1):
|
||||
return pinfString
|
||||
case math.IsInf(n, -1):
|
||||
return ninfString
|
||||
}
|
||||
return Token{str: "f", num: math.Float64bits(n)}
|
||||
}
|
||||
|
||||
// Int constructs a Token representing a JSON number from an int64.
|
||||
func Int(n int64) Token {
|
||||
if n == 0 {
|
||||
return zeroNumber
|
||||
}
|
||||
return Token{str: "i", num: uint64(n)}
|
||||
}
|
||||
|
||||
// Uint constructs a Token representing a JSON number from a uint64.
|
||||
func Uint(n uint64) Token {
|
||||
if n == 0 {
|
||||
return zeroNumber
|
||||
}
|
||||
return Token{str: "u", num: uint64(n)}
|
||||
}
|
||||
|
||||
// Clone makes a copy of the Token such that its value remains valid
|
||||
// even after a subsequent [Decoder.Read] call.
|
||||
func (t Token) Clone() Token {
|
||||
// TODO: Allow caller to avoid any allocations?
|
||||
if raw := t.raw; raw != nil {
|
||||
// Avoid copying globals.
|
||||
if t.raw.prevStart == 0 {
|
||||
switch t.raw {
|
||||
case Null.raw:
|
||||
return Null
|
||||
case False.raw:
|
||||
return False
|
||||
case True.raw:
|
||||
return True
|
||||
case BeginObject.raw:
|
||||
return BeginObject
|
||||
case EndObject.raw:
|
||||
return EndObject
|
||||
case BeginArray.raw:
|
||||
return BeginArray
|
||||
case EndArray.raw:
|
||||
return EndArray
|
||||
}
|
||||
}
|
||||
|
||||
if uint64(raw.previousOffsetStart()) != t.num {
|
||||
panic(invalidTokenPanic)
|
||||
}
|
||||
buf := bytes.Clone(raw.previousBuffer())
|
||||
return Token{raw: &decodeBuffer{buf: buf, prevStart: 0, prevEnd: len(buf)}}
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// Bool returns the value for a JSON boolean.
|
||||
// It panics if the token kind is not a JSON boolean.
|
||||
func (t Token) Bool() bool {
|
||||
switch t.raw {
|
||||
case True.raw:
|
||||
return true
|
||||
case False.raw:
|
||||
return false
|
||||
default:
|
||||
panic("invalid JSON token kind: " + t.Kind().String())
|
||||
}
|
||||
}
|
||||
|
||||
// appendString appends a JSON string to dst and returns it.
|
||||
// It panics if t is not a JSON string.
|
||||
func (t Token) appendString(dst []byte, flags *jsonflags.Flags) ([]byte, error) {
|
||||
if raw := t.raw; raw != nil {
|
||||
// Handle raw string value.
|
||||
buf := raw.previousBuffer()
|
||||
if Kind(buf[0]) == '"' {
|
||||
if jsonwire.ConsumeSimpleString(buf) == len(buf) {
|
||||
return append(dst, buf...), nil
|
||||
}
|
||||
dst, _, err := jsonwire.ReformatString(dst, buf, flags)
|
||||
return dst, err
|
||||
}
|
||||
} else if len(t.str) != 0 && t.num == 0 {
|
||||
// Handle exact string value.
|
||||
return jsonwire.AppendQuote(dst, t.str, flags)
|
||||
}
|
||||
|
||||
panic("invalid JSON token kind: " + t.Kind().String())
|
||||
}
|
||||
|
||||
// String returns the unescaped string value for a JSON string.
|
||||
// For other JSON kinds, this returns the raw JSON representation.
|
||||
func (t Token) String() string {
|
||||
// This is inlinable to take advantage of "function outlining".
|
||||
// This avoids an allocation for the string(b) conversion
|
||||
// if the caller does not use the string in an escaping manner.
|
||||
// See https://blog.filippo.io/efficient-go-apis-with-the-inliner/
|
||||
s, b := t.string()
|
||||
if len(b) > 0 {
|
||||
return string(b)
|
||||
}
|
||||
return s
|
||||
}
|
||||
func (t Token) string() (string, []byte) {
|
||||
if raw := t.raw; raw != nil {
|
||||
if uint64(raw.previousOffsetStart()) != t.num {
|
||||
panic(invalidTokenPanic)
|
||||
}
|
||||
buf := raw.previousBuffer()
|
||||
if buf[0] == '"' {
|
||||
// TODO: Preserve ValueFlags in Token?
|
||||
isVerbatim := jsonwire.ConsumeSimpleString(buf) == len(buf)
|
||||
return "", jsonwire.UnquoteMayCopy(buf, isVerbatim)
|
||||
}
|
||||
// Handle tokens that are not JSON strings for fmt.Stringer.
|
||||
return "", buf
|
||||
}
|
||||
if len(t.str) != 0 && t.num == 0 {
|
||||
return t.str, nil
|
||||
}
|
||||
// Handle tokens that are not JSON strings for fmt.Stringer.
|
||||
if t.num > 0 {
|
||||
switch t.str[0] {
|
||||
case 'f':
|
||||
return string(jsonwire.AppendFloat(nil, math.Float64frombits(t.num), 64)), nil
|
||||
case 'i':
|
||||
return strconv.FormatInt(int64(t.num), 10), nil
|
||||
case 'u':
|
||||
return strconv.FormatUint(uint64(t.num), 10), nil
|
||||
}
|
||||
}
|
||||
return "<invalid jsontext.Token>", nil
|
||||
}
|
||||
|
||||
// appendNumber appends a JSON number to dst and returns it.
|
||||
// It panics if t is not a JSON number.
|
||||
func (t Token) appendNumber(dst []byte, flags *jsonflags.Flags) ([]byte, error) {
|
||||
if raw := t.raw; raw != nil {
|
||||
// Handle raw number value.
|
||||
buf := raw.previousBuffer()
|
||||
if Kind(buf[0]).normalize() == '0' {
|
||||
dst, _, err := jsonwire.ReformatNumber(dst, buf, flags)
|
||||
return dst, err
|
||||
}
|
||||
} else if t.num != 0 {
|
||||
// Handle exact number value.
|
||||
switch t.str[0] {
|
||||
case 'f':
|
||||
return jsonwire.AppendFloat(dst, math.Float64frombits(t.num), 64), nil
|
||||
case 'i':
|
||||
return strconv.AppendInt(dst, int64(t.num), 10), nil
|
||||
case 'u':
|
||||
return strconv.AppendUint(dst, uint64(t.num), 10), nil
|
||||
}
|
||||
}
|
||||
|
||||
panic("invalid JSON token kind: " + t.Kind().String())
|
||||
}
|
||||
|
||||
// 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 panics for all other cases.
|
||||
func (t Token) Float() float64 {
|
||||
if raw := t.raw; raw != nil {
|
||||
// Handle raw number value.
|
||||
if uint64(raw.previousOffsetStart()) != t.num {
|
||||
panic(invalidTokenPanic)
|
||||
}
|
||||
buf := raw.previousBuffer()
|
||||
if Kind(buf[0]).normalize() == '0' {
|
||||
fv, _ := jsonwire.ParseFloat(buf, 64)
|
||||
return fv
|
||||
}
|
||||
} else if t.num != 0 {
|
||||
// Handle exact number value.
|
||||
switch t.str[0] {
|
||||
case 'f':
|
||||
return math.Float64frombits(t.num)
|
||||
case 'i':
|
||||
return float64(int64(t.num))
|
||||
case 'u':
|
||||
return float64(uint64(t.num))
|
||||
}
|
||||
}
|
||||
|
||||
// Handle string values with "NaN", "Infinity", or "-Infinity".
|
||||
if t.Kind() == '"' {
|
||||
switch t.String() {
|
||||
case "NaN":
|
||||
return math.NaN()
|
||||
case "Infinity":
|
||||
return math.Inf(+1)
|
||||
case "-Infinity":
|
||||
return math.Inf(-1)
|
||||
}
|
||||
}
|
||||
|
||||
panic("invalid JSON token kind: " + t.Kind().String())
|
||||
}
|
||||
|
||||
// Int returns the signed integer value for a JSON number.
|
||||
// The fractional component of any number is ignored (truncation toward zero).
|
||||
// Any number beyond the representation of an int64 will be saturated
|
||||
// to the closest representable value.
|
||||
// It panics if the token kind is not a JSON number.
|
||||
func (t Token) Int() int64 {
|
||||
if raw := t.raw; raw != nil {
|
||||
// Handle raw integer value.
|
||||
if uint64(raw.previousOffsetStart()) != t.num {
|
||||
panic(invalidTokenPanic)
|
||||
}
|
||||
neg := false
|
||||
buf := raw.previousBuffer()
|
||||
if len(buf) > 0 && buf[0] == '-' {
|
||||
neg, buf = true, buf[1:]
|
||||
}
|
||||
if numAbs, ok := jsonwire.ParseUint(buf); ok {
|
||||
if neg {
|
||||
if numAbs > -minInt64 {
|
||||
return minInt64
|
||||
}
|
||||
return -1 * int64(numAbs)
|
||||
} else {
|
||||
if numAbs > +maxInt64 {
|
||||
return maxInt64
|
||||
}
|
||||
return +1 * int64(numAbs)
|
||||
}
|
||||
}
|
||||
} else if t.num != 0 {
|
||||
// Handle exact integer value.
|
||||
switch t.str[0] {
|
||||
case 'i':
|
||||
return int64(t.num)
|
||||
case 'u':
|
||||
if t.num > maxInt64 {
|
||||
return maxInt64
|
||||
}
|
||||
return int64(t.num)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle JSON number that is a floating-point value.
|
||||
if t.Kind() == '0' {
|
||||
switch fv := t.Float(); {
|
||||
case fv >= maxInt64:
|
||||
return maxInt64
|
||||
case fv <= minInt64:
|
||||
return minInt64
|
||||
default:
|
||||
return int64(fv) // truncation toward zero
|
||||
}
|
||||
}
|
||||
|
||||
panic("invalid JSON token kind: " + t.Kind().String())
|
||||
}
|
||||
|
||||
// Uint returns the unsigned integer value for a JSON number.
|
||||
// The fractional component of any number is ignored (truncation toward zero).
|
||||
// Any number beyond the representation of an uint64 will be saturated
|
||||
// to the closest representable value.
|
||||
// It panics if the token kind is not a JSON number.
|
||||
func (t Token) Uint() uint64 {
|
||||
// NOTE: This accessor returns 0 for any negative JSON number,
|
||||
// which might be surprising, but is at least consistent with the behavior
|
||||
// of saturating out-of-bounds numbers to the closest representable number.
|
||||
|
||||
if raw := t.raw; raw != nil {
|
||||
// Handle raw integer value.
|
||||
if uint64(raw.previousOffsetStart()) != t.num {
|
||||
panic(invalidTokenPanic)
|
||||
}
|
||||
neg := false
|
||||
buf := raw.previousBuffer()
|
||||
if len(buf) > 0 && buf[0] == '-' {
|
||||
neg, buf = true, buf[1:]
|
||||
}
|
||||
if num, ok := jsonwire.ParseUint(buf); ok {
|
||||
if neg {
|
||||
return minUint64
|
||||
}
|
||||
return num
|
||||
}
|
||||
} else if t.num != 0 {
|
||||
// Handle exact integer value.
|
||||
switch t.str[0] {
|
||||
case 'u':
|
||||
return t.num
|
||||
case 'i':
|
||||
if int64(t.num) < minUint64 {
|
||||
return minUint64
|
||||
}
|
||||
return uint64(int64(t.num))
|
||||
}
|
||||
}
|
||||
|
||||
// Handle JSON number that is a floating-point value.
|
||||
if t.Kind() == '0' {
|
||||
switch fv := t.Float(); {
|
||||
case fv >= maxUint64:
|
||||
return maxUint64
|
||||
case fv <= minUint64:
|
||||
return minUint64
|
||||
default:
|
||||
return uint64(fv) // truncation toward zero
|
||||
}
|
||||
}
|
||||
|
||||
panic("invalid JSON token kind: " + t.Kind().String())
|
||||
}
|
||||
|
||||
// Kind returns the token kind.
|
||||
func (t Token) Kind() Kind {
|
||||
switch {
|
||||
case t.raw != nil:
|
||||
raw := t.raw
|
||||
if uint64(raw.previousOffsetStart()) != t.num {
|
||||
panic(invalidTokenPanic)
|
||||
}
|
||||
return Kind(t.raw.buf[raw.prevStart]).normalize()
|
||||
case t.num != 0:
|
||||
return '0'
|
||||
case len(t.str) != 0:
|
||||
return '"'
|
||||
default:
|
||||
return invalidKind
|
||||
}
|
||||
}
|
||||
|
||||
// Kind represents each possible JSON token 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':
|
||||
//
|
||||
// - 'n': null
|
||||
// - 'f': false
|
||||
// - 't': true
|
||||
// - '"': string
|
||||
// - '0': number
|
||||
// - '{': object start
|
||||
// - '}': object end
|
||||
// - '[': array start
|
||||
// - ']': array end
|
||||
//
|
||||
// An invalid kind is usually represented using 0,
|
||||
// but may be non-zero due to invalid JSON data.
|
||||
type Kind byte
|
||||
|
||||
const invalidKind Kind = 0
|
||||
|
||||
// String prints the kind in a humanly readable fashion.
|
||||
func (k Kind) String() string {
|
||||
switch k {
|
||||
case 'n':
|
||||
return "null"
|
||||
case 'f':
|
||||
return "false"
|
||||
case 't':
|
||||
return "true"
|
||||
case '"':
|
||||
return "string"
|
||||
case '0':
|
||||
return "number"
|
||||
case '{':
|
||||
return "{"
|
||||
case '}':
|
||||
return "}"
|
||||
case '[':
|
||||
return "["
|
||||
case ']':
|
||||
return "]"
|
||||
default:
|
||||
return "<invalid jsontext.Kind: " + jsonwire.QuoteRune(string(k)) + ">"
|
||||
}
|
||||
}
|
||||
|
||||
// normalize coalesces all possible starting characters of a number as just '0'.
|
||||
func (k Kind) normalize() Kind {
|
||||
if k == '-' || ('0' <= k && k <= '9') {
|
||||
return '0'
|
||||
}
|
||||
return k
|
||||
}
|
393
vendor/github.com/go-json-experiment/json/jsontext/value.go
generated
vendored
Normal file
393
vendor/github.com/go-json-experiment/json/jsontext/value.go
generated
vendored
Normal file
@ -0,0 +1,393 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package jsontext
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"slices"
|
||||
"sync"
|
||||
|
||||
"github.com/go-json-experiment/json/internal/jsonflags"
|
||||
"github.com/go-json-experiment/json/internal/jsonwire"
|
||||
)
|
||||
|
||||
// NOTE: Value is analogous to v1 json.RawMessage.
|
||||
|
||||
// AppendFormat formats the JSON value in src and appends it to dst
|
||||
// according to the specified options.
|
||||
// See [Value.Format] for more details about the formatting behavior.
|
||||
//
|
||||
// The dst and src may overlap.
|
||||
// If an error is reported, then the entirety of src is appended to dst.
|
||||
func AppendFormat(dst, src []byte, opts ...Options) ([]byte, error) {
|
||||
e := getBufferedEncoder(opts...)
|
||||
defer putBufferedEncoder(e)
|
||||
e.s.Flags.Set(jsonflags.OmitTopLevelNewline | 1)
|
||||
if err := e.s.WriteValue(src); err != nil {
|
||||
return append(dst, src...), err
|
||||
}
|
||||
return append(dst, e.s.Buf...), nil
|
||||
}
|
||||
|
||||
// Value represents a single raw JSON value, which may be one of the following:
|
||||
// - a JSON literal (i.e., null, true, or false)
|
||||
// - a JSON string (e.g., "hello, world!")
|
||||
// - a JSON number (e.g., 123.456)
|
||||
// - an entire JSON object (e.g., {"fizz":"buzz"} )
|
||||
// - an entire JSON array (e.g., [1,2,3] )
|
||||
//
|
||||
// Value can represent entire array or object values, while [Token] cannot.
|
||||
// Value may contain leading and/or trailing whitespace.
|
||||
type Value []byte
|
||||
|
||||
// Clone returns a copy of v.
|
||||
func (v Value) Clone() Value {
|
||||
return bytes.Clone(v)
|
||||
}
|
||||
|
||||
// String returns the string formatting of v.
|
||||
func (v Value) String() string {
|
||||
if v == nil {
|
||||
return "null"
|
||||
}
|
||||
return string(v)
|
||||
}
|
||||
|
||||
// IsValid reports whether the raw JSON value is syntactically valid
|
||||
// according to the specified options.
|
||||
//
|
||||
// By default (if no options are specified), it validates according to RFC 7493.
|
||||
// It verifies whether the input is properly encoded as UTF-8,
|
||||
// that escape sequences within strings decode to valid Unicode codepoints, and
|
||||
// that all names in each object are unique.
|
||||
// It does not verify whether numbers are representable within the limits
|
||||
// of any common numeric type (e.g., float64, int64, or uint64).
|
||||
//
|
||||
// Relevant options include:
|
||||
// - [AllowDuplicateNames]
|
||||
// - [AllowInvalidUTF8]
|
||||
//
|
||||
// All other options are ignored.
|
||||
func (v Value) IsValid(opts ...Options) bool {
|
||||
// TODO: Document support for [WithByteLimit] and [WithDepthLimit].
|
||||
d := getBufferedDecoder(v, opts...)
|
||||
defer putBufferedDecoder(d)
|
||||
_, errVal := d.ReadValue()
|
||||
_, errEOF := d.ReadToken()
|
||||
return errVal == nil && errEOF == io.EOF
|
||||
}
|
||||
|
||||
// Format formats the raw JSON value in place.
|
||||
//
|
||||
// By default (if no options are specified), it validates according to RFC 7493
|
||||
// and produces the minimal JSON representation, where
|
||||
// all whitespace is elided and JSON strings use the shortest encoding.
|
||||
//
|
||||
// Relevant options include:
|
||||
// - [AllowDuplicateNames]
|
||||
// - [AllowInvalidUTF8]
|
||||
// - [EscapeForHTML]
|
||||
// - [EscapeForJS]
|
||||
// - [PreserveRawStrings]
|
||||
// - [CanonicalizeRawInts]
|
||||
// - [CanonicalizeRawFloats]
|
||||
// - [ReorderRawObjects]
|
||||
// - [SpaceAfterColon]
|
||||
// - [SpaceAfterComma]
|
||||
// - [Multiline]
|
||||
// - [WithIndent]
|
||||
// - [WithIndentPrefix]
|
||||
//
|
||||
// All other options are ignored.
|
||||
//
|
||||
// It is guaranteed to succeed if the value is valid according to the same options.
|
||||
// If the value is already formatted, then the buffer is not mutated.
|
||||
func (v *Value) Format(opts ...Options) error {
|
||||
// TODO: Document support for [WithByteLimit] and [WithDepthLimit].
|
||||
return v.format(opts, nil)
|
||||
}
|
||||
|
||||
// format accepts two []Options to avoid the allocation appending them together.
|
||||
// It is equivalent to v.Format(append(opts1, opts2...)...).
|
||||
func (v *Value) format(opts1, opts2 []Options) error {
|
||||
e := getBufferedEncoder(opts1...)
|
||||
defer putBufferedEncoder(e)
|
||||
e.s.Join(opts2...)
|
||||
e.s.Flags.Set(jsonflags.OmitTopLevelNewline | 1)
|
||||
if err := e.s.WriteValue(*v); err != nil {
|
||||
return err
|
||||
}
|
||||
if !bytes.Equal(*v, e.s.Buf) {
|
||||
*v = append((*v)[:0], e.s.Buf...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Compact removes all whitespace from the raw JSON value.
|
||||
//
|
||||
// It does not reformat JSON strings or numbers to use any other representation.
|
||||
// To maximize the set of JSON values that can be formatted,
|
||||
// this permits values with duplicate names and invalid UTF-8.
|
||||
//
|
||||
// Compact is equivalent to calling [Value.Format] with the following options:
|
||||
// - [AllowDuplicateNames](true)
|
||||
// - [AllowInvalidUTF8](true)
|
||||
// - [PreserveRawStrings](true)
|
||||
//
|
||||
// Any options specified by the caller are applied after the initial set
|
||||
// and may deliberately override prior options.
|
||||
func (v *Value) Compact(opts ...Options) error {
|
||||
return v.format([]Options{
|
||||
AllowDuplicateNames(true),
|
||||
AllowInvalidUTF8(true),
|
||||
PreserveRawStrings(true),
|
||||
}, opts)
|
||||
}
|
||||
|
||||
// Indent reformats the whitespace in the raw JSON value so that each element
|
||||
// in a JSON object or array begins on a indented line according to the nesting.
|
||||
//
|
||||
// It does not reformat JSON strings or numbers to use any other representation.
|
||||
// To maximize the set of JSON values that can be formatted,
|
||||
// this permits values with duplicate names and invalid UTF-8.
|
||||
//
|
||||
// Indent is equivalent to calling [Value.Format] with the following options:
|
||||
// - [AllowDuplicateNames](true)
|
||||
// - [AllowInvalidUTF8](true)
|
||||
// - [PreserveRawStrings](true)
|
||||
// - [Multiline](true)
|
||||
//
|
||||
// Any options specified by the caller are applied after the initial set
|
||||
// and may deliberately override prior options.
|
||||
func (v *Value) Indent(opts ...Options) error {
|
||||
return v.format([]Options{
|
||||
AllowDuplicateNames(true),
|
||||
AllowInvalidUTF8(true),
|
||||
PreserveRawStrings(true),
|
||||
Multiline(true),
|
||||
}, opts)
|
||||
}
|
||||
|
||||
// Canonicalize canonicalizes the raw JSON value according to the
|
||||
// JSON Canonicalization Scheme (JCS) as defined by RFC 8785
|
||||
// where it produces a stable representation of a JSON value.
|
||||
//
|
||||
// JSON strings are formatted to use their minimal representation,
|
||||
// JSON numbers are formatted as double precision numbers according
|
||||
// to some stable serialization algorithm.
|
||||
// JSON object members are sorted in ascending order by name.
|
||||
// All whitespace is removed.
|
||||
//
|
||||
// The output stability is dependent on the stability of the application data
|
||||
// (see RFC 8785, Appendix E). It cannot produce stable output from
|
||||
// fundamentally unstable input. For example, if the JSON value
|
||||
// contains ephemeral data (e.g., a frequently changing timestamp),
|
||||
// then the value is still unstable regardless of whether this is called.
|
||||
//
|
||||
// Canonicalize is equivalent to calling [Value.Format] with the following options:
|
||||
// - [CanonicalizeRawInts](true)
|
||||
// - [CanonicalizeRawFloats](true)
|
||||
// - [ReorderRawObjects](true)
|
||||
//
|
||||
// Any options specified by the caller are applied after the initial set
|
||||
// and may deliberately override prior options.
|
||||
//
|
||||
// Note that JCS treats all JSON numbers as IEEE 754 double precision numbers.
|
||||
// Any numbers with precision beyond what is representable by that form
|
||||
// will lose their precision when canonicalized. For example, integer values
|
||||
// beyond ±2⁵³ will lose their precision. To preserve the original representation
|
||||
// of JSON integers, additionally set [CanonicalizeRawInts] to false:
|
||||
//
|
||||
// v.Canonicalize(jsontext.CanonicalizeRawInts(false))
|
||||
func (v *Value) Canonicalize(opts ...Options) error {
|
||||
return v.format([]Options{
|
||||
CanonicalizeRawInts(true),
|
||||
CanonicalizeRawFloats(true),
|
||||
ReorderRawObjects(true),
|
||||
}, opts)
|
||||
}
|
||||
|
||||
// MarshalJSON returns v as the JSON encoding of v.
|
||||
// It returns the stored value as the raw JSON output without any validation.
|
||||
// If v is nil, then this returns a JSON null.
|
||||
func (v Value) MarshalJSON() ([]byte, error) {
|
||||
// NOTE: This matches the behavior of v1 json.RawMessage.MarshalJSON.
|
||||
if v == nil {
|
||||
return []byte("null"), nil
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON sets v as the JSON encoding of b.
|
||||
// It stores a copy of the provided raw JSON input without any validation.
|
||||
func (v *Value) UnmarshalJSON(b []byte) error {
|
||||
// NOTE: This matches the behavior of v1 json.RawMessage.UnmarshalJSON.
|
||||
if v == nil {
|
||||
return errors.New("jsontext.Value: UnmarshalJSON on nil pointer")
|
||||
}
|
||||
*v = append((*v)[:0], b...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Kind returns the starting token kind.
|
||||
// For a valid value, this will never include '}' or ']'.
|
||||
func (v Value) Kind() Kind {
|
||||
if v := v[jsonwire.ConsumeWhitespace(v):]; len(v) > 0 {
|
||||
return Kind(v[0]).normalize()
|
||||
}
|
||||
return invalidKind
|
||||
}
|
||||
|
||||
const commaAndWhitespace = ", \n\r\t"
|
||||
|
||||
type objectMember struct {
|
||||
// name is the unquoted name.
|
||||
name []byte // e.g., "name"
|
||||
// buffer is the entirety of the raw JSON object member
|
||||
// starting from right after the previous member (or opening '{')
|
||||
// until right after the member value.
|
||||
buffer []byte // e.g., `, \n\r\t"name": "value"`
|
||||
}
|
||||
|
||||
func (x objectMember) Compare(y objectMember) int {
|
||||
if c := jsonwire.CompareUTF16(x.name, y.name); c != 0 {
|
||||
return c
|
||||
}
|
||||
// With [AllowDuplicateNames] or [AllowInvalidUTF8],
|
||||
// names could be identical, so also sort using the member value.
|
||||
return jsonwire.CompareUTF16(
|
||||
bytes.TrimLeft(x.buffer, commaAndWhitespace),
|
||||
bytes.TrimLeft(y.buffer, commaAndWhitespace))
|
||||
}
|
||||
|
||||
var objectMemberPool = sync.Pool{New: func() any { return new([]objectMember) }}
|
||||
|
||||
func getObjectMembers() *[]objectMember {
|
||||
ns := objectMemberPool.Get().(*[]objectMember)
|
||||
*ns = (*ns)[:0]
|
||||
return ns
|
||||
}
|
||||
func putObjectMembers(ns *[]objectMember) {
|
||||
if cap(*ns) < 1<<10 {
|
||||
clear(*ns) // avoid pinning name and buffer
|
||||
objectMemberPool.Put(ns)
|
||||
}
|
||||
}
|
||||
|
||||
// mustReorderObjects reorders in-place all object members in a JSON value,
|
||||
// which must be valid otherwise it panics.
|
||||
func mustReorderObjects(b []byte) {
|
||||
// Obtain a buffered encoder just to use its internal buffer as
|
||||
// a scratch buffer for reordering object members.
|
||||
e2 := getBufferedEncoder()
|
||||
defer putBufferedEncoder(e2)
|
||||
|
||||
// Disable unnecessary checks to syntactically parse the JSON value.
|
||||
d := getBufferedDecoder(b)
|
||||
defer putBufferedDecoder(d)
|
||||
d.s.Flags.Set(jsonflags.AllowDuplicateNames | jsonflags.AllowInvalidUTF8 | 1)
|
||||
mustReorderObjectsFromDecoder(d, &e2.s.Buf) // per RFC 8785, section 3.2.3
|
||||
}
|
||||
|
||||
// mustReorderObjectsFromDecoder recursively reorders all object members in place
|
||||
// according to the ordering specified in RFC 8785, section 3.2.3.
|
||||
//
|
||||
// Pre-conditions:
|
||||
// - The value is valid (i.e., no decoder errors should ever occur).
|
||||
// - Initial call is provided a Decoder reading from the start of v.
|
||||
//
|
||||
// Post-conditions:
|
||||
// - Exactly one JSON value is read from the Decoder.
|
||||
// - All fully-parsed JSON objects are reordered by directly moving
|
||||
// the members in the value buffer.
|
||||
//
|
||||
// The runtime is approximately O(n·log(n)) + O(m·log(m)),
|
||||
// where n is len(v) and m is the total number of object members.
|
||||
func mustReorderObjectsFromDecoder(d *Decoder, scratch *[]byte) {
|
||||
switch tok, err := d.ReadToken(); tok.Kind() {
|
||||
case '{':
|
||||
// Iterate and collect the name and offsets for every object member.
|
||||
members := getObjectMembers()
|
||||
defer putObjectMembers(members)
|
||||
var prevMember objectMember
|
||||
isSorted := true
|
||||
|
||||
beforeBody := d.InputOffset() // offset after '{'
|
||||
for d.PeekKind() != '}' {
|
||||
beforeName := d.InputOffset()
|
||||
var flags jsonwire.ValueFlags
|
||||
name, _ := d.s.ReadValue(&flags)
|
||||
name = jsonwire.UnquoteMayCopy(name, flags.IsVerbatim())
|
||||
mustReorderObjectsFromDecoder(d, scratch)
|
||||
afterValue := d.InputOffset()
|
||||
|
||||
currMember := objectMember{name, d.s.buf[beforeName:afterValue]}
|
||||
if isSorted && len(*members) > 0 {
|
||||
isSorted = objectMember.Compare(prevMember, currMember) < 0
|
||||
}
|
||||
*members = append(*members, currMember)
|
||||
prevMember = currMember
|
||||
}
|
||||
afterBody := d.InputOffset() // offset before '}'
|
||||
d.ReadToken()
|
||||
|
||||
// Sort the members; return early if it's already sorted.
|
||||
if isSorted {
|
||||
return
|
||||
}
|
||||
firstBufferBeforeSorting := (*members)[0].buffer
|
||||
slices.SortFunc(*members, objectMember.Compare)
|
||||
firstBufferAfterSorting := (*members)[0].buffer
|
||||
|
||||
// Append the reordered members to a new buffer,
|
||||
// then copy the reordered members back over the original members.
|
||||
// Avoid swapping in place since each member may be a different size
|
||||
// where moving a member over a smaller member may corrupt the data
|
||||
// for subsequent members before they have been moved.
|
||||
//
|
||||
// The following invariant must hold:
|
||||
// sum([m.after-m.before for m in members]) == afterBody-beforeBody
|
||||
commaAndWhitespacePrefix := func(b []byte) []byte {
|
||||
return b[:len(b)-len(bytes.TrimLeft(b, commaAndWhitespace))]
|
||||
}
|
||||
sorted := (*scratch)[:0]
|
||||
for i, member := range *members {
|
||||
switch {
|
||||
case i == 0 && &member.buffer[0] != &firstBufferBeforeSorting[0]:
|
||||
// First member after sorting is not the first member before sorting,
|
||||
// so use the prefix of the first member before sorting.
|
||||
sorted = append(sorted, commaAndWhitespacePrefix(firstBufferBeforeSorting)...)
|
||||
sorted = append(sorted, bytes.TrimLeft(member.buffer, commaAndWhitespace)...)
|
||||
case i != 0 && &member.buffer[0] == &firstBufferBeforeSorting[0]:
|
||||
// Later member after sorting is the first member before sorting,
|
||||
// so use the prefix of the first member after sorting.
|
||||
sorted = append(sorted, commaAndWhitespacePrefix(firstBufferAfterSorting)...)
|
||||
sorted = append(sorted, bytes.TrimLeft(member.buffer, commaAndWhitespace)...)
|
||||
default:
|
||||
sorted = append(sorted, member.buffer...)
|
||||
}
|
||||
}
|
||||
if int(afterBody-beforeBody) != len(sorted) {
|
||||
panic("BUG: length invariant violated")
|
||||
}
|
||||
copy(d.s.buf[beforeBody:afterBody], sorted)
|
||||
|
||||
// Update scratch buffer to the largest amount ever used.
|
||||
if len(sorted) > len(*scratch) {
|
||||
*scratch = sorted
|
||||
}
|
||||
case '[':
|
||||
for d.PeekKind() != ']' {
|
||||
mustReorderObjectsFromDecoder(d, scratch)
|
||||
}
|
||||
d.ReadToken()
|
||||
default:
|
||||
if err != nil {
|
||||
panic("BUG: " + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
243
vendor/github.com/go-json-experiment/json/migrate.sh
generated
vendored
Normal file
243
vendor/github.com/go-json-experiment/json/migrate.sh
generated
vendored
Normal file
@ -0,0 +1,243 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
GOROOT=${1:-../go}
|
||||
JSONROOT="."
|
||||
|
||||
# Check if the Go toolchain has a clean checkout.
|
||||
if [ -n "$(cd $GOROOT; git status --porcelain)" ]; then
|
||||
(cd $GOROOT; git status --porcelain)
|
||||
echo "Working directory is not clean."
|
||||
echo ""
|
||||
echo "To cleanup, run:"
|
||||
echo " (cd $GOROOT && git checkout . && git clean -fd)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
/bin/rm -rf $GOROOT/src/encoding/json/*
|
||||
cp $JSONROOT/v1/* $GOROOT/src/encoding/json/
|
||||
cp -r $JSONROOT/internal/ $GOROOT/src/encoding/json/internal/
|
||||
mkdir $GOROOT/src/encoding/json/v2/
|
||||
cp -r $JSONROOT/*.go $GOROOT/src/encoding/json/v2/
|
||||
mkdir $GOROOT/src/encoding/json/jsontext/
|
||||
cp -r $JSONROOT/jsontext/*.go $GOROOT/src/encoding/json/jsontext/
|
||||
find $GOROOT/src/encoding/json -type f -exec sed -i 's|github[.]com/go-json-experiment/json/v1|encoding/json|g' {} +
|
||||
find $GOROOT/src/encoding/json -type f -exec sed -i 's|github[.]com/go-json-experiment/json/|encoding/json/|g' {} +
|
||||
find $GOROOT/src/encoding/json -type f -exec sed -i 's|github[.]com/go-json-experiment/json|encoding/json/v2|g' {} +
|
||||
|
||||
# Adjust for changed package path.
|
||||
sed -i 's/json\.struct/v2.struct/g' $GOROOT/src/encoding/json/v2/errors_test.go
|
||||
|
||||
# Adjust tests that hardcode formatted error strings.
|
||||
sed -i 's/}`, "Time.UnmarshalJSON: input is not a JSON string/}`, "json: cannot unmarshal JSON object into Go type time.Time/g' $GOROOT/src/time/time_test.go
|
||||
sed -i 's/]`, "Time.UnmarshalJSON: input is not a JSON string/]`, "json: cannot unmarshal JSON array into Go type time.Time/g' $GOROOT/src/time/time_test.go
|
||||
|
||||
# Adjust for changed dependency tree.
|
||||
sed -i 's|encoding/json|encoding/json/v2|g' $GOROOT/src/cmd/go/internal/imports/scan_test.go
|
||||
sed -i 's|encoding/binary|internal/reflectlite|g' $GOROOT/src/cmd/go/internal/imports/scan_test.go
|
||||
LINE=$(sed -n '/encoding\/json, encoding\/pem, encoding\/xml, mime;/=' $GOROOT/src/go/build/deps_test.go)
|
||||
sed -i 's|encoding/json, encoding/pem, encoding/xml, mime|encoding/pem, encoding/xml, mime|g' $GOROOT/src/go/build/deps_test.go
|
||||
sed -i "$((LINE+ 1)) i\\\\" $GOROOT/src/go/build/deps_test.go
|
||||
sed -i "$((LINE+ 2)) i\\\tSTR, errors" $GOROOT/src/go/build/deps_test.go
|
||||
sed -i "$((LINE+ 3)) i\\\t< encoding/json/internal" $GOROOT/src/go/build/deps_test.go
|
||||
sed -i "$((LINE+ 4)) i\\\t< encoding/json/internal/jsonflags" $GOROOT/src/go/build/deps_test.go
|
||||
sed -i "$((LINE+ 5)) i\\\t< encoding/json/internal/jsonopts" $GOROOT/src/go/build/deps_test.go
|
||||
sed -i "$((LINE+ 6)) i\\\t< encoding/json/internal/jsonwire" $GOROOT/src/go/build/deps_test.go
|
||||
sed -i "$((LINE+ 7)) i\\\t< encoding/json/jsontext;" $GOROOT/src/go/build/deps_test.go
|
||||
sed -i "$((LINE+ 8)) i\\\\" $GOROOT/src/go/build/deps_test.go
|
||||
sed -i "$((LINE+ 9)) i\\\tFMT," $GOROOT/src/go/build/deps_test.go
|
||||
sed -i "$((LINE+10)) i\\\tencoding/hex," $GOROOT/src/go/build/deps_test.go
|
||||
sed -i "$((LINE+11)) i\\\tencoding/base32," $GOROOT/src/go/build/deps_test.go
|
||||
sed -i "$((LINE+12)) i\\\tencoding/base64," $GOROOT/src/go/build/deps_test.go
|
||||
sed -i "$((LINE+13)) i\\\tencoding/binary," $GOROOT/src/go/build/deps_test.go
|
||||
sed -i "$((LINE+14)) i\\\tencoding/json/jsontext," $GOROOT/src/go/build/deps_test.go
|
||||
sed -i "$((LINE+15)) i\\\tencoding/json/internal," $GOROOT/src/go/build/deps_test.go
|
||||
sed -i "$((LINE+16)) i\\\tencoding/json/internal/jsonflags," $GOROOT/src/go/build/deps_test.go
|
||||
sed -i "$((LINE+17)) i\\\tencoding/json/internal/jsonopts," $GOROOT/src/go/build/deps_test.go
|
||||
sed -i "$((LINE+18)) i\\\tencoding/json/internal/jsonwire" $GOROOT/src/go/build/deps_test.go
|
||||
sed -i "$((LINE+19)) i\\\t< encoding/json/v2" $GOROOT/src/go/build/deps_test.go
|
||||
sed -i "$((LINE+20)) i\\\t< encoding/json;" $GOROOT/src/go/build/deps_test.go
|
||||
LINE=$(sed -n '/Test-only packages can have anything they want/=' $GOROOT/src/go/build/deps_test.go)
|
||||
sed -i "$((LINE+1)) i\\\tFMT, compress/gzip, embed, encoding/binary < encoding/json/internal/jsontest;" $GOROOT/src/go/build/deps_test.go
|
||||
|
||||
# Adjust for newly added API.
|
||||
ISSUE=71497
|
||||
FILE="next/$ISSUE.txt"
|
||||
NEXT="$GOROOT/doc/next/6-stdlib/99-minor"
|
||||
mkdir -p $NEXT/encoding/json
|
||||
echo "A new [Options] type with associated constructors provide individual options" >> $NEXT/encoding/json/$ISSUE.md
|
||||
echo "to configure \"encoding/json/v2\" to operate with certain historical v1 behavior." >> $NEXT/encoding/json/$ISSUE.md
|
||||
echo "The [DefaultOptionsV2] option represents the set of all options needed" >> $NEXT/encoding/json/$ISSUE.md
|
||||
echo "to configure \"encoding/json/v2\" to entirely operate with historical v1 behavior." >> $NEXT/encoding/json/$ISSUE.md
|
||||
mkdir -p $NEXT/encoding/json/v2
|
||||
echo "A new major version of \"encoding/json\" for processing JSON at a semantic level which is" >> $NEXT/encoding/json/v2/$ISSUE.md
|
||||
echo "functionality that determines the meaning of JSON values as Go values and vice-versa." >> $NEXT/encoding/json/v2/$ISSUE.md
|
||||
mkdir -p $NEXT/encoding/json/jsontext
|
||||
echo "A new package to process JSON at a syntactic level that" >> $NEXT/encoding/json/jsontext/$ISSUE.md
|
||||
echo "is concerned with processing JSON based on its grammar alone." >> $NEXT/encoding/json/jsontext/$ISSUE.md
|
||||
NEXT="$GOROOT/api/next/$ISSUE.txt"
|
||||
echo "pkg encoding/json, func CallMethodsWithLegacySemantics(bool) jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json, func DefaultOptionsV1() jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json, func EscapeInvalidUTF8(bool) jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json, func FormatBytesWithLegacySemantics(bool) jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json, func FormatTimeWithLegacySemantics(bool) jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json, func MatchCaseSensitiveDelimiter(bool) jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json, func MergeWithLegacySemantics(bool) jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json, func OmitEmptyWithLegacyDefinition(bool) jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json, func ReportErrorsWithLegacySemantics(bool) jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json, func StringifyWithLegacySemantics(bool) jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json, func UnmarshalArrayFromAnyLength(bool) jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json, method (*Number) UnmarshalJSONFrom(*jsontext.Decoder, jsonopts.Options) error #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json, method (*UnmarshalTypeError) Unwrap() error #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json, method (Number) MarshalJSONTo(*jsontext.Encoder, jsonopts.Options) error #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json, type Marshaler = json.Marshaler #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json, type Options = jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json, type RawMessage = jsontext.Value #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json, type UnmarshalTypeError struct, Err error #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json, type Unmarshaler = json.Unmarshaler #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, func AllowDuplicateNames(bool) jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, func AllowInvalidUTF8(bool) jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, func AppendFormat([]uint8, []uint8, ...jsonopts.Options) ([]uint8, error) #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, func AppendQuote[\$0 interface{ ~[]uint8 | ~string }]([]uint8, \$0) ([]uint8, error) #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, func AppendUnquote[\$0 interface{ ~[]uint8 | ~string }]([]uint8, \$0) ([]uint8, error) #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, func Bool(bool) Token #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, func CanonicalizeRawFloats(bool) jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, func CanonicalizeRawInts(bool) jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, func EscapeForHTML(bool) jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, func EscapeForJS(bool) jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, func Float(float64) Token #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, func Int(int64) Token #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, func Multiline(bool) jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, func NewDecoder(io.Reader, ...jsonopts.Options) *Decoder #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, func NewEncoder(io.Writer, ...jsonopts.Options) *Encoder #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, func PreserveRawStrings(bool) jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, func ReorderRawObjects(bool) jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, func SpaceAfterColon(bool) jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, func SpaceAfterComma(bool) jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, func String(string) Token #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, func Uint(uint64) Token #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, func WithIndent(string) jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, func WithIndentPrefix(string) jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (*Decoder) InputOffset() int64 #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (*Decoder) PeekKind() Kind #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (*Decoder) ReadToken() (Token, error) #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (*Decoder) ReadValue() (Value, error) #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (*Decoder) Reset(io.Reader, ...jsonopts.Options) #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (*Decoder) SkipValue() error #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (*Decoder) StackDepth() int #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (*Decoder) StackIndex(int) (Kind, int64) #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (*Decoder) StackPointer() Pointer #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (*Decoder) UnreadBuffer() []uint8 #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (*Encoder) OutputOffset() int64 #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (*Encoder) Reset(io.Writer, ...jsonopts.Options) #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (*Encoder) StackDepth() int #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (*Encoder) StackIndex(int) (Kind, int64) #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (*Encoder) StackPointer() Pointer #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (*Encoder) UnusedBuffer() []uint8 #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (*Encoder) WriteToken(Token) error #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (*Encoder) WriteValue(Value) error #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (*SyntacticError) Error() string #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (*SyntacticError) Unwrap() error #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (*Value) Canonicalize(...jsonopts.Options) error #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (*Value) Compact(...jsonopts.Options) error #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (*Value) Format(...jsonopts.Options) error #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (*Value) Indent(...jsonopts.Options) error #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (*Value) UnmarshalJSON([]uint8) error #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (Kind) String() string #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (Pointer) AppendToken(string) Pointer #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (Pointer) Contains(Pointer) bool #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (Pointer) IsValid() bool #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (Pointer) LastToken() string #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (Pointer) Parent() Pointer #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (Pointer) Tokens() iter.Seq[string] #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (Token) Bool() bool #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (Token) Clone() Token #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (Token) Float() float64 #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (Token) Int() int64 #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (Token) Kind() Kind #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (Token) String() string #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (Token) Uint() uint64 #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (Value) Clone() Value #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (Value) IsValid(...jsonopts.Options) bool #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (Value) Kind() Kind #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (Value) MarshalJSON() ([]uint8, error) #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, method (Value) String() string #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, type Decoder struct #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, type Encoder struct #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, type Kind uint8 #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, type Options = jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, type Pointer string #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, type SyntacticError struct #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, type SyntacticError struct, ByteOffset int64 #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, type SyntacticError struct, Err error #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, type SyntacticError struct, JSONPointer Pointer #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, type Token struct #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, type Value []uint8 #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, var BeginArray Token #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, var BeginObject Token #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, var EndArray Token #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, var EndObject Token #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, var ErrDuplicateName error #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, var ErrNonStringName error #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, var False Token #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, var Internal exporter #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, var Null Token #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/jsontext, var True Token #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, func DefaultOptionsV2() jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, func Deterministic(bool) jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, func DiscardUnknownMembers(bool) jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, func FormatNilMapAsNull(bool) jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, func FormatNilSliceAsNull(bool) jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, func GetOption[\$0 interface{}](jsonopts.Options, func(\$0) jsonopts.Options) (\$0, bool) #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, func JoinMarshalers(...*typedArshalers[jsontext.Encoder]) *typedArshalers[jsontext.Encoder] #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, func JoinOptions(...jsonopts.Options) jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, func JoinUnmarshalers(...*typedArshalers[jsontext.Decoder]) *typedArshalers[jsontext.Decoder] #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, func Marshal(interface{}, ...jsonopts.Options) ([]uint8, error) #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, func MarshalEncode(*jsontext.Encoder, interface{}, ...jsonopts.Options) error #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, func MarshalFunc[\$0 interface{}](func(\$0) ([]uint8, error)) *typedArshalers[jsontext.Encoder] #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, func MarshalToFunc[\$0 interface{}](func(*jsontext.Encoder, \$0, jsonopts.Options) error) *typedArshalers[jsontext.Encoder] #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, func MarshalWrite(io.Writer, interface{}, ...jsonopts.Options) error #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, func MatchCaseInsensitiveNames(bool) jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, func OmitZeroStructFields(bool) jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, func RejectUnknownMembers(bool) jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, func StringifyNumbers(bool) jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, func Unmarshal([]uint8, interface{}, ...jsonopts.Options) error #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, func UnmarshalDecode(*jsontext.Decoder, interface{}, ...jsonopts.Options) error #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, func UnmarshalFromFunc[\$0 interface{}](func(*jsontext.Decoder, \$0, jsonopts.Options) error) *typedArshalers[jsontext.Decoder] #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, func UnmarshalFunc[\$0 interface{}](func([]uint8, \$0) error) *typedArshalers[jsontext.Decoder] #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, func UnmarshalRead(io.Reader, interface{}, ...jsonopts.Options) error #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, func WithMarshalers(*typedArshalers[jsontext.Encoder]) jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, func WithUnmarshalers(*typedArshalers[jsontext.Decoder]) jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, method (*SemanticError) Error() string #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, method (*SemanticError) Unwrap() error #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, type Marshaler interface { MarshalJSON } #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, type Marshaler interface, MarshalJSON() ([]uint8, error) #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, type MarshalerTo interface { MarshalJSONTo } #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, type MarshalerTo interface, MarshalJSONTo(*jsontext.Encoder, jsonopts.Options) error #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, type Marshalers = typedArshalers[jsontext.Encoder] #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, type Options = jsonopts.Options #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, type SemanticError struct #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, type SemanticError struct, ByteOffset int64 #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, type SemanticError struct, Err error #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, type SemanticError struct, GoType reflect.Type #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, type SemanticError struct, JSONKind jsontext.Kind #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, type SemanticError struct, JSONPointer jsontext.Pointer #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, type SemanticError struct, JSONValue jsontext.Value #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, type Unmarshaler interface { UnmarshalJSON } #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, type Unmarshaler interface, UnmarshalJSON([]uint8) error #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, type UnmarshalerFrom interface { UnmarshalJSONFrom } #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, type UnmarshalerFrom interface, UnmarshalJSONFrom(*jsontext.Decoder, jsonopts.Options) error #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, type Unmarshalers = typedArshalers[jsontext.Decoder] #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, var ErrUnknownName error #$ISSUE" >> $NEXT
|
||||
echo "pkg encoding/json/v2, var SkipFunc error #$ISSUE" >> $NEXT
|
||||
# The following declarations were moved to encoding/json/v2 or encoding/json/jsontext.
|
||||
EXCEPT="$GOROOT/api/except.txt"
|
||||
echo "pkg encoding/json, method (*RawMessage) UnmarshalJSON([]uint8) error" >> $EXCEPT
|
||||
echo "pkg encoding/json, method (RawMessage) MarshalJSON() ([]uint8, error)" >> $EXCEPT
|
||||
echo "pkg encoding/json, type Marshaler interface { MarshalJSON }" >> $EXCEPT
|
||||
echo "pkg encoding/json, type Marshaler interface, MarshalJSON() ([]uint8, error)" >> $EXCEPT
|
||||
echo "pkg encoding/json, type RawMessage []uint8" >> $EXCEPT
|
||||
echo "pkg encoding/json, type Unmarshaler interface { UnmarshalJSON }" >> $EXCEPT
|
||||
echo "pkg encoding/json, type Unmarshaler interface, UnmarshalJSON([]uint8) error" >> $EXCEPT
|
||||
|
||||
# Run the tests.
|
||||
(cd $GOROOT/src; ./all.bash)
|
286
vendor/github.com/go-json-experiment/json/options.go
generated
vendored
Normal file
286
vendor/github.com/go-json-experiment/json/options.go
generated
vendored
Normal file
@ -0,0 +1,286 @@
|
||||
// Copyright 2023 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-json-experiment/json/internal"
|
||||
"github.com/go-json-experiment/json/internal/jsonflags"
|
||||
"github.com/go-json-experiment/json/internal/jsonopts"
|
||||
)
|
||||
|
||||
// Options configure [Marshal], [MarshalWrite], [MarshalEncode],
|
||||
// [Unmarshal], [UnmarshalRead], and [UnmarshalDecode] with specific features.
|
||||
// Each function takes in a variadic list of options, where properties
|
||||
// set in later options override the value of previously set properties.
|
||||
//
|
||||
// The Options type is identical to [encoding/json.Options] and
|
||||
// [encoding/json/jsontext.Options]. Options from the other packages can
|
||||
// be used interchangeably with functionality in this package.
|
||||
//
|
||||
// Options represent either a singular option or a set of options.
|
||||
// It can be functionally thought of as a Go map of option properties
|
||||
// (even though the underlying implementation avoids Go maps for performance).
|
||||
//
|
||||
// The constructors (e.g., [Deterministic]) return a singular option value:
|
||||
//
|
||||
// opt := Deterministic(true)
|
||||
//
|
||||
// which is analogous to creating a single entry map:
|
||||
//
|
||||
// opt := Options{"Deterministic": true}
|
||||
//
|
||||
// [JoinOptions] composes multiple options values to together:
|
||||
//
|
||||
// out := JoinOptions(opts...)
|
||||
//
|
||||
// which is analogous to making a new map and copying the options over:
|
||||
//
|
||||
// out := make(Options)
|
||||
// for _, m := range opts {
|
||||
// for k, v := range m {
|
||||
// out[k] = v
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// [GetOption] looks up the value of options parameter:
|
||||
//
|
||||
// v, ok := GetOption(opts, Deterministic)
|
||||
//
|
||||
// which is analogous to a Go map lookup:
|
||||
//
|
||||
// v, ok := Options["Deterministic"]
|
||||
//
|
||||
// There is a single Options type, which is used with both marshal and unmarshal.
|
||||
// Some options affect both operations, while others only affect one operation:
|
||||
//
|
||||
// - [StringifyNumbers] affects marshaling and unmarshaling
|
||||
// - [Deterministic] affects marshaling only
|
||||
// - [FormatNilSliceAsNull] affects marshaling only
|
||||
// - [FormatNilMapAsNull] affects marshaling only
|
||||
// - [OmitZeroStructFields] affects marshaling only
|
||||
// - [MatchCaseInsensitiveNames] affects marshaling and unmarshaling
|
||||
// - [DiscardUnknownMembers] affects marshaling only
|
||||
// - [RejectUnknownMembers] affects unmarshaling only
|
||||
// - [WithMarshalers] affects marshaling only
|
||||
// - [WithUnmarshalers] affects unmarshaling only
|
||||
//
|
||||
// Options that do not affect a particular operation are ignored.
|
||||
type Options = jsonopts.Options
|
||||
|
||||
// JoinOptions coalesces the provided list of options into a single Options.
|
||||
// Properties set in later options override the value of previously set properties.
|
||||
func JoinOptions(srcs ...Options) Options {
|
||||
var dst jsonopts.Struct
|
||||
dst.Join(srcs...)
|
||||
return &dst
|
||||
}
|
||||
|
||||
// GetOption returns the value stored in opts with the provided setter,
|
||||
// reporting whether the value is present.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// v, ok := json.GetOption(opts, json.Deterministic)
|
||||
//
|
||||
// Options are most commonly introspected to alter the JSON representation of
|
||||
// [MarshalerTo.MarshalJSONTo] and [UnmarshalerFrom.UnmarshalJSONFrom] methods, and
|
||||
// [MarshalToFunc] and [UnmarshalFromFunc] functions.
|
||||
// In such cases, the presence bit should generally be ignored.
|
||||
func GetOption[T any](opts Options, setter func(T) Options) (T, bool) {
|
||||
return jsonopts.GetOption(opts, setter)
|
||||
}
|
||||
|
||||
// DefaultOptionsV2 is the full set of all options that define v2 semantics.
|
||||
// It is equivalent to all options under [Options], [encoding/json.Options],
|
||||
// and [encoding/json/jsontext.Options] being set to false or the zero value,
|
||||
// except for the options related to whitespace formatting.
|
||||
func DefaultOptionsV2() Options {
|
||||
return &jsonopts.DefaultOptionsV2
|
||||
}
|
||||
|
||||
// StringifyNumbers specifies that numeric Go types should be marshaled
|
||||
// as a JSON string containing the equivalent JSON number value.
|
||||
// When unmarshaling, numeric Go types are parsed from a JSON string
|
||||
// containing the JSON number without any surrounding whitespace.
|
||||
//
|
||||
// According to RFC 8259, section 6, a JSON implementation may choose to
|
||||
// limit the representation of a JSON number to an IEEE 754 binary64 value.
|
||||
// This may cause decoders to lose precision for int64 and uint64 types.
|
||||
// Quoting JSON numbers as a JSON string preserves the exact precision.
|
||||
//
|
||||
// This affects either marshaling or unmarshaling.
|
||||
func StringifyNumbers(v bool) Options {
|
||||
if v {
|
||||
return jsonflags.StringifyNumbers | 1
|
||||
} else {
|
||||
return jsonflags.StringifyNumbers | 0
|
||||
}
|
||||
}
|
||||
|
||||
// Deterministic specifies that the same input value will be serialized
|
||||
// as the exact same output bytes. Different processes of
|
||||
// the same program will serialize equal values to the same bytes,
|
||||
// but different versions of the same program are not guaranteed
|
||||
// to produce the exact same sequence of bytes.
|
||||
//
|
||||
// This only affects marshaling and is ignored when unmarshaling.
|
||||
func Deterministic(v bool) Options {
|
||||
if v {
|
||||
return jsonflags.Deterministic | 1
|
||||
} else {
|
||||
return jsonflags.Deterministic | 0
|
||||
}
|
||||
}
|
||||
|
||||
// FormatNilSliceAsNull specifies that a nil Go slice should marshal as a
|
||||
// JSON null instead of the default representation as an empty JSON array
|
||||
// (or an empty JSON string in the case of ~[]byte).
|
||||
// Slice fields explicitly marked with `format:emitempty` still marshal
|
||||
// as an empty JSON array.
|
||||
//
|
||||
// This only affects marshaling and is ignored when unmarshaling.
|
||||
func FormatNilSliceAsNull(v bool) Options {
|
||||
if v {
|
||||
return jsonflags.FormatNilSliceAsNull | 1
|
||||
} else {
|
||||
return jsonflags.FormatNilSliceAsNull | 0
|
||||
}
|
||||
}
|
||||
|
||||
// FormatNilMapAsNull specifies that a nil Go map should marshal as a
|
||||
// JSON null instead of the default representation as an empty JSON object.
|
||||
// Map fields explicitly marked with `format:emitempty` still marshal
|
||||
// as an empty JSON object.
|
||||
//
|
||||
// This only affects marshaling and is ignored when unmarshaling.
|
||||
func FormatNilMapAsNull(v bool) Options {
|
||||
if v {
|
||||
return jsonflags.FormatNilMapAsNull | 1
|
||||
} else {
|
||||
return jsonflags.FormatNilMapAsNull | 0
|
||||
}
|
||||
}
|
||||
|
||||
// OmitZeroStructFields specifies that a Go struct should marshal in such a way
|
||||
// that all struct fields that are zero are omitted from the marshaled output
|
||||
// if the value is zero as determined by the "IsZero() bool" method if present,
|
||||
// otherwise based on whether the field is the zero Go value.
|
||||
// This is semantically equivalent to specifying the `omitzero` tag option
|
||||
// on every field in a Go struct.
|
||||
//
|
||||
// This only affects marshaling and is ignored when unmarshaling.
|
||||
func OmitZeroStructFields(v bool) Options {
|
||||
if v {
|
||||
return jsonflags.OmitZeroStructFields | 1
|
||||
} else {
|
||||
return jsonflags.OmitZeroStructFields | 0
|
||||
}
|
||||
}
|
||||
|
||||
// MatchCaseInsensitiveNames specifies that JSON object members are matched
|
||||
// against Go struct fields using a case-insensitive match of the name.
|
||||
// Go struct fields explicitly marked with `case:strict` or `case:ignore`
|
||||
// always use case-sensitive (or case-insensitive) name matching,
|
||||
// regardless of the value of this option.
|
||||
//
|
||||
// This affects either marshaling or unmarshaling.
|
||||
// For marshaling, this option may alter the detection of duplicate names
|
||||
// (assuming [jsontext.AllowDuplicateNames] is false) from inlined fields
|
||||
// if it matches one of the declared fields in the Go struct.
|
||||
func MatchCaseInsensitiveNames(v bool) Options {
|
||||
if v {
|
||||
return jsonflags.MatchCaseInsensitiveNames | 1
|
||||
} else {
|
||||
return jsonflags.MatchCaseInsensitiveNames | 0
|
||||
}
|
||||
}
|
||||
|
||||
// DiscardUnknownMembers specifies that marshaling should ignore any
|
||||
// JSON object members stored in Go struct fields dedicated to storing
|
||||
// unknown JSON object members.
|
||||
//
|
||||
// This only affects marshaling and is ignored when unmarshaling.
|
||||
func DiscardUnknownMembers(v bool) Options {
|
||||
if v {
|
||||
return jsonflags.DiscardUnknownMembers | 1
|
||||
} else {
|
||||
return jsonflags.DiscardUnknownMembers | 0
|
||||
}
|
||||
}
|
||||
|
||||
// RejectUnknownMembers specifies that unknown members should be rejected
|
||||
// when unmarshaling a JSON object, regardless of whether there is a field
|
||||
// to store unknown members.
|
||||
//
|
||||
// This only affects unmarshaling and is ignored when marshaling.
|
||||
func RejectUnknownMembers(v bool) Options {
|
||||
if v {
|
||||
return jsonflags.RejectUnknownMembers | 1
|
||||
} else {
|
||||
return jsonflags.RejectUnknownMembers | 0
|
||||
}
|
||||
}
|
||||
|
||||
// WithMarshalers specifies a list of type-specific marshalers to use,
|
||||
// which can be used to override the default marshal behavior for values
|
||||
// of particular types.
|
||||
//
|
||||
// This only affects marshaling and is ignored when unmarshaling.
|
||||
func WithMarshalers(v *Marshalers) Options {
|
||||
return (*marshalersOption)(v)
|
||||
}
|
||||
|
||||
// WithUnmarshalers specifies a list of type-specific unmarshalers to use,
|
||||
// which can be used to override the default unmarshal behavior for values
|
||||
// of particular types.
|
||||
//
|
||||
// This only affects unmarshaling and is ignored when marshaling.
|
||||
func WithUnmarshalers(v *Unmarshalers) Options {
|
||||
return (*unmarshalersOption)(v)
|
||||
}
|
||||
|
||||
// These option types are declared here instead of "jsonopts"
|
||||
// to avoid a dependency on "reflect" from "jsonopts".
|
||||
type (
|
||||
marshalersOption Marshalers
|
||||
unmarshalersOption Unmarshalers
|
||||
)
|
||||
|
||||
func (*marshalersOption) JSONOptions(internal.NotForPublicUse) {}
|
||||
func (*unmarshalersOption) JSONOptions(internal.NotForPublicUse) {}
|
||||
|
||||
// Inject support into "jsonopts" to handle these types.
|
||||
func init() {
|
||||
jsonopts.GetUnknownOption = func(src *jsonopts.Struct, zero jsonopts.Options) (any, bool) {
|
||||
switch zero.(type) {
|
||||
case *marshalersOption:
|
||||
if !src.Flags.Has(jsonflags.Marshalers) {
|
||||
return (*Marshalers)(nil), false
|
||||
}
|
||||
return src.Marshalers.(*Marshalers), true
|
||||
case *unmarshalersOption:
|
||||
if !src.Flags.Has(jsonflags.Unmarshalers) {
|
||||
return (*Unmarshalers)(nil), false
|
||||
}
|
||||
return src.Unmarshalers.(*Unmarshalers), true
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown option %T", zero))
|
||||
}
|
||||
}
|
||||
jsonopts.JoinUnknownOption = func(dst *jsonopts.Struct, src jsonopts.Options) {
|
||||
switch src := src.(type) {
|
||||
case *marshalersOption:
|
||||
dst.Flags.Set(jsonflags.Marshalers | 1)
|
||||
dst.Marshalers = (*Marshalers)(src)
|
||||
case *unmarshalersOption:
|
||||
dst.Flags.Set(jsonflags.Unmarshalers | 1)
|
||||
dst.Unmarshalers = (*Unmarshalers)(src)
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown option %T", src))
|
||||
}
|
||||
}
|
||||
}
|
11
vendor/github.com/urfave/cli/v3/.gitignore
generated
vendored
Normal file
11
vendor/github.com/urfave/cli/v3/.gitignore
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
*.coverprofile
|
||||
*.exe
|
||||
*.orig
|
||||
.*envrc
|
||||
.envrc
|
||||
.idea
|
||||
/.local/
|
||||
/site/
|
||||
coverage.txt
|
||||
examples/*/built-example
|
||||
vendor
|
5
vendor/github.com/urfave/cli/v3/.golangci.yaml
generated
vendored
Normal file
5
vendor/github.com/urfave/cli/v3/.golangci.yaml
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
# https://golangci-lint.run/usage/configuration/
|
||||
linters:
|
||||
enable:
|
||||
- makezero
|
||||
- misspell
|
75
vendor/github.com/urfave/cli/v3/CODE_OF_CONDUCT.md
generated
vendored
Normal file
75
vendor/github.com/urfave/cli/v3/CODE_OF_CONDUCT.md
generated
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||
education, socio-economic status, nationality, personal appearance, race,
|
||||
religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting urfave-governance@googlegroups.com, a members-only group
|
||||
that is world-postable. All complaints will be reviewed and investigated and
|
||||
will result in a response that is deemed necessary and appropriate to the
|
||||
circumstances. The project team is obligated to maintain confidentiality with
|
||||
regard to the reporter of an incident. Further details of specific enforcement
|
||||
policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
21
vendor/github.com/urfave/cli/v3/LICENSE
generated
vendored
Normal file
21
vendor/github.com/urfave/cli/v3/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 urfave/cli maintainers
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
26
vendor/github.com/urfave/cli/v3/Makefile
generated
vendored
Normal file
26
vendor/github.com/urfave/cli/v3/Makefile
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
# NOTE: this Makefile is meant to provide a simplified entry point for humans to
|
||||
# run all of the critical steps to verify one's changes are harmonious in
|
||||
# nature. Keeping target bodies to one line each and abstaining from make magic
|
||||
# are very important so that maintainers and contributors can focus their
|
||||
# attention on files that are primarily Go.
|
||||
|
||||
GO_RUN_BUILD := go run scripts/build.go
|
||||
|
||||
.PHONY: all
|
||||
all: generate vet test check-binary-size gfmrun
|
||||
|
||||
# NOTE: this is a special catch-all rule to run any of the commands
|
||||
# defined in scripts/build.go with optional arguments passed
|
||||
# via GFLAGS (global flags) and FLAGS (command-specific flags), e.g.:
|
||||
#
|
||||
# $ make test GFLAGS='--packages cli'
|
||||
%:
|
||||
$(GO_RUN_BUILD) $(GFLAGS) $* $(FLAGS)
|
||||
|
||||
.PHONY: docs
|
||||
docs:
|
||||
mkdocs build
|
||||
|
||||
.PHONY: serve-docs
|
||||
serve-docs:
|
||||
mkdocs serve
|
56
vendor/github.com/urfave/cli/v3/README.md
generated
vendored
Normal file
56
vendor/github.com/urfave/cli/v3/README.md
generated
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
# Welcome to urfave/cli
|
||||
|
||||
[![Go Reference][goreference_badge]][goreference_link]
|
||||
[![Go Report Card][goreportcard_badge]][goreportcard_link]
|
||||
[![codecov][codecov_badge]][codecov_link]
|
||||
[![Tests status][test_badge]][test_link]
|
||||
|
||||
urfave/cli is a **declarative**, simple, fast, and fun package for building
|
||||
command line tools in Go featuring:
|
||||
|
||||
- commands and subcommands with alias and prefix match support
|
||||
- flexible and permissive help system
|
||||
- dynamic shell completion for `bash`, `zsh`, `fish`, and `powershell`
|
||||
- no dependencies except Go standard library
|
||||
- input flags for simple types, slices of simple types, time, duration, and
|
||||
others
|
||||
- compound short flag support (`-a` `-b` `-c` can be shortened to `-abc`)
|
||||
- documentation generation in `man` and Markdown (supported via the
|
||||
[`urfave/cli-docs`][urfave/cli-docs] module)
|
||||
- input lookup from:
|
||||
- environment variables
|
||||
- plain text files
|
||||
- structured file formats (supported via the
|
||||
[`urfave/cli-altsrc`][urfave/cli-altsrc] module)
|
||||
|
||||
## Documentation
|
||||
|
||||
See the hosted documentation website at <https://cli.urfave.org>. Contents of
|
||||
this website are built from the [`./docs`](./docs) directory.
|
||||
|
||||
## Support
|
||||
|
||||
Check the [Q&A discussions]. If you don't find answer to your question, [create
|
||||
a new discussion].
|
||||
|
||||
If you found a bug or have a feature request, [create a new issue].
|
||||
|
||||
Please keep in mind that this project is run by unpaid volunteers.
|
||||
|
||||
### License
|
||||
|
||||
See [`LICENSE`](./LICENSE).
|
||||
|
||||
[test_badge]: https://github.com/urfave/cli/actions/workflows/test.yml/badge.svg
|
||||
[test_link]: https://github.com/urfave/cli/actions/workflows/test.yml
|
||||
[goreference_badge]: https://pkg.go.dev/badge/github.com/urfave/cli/v3.svg
|
||||
[goreference_link]: https://pkg.go.dev/github.com/urfave/cli/v3
|
||||
[goreportcard_badge]: https://goreportcard.com/badge/github.com/urfave/cli/v3
|
||||
[goreportcard_link]: https://goreportcard.com/report/github.com/urfave/cli/v3
|
||||
[codecov_badge]: https://codecov.io/gh/urfave/cli/branch/main/graph/badge.svg?token=t9YGWLh05g
|
||||
[codecov_link]: https://codecov.io/gh/urfave/cli
|
||||
[Q&A discussions]: https://github.com/urfave/cli/discussions/categories/q-a
|
||||
[create a new discussion]: https://github.com/urfave/cli/discussions/new?category=q-a
|
||||
[urfave/cli-docs]: https://github.com/urfave/cli-docs
|
||||
[urfave/cli-altsrc]: https://github.com/urfave/cli-altsrc
|
||||
[create a new issue]: https://github.com/urfave/cli/issues/new/choose
|
160
vendor/github.com/urfave/cli/v3/args.go
generated
vendored
Normal file
160
vendor/github.com/urfave/cli/v3/args.go
generated
vendored
Normal file
@ -0,0 +1,160 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Args interface {
|
||||
// Get returns the nth argument, or else a blank string
|
||||
Get(n int) string
|
||||
// First returns the first argument, or else a blank string
|
||||
First() string
|
||||
// Tail returns the rest of the arguments (not the first one)
|
||||
// or else an empty string slice
|
||||
Tail() []string
|
||||
// Len returns the length of the wrapped slice
|
||||
Len() int
|
||||
// Present checks if there are any arguments present
|
||||
Present() bool
|
||||
// Slice returns a copy of the internal slice
|
||||
Slice() []string
|
||||
}
|
||||
|
||||
type stringSliceArgs struct {
|
||||
v []string
|
||||
}
|
||||
|
||||
func (a *stringSliceArgs) Get(n int) string {
|
||||
if len(a.v) > n {
|
||||
return a.v[n]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (a *stringSliceArgs) First() string {
|
||||
return a.Get(0)
|
||||
}
|
||||
|
||||
func (a *stringSliceArgs) Tail() []string {
|
||||
if a.Len() >= 2 {
|
||||
tail := a.v[1:]
|
||||
ret := make([]string, len(tail))
|
||||
copy(ret, tail)
|
||||
return ret
|
||||
}
|
||||
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (a *stringSliceArgs) Len() int {
|
||||
return len(a.v)
|
||||
}
|
||||
|
||||
func (a *stringSliceArgs) Present() bool {
|
||||
return a.Len() != 0
|
||||
}
|
||||
|
||||
func (a *stringSliceArgs) Slice() []string {
|
||||
ret := make([]string, len(a.v))
|
||||
copy(ret, a.v)
|
||||
return ret
|
||||
}
|
||||
|
||||
type Argument interface {
|
||||
Parse([]string) ([]string, error)
|
||||
Usage() string
|
||||
}
|
||||
|
||||
// AnyArguments to differentiate between no arguments(nil) vs aleast one
|
||||
var AnyArguments = []Argument{
|
||||
&StringArg{
|
||||
Max: -1,
|
||||
},
|
||||
}
|
||||
|
||||
type ArgumentBase[T any, C any, VC ValueCreator[T, C]] struct {
|
||||
Name string `json:"name"` // the name of this argument
|
||||
Value T `json:"value"` // the default value of this argument
|
||||
Destination *T `json:"-"` // the destination point for this argument
|
||||
Values *[]T `json:"-"` // all the values of this argument, only if multiple are supported
|
||||
UsageText string `json:"usageText"` // the usage text to show
|
||||
Min int `json:"minTimes"` // the min num of occurrences of this argument
|
||||
Max int `json:"maxTimes"` // the max num of occurrences of this argument, set to -1 for unlimited
|
||||
Config C `json:"config"` // config for this argument similar to Flag Config
|
||||
}
|
||||
|
||||
func (a *ArgumentBase[T, C, VC]) Usage() string {
|
||||
if a.UsageText != "" {
|
||||
return a.UsageText
|
||||
}
|
||||
|
||||
usageFormat := ""
|
||||
if a.Min == 0 {
|
||||
if a.Max == 1 {
|
||||
usageFormat = "[%[1]s]"
|
||||
} else {
|
||||
usageFormat = "[%[1]s ...]"
|
||||
}
|
||||
} else {
|
||||
usageFormat = "%[1]s [%[1]s ...]"
|
||||
}
|
||||
return fmt.Sprintf(usageFormat, a.Name)
|
||||
}
|
||||
|
||||
func (a *ArgumentBase[T, C, VC]) Parse(s []string) ([]string, error) {
|
||||
tracef("calling arg%[1] parse with args %[2]", &a.Name, s)
|
||||
if a.Max == 0 {
|
||||
fmt.Printf("WARNING args %s has max 0, not parsing argument\n", a.Name)
|
||||
return s, nil
|
||||
}
|
||||
if a.Max != -1 && a.Min > a.Max {
|
||||
fmt.Printf("WARNING args %s has min[%d] > max[%d], not parsing argument\n", a.Name, a.Min, a.Max)
|
||||
return s, nil
|
||||
}
|
||||
|
||||
count := 0
|
||||
var vc VC
|
||||
var t T
|
||||
value := vc.Create(a.Value, &t, a.Config)
|
||||
values := []T{}
|
||||
|
||||
for _, arg := range s {
|
||||
if err := value.Set(arg); err != nil {
|
||||
return s, err
|
||||
}
|
||||
values = append(values, value.Get().(T))
|
||||
count++
|
||||
if count >= a.Max && a.Max > -1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if count < a.Min {
|
||||
return s, fmt.Errorf("sufficient count of arg %s not provided, given %d expected %d", a.Name, count, a.Min)
|
||||
}
|
||||
|
||||
if a.Values == nil {
|
||||
a.Values = &values
|
||||
} else if count > 0 {
|
||||
*a.Values = values
|
||||
}
|
||||
|
||||
if a.Max == 1 && a.Destination != nil {
|
||||
if len(values) > 0 {
|
||||
*a.Destination = values[0]
|
||||
} else {
|
||||
*a.Destination = t
|
||||
}
|
||||
}
|
||||
|
||||
return s[count:], nil
|
||||
}
|
||||
|
||||
type (
|
||||
FloatArg = ArgumentBase[float64, NoConfig, floatValue]
|
||||
IntArg = ArgumentBase[int64, IntegerConfig, intValue]
|
||||
StringArg = ArgumentBase[string, StringConfig, stringValue]
|
||||
StringMapArg = ArgumentBase[map[string]string, StringConfig, StringMap]
|
||||
TimestampArg = ArgumentBase[time.Time, TimestampConfig, timestampValue]
|
||||
UintArg = ArgumentBase[uint64, IntegerConfig, uintValue]
|
||||
)
|
34
vendor/github.com/urfave/cli/v3/autocomplete/bash_autocomplete
generated
vendored
Normal file
34
vendor/github.com/urfave/cli/v3/autocomplete/bash_autocomplete
generated
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This is a shell completion script auto-generated by https://github.com/urfave/cli for bash.
|
||||
|
||||
# Macs have bash3 for which the bash-completion package doesn't include
|
||||
# _init_completion. This is a minimal version of that function.
|
||||
__%[1]s_init_completion() {
|
||||
COMPREPLY=()
|
||||
_get_comp_words_by_ref "$@" cur prev words cword
|
||||
}
|
||||
|
||||
__%[1]s_bash_autocomplete() {
|
||||
if [[ "${COMP_WORDS[0]}" != "source" ]]; then
|
||||
local cur opts base words
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
if declare -F _init_completion >/dev/null 2>&1; then
|
||||
_init_completion -n "=:" || return
|
||||
else
|
||||
__%[1]s_init_completion -n "=:" || return
|
||||
fi
|
||||
words=("${words[@]:0:$cword}")
|
||||
if [[ "$cur" == "-"* ]]; then
|
||||
requestComp="${words[*]} ${cur} --generate-shell-completion"
|
||||
else
|
||||
requestComp="${words[*]} --generate-shell-completion"
|
||||
fi
|
||||
opts=$(eval "${requestComp}" 2>/dev/null)
|
||||
COMPREPLY=($(compgen -W "${opts}" -- ${cur}))
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
complete -o bashdefault -o default -o nospace -F __%[1]s_bash_autocomplete %[1]s
|
9
vendor/github.com/urfave/cli/v3/autocomplete/powershell_autocomplete.ps1
generated
vendored
Normal file
9
vendor/github.com/urfave/cli/v3/autocomplete/powershell_autocomplete.ps1
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
$fn = $($MyInvocation.MyCommand.Name)
|
||||
$name = $fn -replace "(.*)\.ps1$", '$1'
|
||||
Register-ArgumentCompleter -Native -CommandName $name -ScriptBlock {
|
||||
param($commandName, $wordToComplete, $cursorPosition)
|
||||
$other = "$wordToComplete --generate-shell-completion"
|
||||
Invoke-Expression $other | ForEach-Object {
|
||||
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
|
||||
}
|
||||
}
|
29
vendor/github.com/urfave/cli/v3/autocomplete/zsh_autocomplete
generated
vendored
Normal file
29
vendor/github.com/urfave/cli/v3/autocomplete/zsh_autocomplete
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
#compdef %[1]s
|
||||
compdef _%[1]s %[1]s
|
||||
|
||||
# This is a shell completion script auto-generated by https://github.com/urfave/cli for zsh.
|
||||
|
||||
_%[1]s() {
|
||||
local -a opts # Declare a local array
|
||||
local current
|
||||
current=${words[-1]} # -1 means "the last element"
|
||||
if [[ "$current" == "-"* ]]; then
|
||||
# Current word starts with a hyphen, so complete flags/options
|
||||
opts=("${(@f)$(${words[@]:0:#words[@]-1} ${current} --generate-shell-completion)}")
|
||||
else
|
||||
# Current word does not start with a hyphen, so complete subcommands
|
||||
opts=("${(@f)$(${words[@]:0:#words[@]-1} --generate-shell-completion)}")
|
||||
fi
|
||||
|
||||
if [[ "${opts[1]}" != "" ]]; then
|
||||
_describe 'values' opts
|
||||
else
|
||||
_files
|
||||
fi
|
||||
}
|
||||
|
||||
# Don't run the completion function when being source-ed or eval-ed.
|
||||
# See https://github.com/urfave/cli/issues/1874 for discussion.
|
||||
if [ "$funcstack[1]" = "_%[1]s" ]; then
|
||||
_%[1]s
|
||||
fi
|
195
vendor/github.com/urfave/cli/v3/category.go
generated
vendored
Normal file
195
vendor/github.com/urfave/cli/v3/category.go
generated
vendored
Normal file
@ -0,0 +1,195 @@
|
||||
package cli
|
||||
|
||||
import "sort"
|
||||
|
||||
// CommandCategories interface allows for category manipulation
|
||||
type CommandCategories interface {
|
||||
// AddCommand adds a command to a category, creating a new category if necessary.
|
||||
AddCommand(category string, command *Command)
|
||||
// Categories returns a slice of categories sorted by name
|
||||
Categories() []CommandCategory
|
||||
}
|
||||
|
||||
type commandCategories []*commandCategory
|
||||
|
||||
func newCommandCategories() CommandCategories {
|
||||
ret := commandCategories([]*commandCategory{})
|
||||
return &ret
|
||||
}
|
||||
|
||||
func (c *commandCategories) Less(i, j int) bool {
|
||||
return lexicographicLess((*c)[i].Name(), (*c)[j].Name())
|
||||
}
|
||||
|
||||
func (c *commandCategories) Len() int {
|
||||
return len(*c)
|
||||
}
|
||||
|
||||
func (c *commandCategories) Swap(i, j int) {
|
||||
(*c)[i], (*c)[j] = (*c)[j], (*c)[i]
|
||||
}
|
||||
|
||||
func (c *commandCategories) AddCommand(category string, command *Command) {
|
||||
for _, commandCategory := range []*commandCategory(*c) {
|
||||
if commandCategory.name == category {
|
||||
commandCategory.commands = append(commandCategory.commands, command)
|
||||
return
|
||||
}
|
||||
}
|
||||
newVal := append(*c,
|
||||
&commandCategory{name: category, commands: []*Command{command}})
|
||||
*c = newVal
|
||||
}
|
||||
|
||||
func (c *commandCategories) Categories() []CommandCategory {
|
||||
ret := make([]CommandCategory, len(*c))
|
||||
for i, cat := range *c {
|
||||
ret[i] = cat
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// CommandCategory is a category containing commands.
|
||||
type CommandCategory interface {
|
||||
// Name returns the category name string
|
||||
Name() string
|
||||
// VisibleCommands returns a slice of the Commands with Hidden=false
|
||||
VisibleCommands() []*Command
|
||||
}
|
||||
|
||||
type commandCategory struct {
|
||||
name string
|
||||
commands []*Command
|
||||
}
|
||||
|
||||
func (c *commandCategory) Name() string {
|
||||
return c.name
|
||||
}
|
||||
|
||||
func (c *commandCategory) VisibleCommands() []*Command {
|
||||
if c.commands == nil {
|
||||
c.commands = []*Command{}
|
||||
}
|
||||
|
||||
var ret []*Command
|
||||
for _, command := range c.commands {
|
||||
if !command.Hidden {
|
||||
ret = append(ret, command)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// FlagCategories interface allows for category manipulation
|
||||
type FlagCategories interface {
|
||||
// AddFlags adds a flag to a category, creating a new category if necessary.
|
||||
AddFlag(category string, fl Flag)
|
||||
// VisibleCategories returns a slice of visible flag categories sorted by name
|
||||
VisibleCategories() []VisibleFlagCategory
|
||||
}
|
||||
|
||||
type defaultFlagCategories struct {
|
||||
m map[string]*defaultVisibleFlagCategory
|
||||
}
|
||||
|
||||
func newFlagCategories() FlagCategories {
|
||||
return &defaultFlagCategories{
|
||||
m: map[string]*defaultVisibleFlagCategory{},
|
||||
}
|
||||
}
|
||||
|
||||
func newFlagCategoriesFromFlags(fs []Flag) FlagCategories {
|
||||
fc := newFlagCategories()
|
||||
|
||||
var categorized bool
|
||||
|
||||
for _, fl := range fs {
|
||||
if cf, ok := fl.(CategorizableFlag); ok {
|
||||
visible := false
|
||||
if vf, ok := fl.(VisibleFlag); ok {
|
||||
visible = vf.IsVisible()
|
||||
}
|
||||
if cat := cf.GetCategory(); cat != "" && visible {
|
||||
fc.AddFlag(cat, fl)
|
||||
categorized = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if categorized {
|
||||
for _, fl := range fs {
|
||||
if cf, ok := fl.(CategorizableFlag); ok {
|
||||
visible := false
|
||||
if vf, ok := fl.(VisibleFlag); ok {
|
||||
visible = vf.IsVisible()
|
||||
}
|
||||
if cf.GetCategory() == "" && visible {
|
||||
fc.AddFlag("", fl)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fc
|
||||
}
|
||||
|
||||
func (f *defaultFlagCategories) AddFlag(category string, fl Flag) {
|
||||
if _, ok := f.m[category]; !ok {
|
||||
f.m[category] = &defaultVisibleFlagCategory{name: category, m: map[string]Flag{}}
|
||||
}
|
||||
|
||||
f.m[category].m[fl.String()] = fl
|
||||
}
|
||||
|
||||
func (f *defaultFlagCategories) VisibleCategories() []VisibleFlagCategory {
|
||||
catNames := []string{}
|
||||
for name := range f.m {
|
||||
catNames = append(catNames, name)
|
||||
}
|
||||
|
||||
sort.Strings(catNames)
|
||||
|
||||
ret := make([]VisibleFlagCategory, len(catNames))
|
||||
for i, name := range catNames {
|
||||
ret[i] = f.m[name]
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// VisibleFlagCategory is a category containing flags.
|
||||
type VisibleFlagCategory interface {
|
||||
// Name returns the category name string
|
||||
Name() string
|
||||
// Flags returns a slice of VisibleFlag sorted by name
|
||||
Flags() []Flag
|
||||
}
|
||||
|
||||
type defaultVisibleFlagCategory struct {
|
||||
name string
|
||||
m map[string]Flag
|
||||
}
|
||||
|
||||
func (fc *defaultVisibleFlagCategory) Name() string {
|
||||
return fc.name
|
||||
}
|
||||
|
||||
func (fc *defaultVisibleFlagCategory) Flags() []Flag {
|
||||
vfNames := []string{}
|
||||
for flName, fl := range fc.m {
|
||||
if vf, ok := fl.(VisibleFlag); ok {
|
||||
if vf.IsVisible() {
|
||||
vfNames = append(vfNames, flName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(vfNames)
|
||||
|
||||
ret := make([]Flag, len(vfNames))
|
||||
for i, flName := range vfNames {
|
||||
ret[i] = fc.m[flName]
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
60
vendor/github.com/urfave/cli/v3/cli.go
generated
vendored
Normal file
60
vendor/github.com/urfave/cli/v3/cli.go
generated
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
// Package cli provides a minimal framework for creating and organizing command line
|
||||
// Go applications. cli is designed to be easy to understand and write, the most simple
|
||||
// cli application can be written as follows:
|
||||
//
|
||||
// func main() {
|
||||
// (&cli.Command{}).Run(context.Background(), os.Args)
|
||||
// }
|
||||
//
|
||||
// Of course this application does not do much, so let's make this an actual application:
|
||||
//
|
||||
// func main() {
|
||||
// cmd := &cli.Command{
|
||||
// Name: "greet",
|
||||
// Usage: "say a greeting",
|
||||
// Action: func(c *cli.Context) error {
|
||||
// fmt.Println("Greetings")
|
||||
// return nil
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// cmd.Run(context.Background(), os.Args)
|
||||
// }
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var isTracingOn = os.Getenv("URFAVE_CLI_TRACING") == "on"
|
||||
|
||||
func tracef(format string, a ...any) {
|
||||
if !isTracingOn {
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(format, "\n") {
|
||||
format = format + "\n"
|
||||
}
|
||||
|
||||
pc, file, line, _ := runtime.Caller(1)
|
||||
cf := runtime.FuncForPC(pc)
|
||||
|
||||
fmt.Fprintf(
|
||||
os.Stderr,
|
||||
strings.Join([]string{
|
||||
"## URFAVE CLI TRACE ",
|
||||
file,
|
||||
":",
|
||||
fmt.Sprintf("%v", line),
|
||||
" ",
|
||||
fmt.Sprintf("(%s)", cf.Name()),
|
||||
" ",
|
||||
format,
|
||||
}, ""),
|
||||
a...,
|
||||
)
|
||||
}
|
1299
vendor/github.com/urfave/cli/v3/command.go
generated
vendored
Normal file
1299
vendor/github.com/urfave/cli/v3/command.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
81
vendor/github.com/urfave/cli/v3/completion.go
generated
vendored
Normal file
81
vendor/github.com/urfave/cli/v3/completion.go
generated
vendored
Normal file
@ -0,0 +1,81 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"fmt"
|
||||
"sort"
|
||||
)
|
||||
|
||||
const (
|
||||
completionCommandName = "completion"
|
||||
|
||||
// This flag is supposed to only be used by the completion script itself to generate completions on the fly.
|
||||
completionFlag = "--generate-shell-completion"
|
||||
)
|
||||
|
||||
type renderCompletion func(cmd *Command, appName string) (string, error)
|
||||
|
||||
var (
|
||||
//go:embed autocomplete
|
||||
autoCompleteFS embed.FS
|
||||
|
||||
shellCompletions = map[string]renderCompletion{
|
||||
"bash": func(c *Command, appName string) (string, error) {
|
||||
b, err := autoCompleteFS.ReadFile("autocomplete/bash_autocomplete")
|
||||
return fmt.Sprintf(string(b), appName), err
|
||||
},
|
||||
"zsh": func(c *Command, appName string) (string, error) {
|
||||
b, err := autoCompleteFS.ReadFile("autocomplete/zsh_autocomplete")
|
||||
return fmt.Sprintf(string(b), appName), err
|
||||
},
|
||||
"fish": func(c *Command, appName string) (string, error) {
|
||||
return c.ToFishCompletion()
|
||||
},
|
||||
"pwsh": func(c *Command, appName string) (string, error) {
|
||||
b, err := autoCompleteFS.ReadFile("autocomplete/powershell_autocomplete.ps1")
|
||||
return string(b), err
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func buildCompletionCommand(appName string) *Command {
|
||||
return &Command{
|
||||
Name: completionCommandName,
|
||||
Hidden: true,
|
||||
Action: func(ctx context.Context, cmd *Command) error {
|
||||
return printShellCompletion(ctx, cmd, appName)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func printShellCompletion(_ context.Context, cmd *Command, appName string) error {
|
||||
var shells []string
|
||||
for k := range shellCompletions {
|
||||
shells = append(shells, k)
|
||||
}
|
||||
|
||||
sort.Strings(shells)
|
||||
|
||||
if cmd.Args().Len() == 0 {
|
||||
return Exit(fmt.Sprintf("no shell provided for completion command. available shells are %+v", shells), 1)
|
||||
}
|
||||
s := cmd.Args().First()
|
||||
|
||||
renderCompletion, ok := shellCompletions[s]
|
||||
if !ok {
|
||||
return Exit(fmt.Sprintf("unknown shell %s, available shells are %+v", s, shells), 1)
|
||||
}
|
||||
|
||||
completionScript, err := renderCompletion(cmd, appName)
|
||||
if err != nil {
|
||||
return Exit(err, 1)
|
||||
}
|
||||
|
||||
_, err = cmd.Writer.Write([]byte(completionScript))
|
||||
if err != nil {
|
||||
return Exit(err, 1)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
185
vendor/github.com/urfave/cli/v3/errors.go
generated
vendored
Normal file
185
vendor/github.com/urfave/cli/v3/errors.go
generated
vendored
Normal file
@ -0,0 +1,185 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// OsExiter is the function used when the app exits. If not set defaults to os.Exit.
|
||||
var OsExiter = os.Exit
|
||||
|
||||
// ErrWriter is used to write errors to the user. This can be anything
|
||||
// implementing the io.Writer interface and defaults to os.Stderr.
|
||||
var ErrWriter io.Writer = os.Stderr
|
||||
|
||||
// MultiError is an error that wraps multiple errors.
|
||||
type MultiError interface {
|
||||
error
|
||||
Errors() []error
|
||||
}
|
||||
|
||||
// newMultiError creates a new MultiError. Pass in one or more errors.
|
||||
func newMultiError(err ...error) MultiError {
|
||||
ret := multiError(err)
|
||||
return &ret
|
||||
}
|
||||
|
||||
type multiError []error
|
||||
|
||||
// Error implements the error interface.
|
||||
func (m *multiError) Error() string {
|
||||
errs := make([]string, len(*m))
|
||||
for i, err := range *m {
|
||||
errs[i] = err.Error()
|
||||
}
|
||||
|
||||
return strings.Join(errs, "\n")
|
||||
}
|
||||
|
||||
// Errors returns a copy of the errors slice
|
||||
func (m *multiError) Errors() []error {
|
||||
errs := make([]error, len(*m))
|
||||
copy(errs, *m)
|
||||
return errs
|
||||
}
|
||||
|
||||
type requiredFlagsErr interface {
|
||||
error
|
||||
}
|
||||
|
||||
type errRequiredFlags struct {
|
||||
missingFlags []string
|
||||
}
|
||||
|
||||
func (e *errRequiredFlags) Error() string {
|
||||
if len(e.missingFlags) == 1 {
|
||||
return fmt.Sprintf("Required flag %q not set", e.missingFlags[0])
|
||||
}
|
||||
joinedMissingFlags := strings.Join(e.missingFlags, ", ")
|
||||
return fmt.Sprintf("Required flags %q not set", joinedMissingFlags)
|
||||
}
|
||||
|
||||
type mutuallyExclusiveGroup struct {
|
||||
flag1Name string
|
||||
flag2Name string
|
||||
}
|
||||
|
||||
func (e *mutuallyExclusiveGroup) Error() string {
|
||||
return fmt.Sprintf("option %s cannot be set along with option %s", e.flag1Name, e.flag2Name)
|
||||
}
|
||||
|
||||
type mutuallyExclusiveGroupRequiredFlag struct {
|
||||
flags *MutuallyExclusiveFlags
|
||||
}
|
||||
|
||||
func (e *mutuallyExclusiveGroupRequiredFlag) Error() string {
|
||||
var missingFlags []string
|
||||
for _, grpf := range e.flags.Flags {
|
||||
var grpString []string
|
||||
for _, f := range grpf {
|
||||
grpString = append(grpString, f.Names()...)
|
||||
}
|
||||
missingFlags = append(missingFlags, strings.Join(grpString, " "))
|
||||
}
|
||||
|
||||
return fmt.Sprintf("one of these flags needs to be provided: %s", strings.Join(missingFlags, ", "))
|
||||
}
|
||||
|
||||
// ErrorFormatter is the interface that will suitably format the error output
|
||||
type ErrorFormatter interface {
|
||||
Format(s fmt.State, verb rune)
|
||||
}
|
||||
|
||||
// ExitCoder is the interface checked by `App` and `Command` for a custom exit
|
||||
// code
|
||||
type ExitCoder interface {
|
||||
error
|
||||
ExitCode() int
|
||||
}
|
||||
|
||||
type exitError struct {
|
||||
exitCode int
|
||||
err error
|
||||
}
|
||||
|
||||
// Exit wraps a message and exit code into an error, which by default is
|
||||
// handled with a call to os.Exit during default error handling.
|
||||
//
|
||||
// This is the simplest way to trigger a non-zero exit code for an App without
|
||||
// having to call os.Exit manually. During testing, this behavior can be avoided
|
||||
// by overriding the ExitErrHandler function on an App or the package-global
|
||||
// OsExiter function.
|
||||
func Exit(message interface{}, exitCode int) ExitCoder {
|
||||
var err error
|
||||
|
||||
switch e := message.(type) {
|
||||
case ErrorFormatter:
|
||||
err = fmt.Errorf("%+v", message)
|
||||
case error:
|
||||
err = e
|
||||
default:
|
||||
err = fmt.Errorf("%+v", message)
|
||||
}
|
||||
|
||||
return &exitError{
|
||||
err: err,
|
||||
exitCode: exitCode,
|
||||
}
|
||||
}
|
||||
|
||||
func (ee *exitError) Error() string {
|
||||
return ee.err.Error()
|
||||
}
|
||||
|
||||
func (ee *exitError) ExitCode() int {
|
||||
return ee.exitCode
|
||||
}
|
||||
|
||||
// HandleExitCoder handles errors implementing ExitCoder by printing their
|
||||
// message and calling OsExiter with the given exit code.
|
||||
//
|
||||
// If the given error instead implements MultiError, each error will be checked
|
||||
// for the ExitCoder interface, and OsExiter will be called with the last exit
|
||||
// code found, or exit code 1 if no ExitCoder is found.
|
||||
//
|
||||
// This function is the default error-handling behavior for an App.
|
||||
func HandleExitCoder(err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if exitErr, ok := err.(ExitCoder); ok {
|
||||
if err.Error() != "" {
|
||||
if _, ok := exitErr.(ErrorFormatter); ok {
|
||||
_, _ = fmt.Fprintf(ErrWriter, "%+v\n", err)
|
||||
} else {
|
||||
_, _ = fmt.Fprintln(ErrWriter, err)
|
||||
}
|
||||
}
|
||||
OsExiter(exitErr.ExitCode())
|
||||
return
|
||||
}
|
||||
|
||||
if multiErr, ok := err.(MultiError); ok {
|
||||
code := handleMultiError(multiErr)
|
||||
OsExiter(code)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func handleMultiError(multiErr MultiError) int {
|
||||
code := 1
|
||||
for _, merr := range multiErr.Errors() {
|
||||
if multiErr2, ok := merr.(MultiError); ok {
|
||||
code = handleMultiError(multiErr2)
|
||||
} else if merr != nil {
|
||||
fmt.Fprintln(ErrWriter, merr)
|
||||
if exitErr, ok := merr.(ExitCoder); ok {
|
||||
code = exitErr.ExitCode()
|
||||
}
|
||||
}
|
||||
}
|
||||
return code
|
||||
}
|
179
vendor/github.com/urfave/cli/v3/fish.go
generated
vendored
Normal file
179
vendor/github.com/urfave/cli/v3/fish.go
generated
vendored
Normal file
@ -0,0 +1,179 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// ToFishCompletion creates a fish completion string for the `*App`
|
||||
// The function errors if either parsing or writing of the string fails.
|
||||
func (cmd *Command) ToFishCompletion() (string, error) {
|
||||
var w bytes.Buffer
|
||||
if err := cmd.writeFishCompletionTemplate(&w); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return w.String(), nil
|
||||
}
|
||||
|
||||
type fishCommandCompletionTemplate struct {
|
||||
Command *Command
|
||||
Completions []string
|
||||
AllCommands []string
|
||||
}
|
||||
|
||||
func (cmd *Command) writeFishCompletionTemplate(w io.Writer) error {
|
||||
const name = "cli"
|
||||
t, err := template.New(name).Parse(FishCompletionTemplate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
allCommands := []string{}
|
||||
|
||||
// Add global flags
|
||||
completions := cmd.prepareFishFlags(cmd.VisibleFlags(), allCommands)
|
||||
|
||||
// Add help flag
|
||||
if !cmd.HideHelp {
|
||||
completions = append(
|
||||
completions,
|
||||
cmd.prepareFishFlags([]Flag{HelpFlag}, allCommands)...,
|
||||
)
|
||||
}
|
||||
|
||||
// Add version flag
|
||||
if !cmd.HideVersion {
|
||||
completions = append(
|
||||
completions,
|
||||
cmd.prepareFishFlags([]Flag{VersionFlag}, allCommands)...,
|
||||
)
|
||||
}
|
||||
|
||||
// Add commands and their flags
|
||||
completions = append(
|
||||
completions,
|
||||
cmd.prepareFishCommands(cmd.VisibleCommands(), &allCommands, []string{})...,
|
||||
)
|
||||
|
||||
return t.ExecuteTemplate(w, name, &fishCommandCompletionTemplate{
|
||||
Command: cmd,
|
||||
Completions: completions,
|
||||
AllCommands: allCommands,
|
||||
})
|
||||
}
|
||||
|
||||
func (cmd *Command) prepareFishCommands(commands []*Command, allCommands *[]string, previousCommands []string) []string {
|
||||
completions := []string{}
|
||||
for _, command := range commands {
|
||||
var completion strings.Builder
|
||||
completion.WriteString(fmt.Sprintf(
|
||||
"complete -r -c %s -n '%s' -a '%s'",
|
||||
cmd.Name,
|
||||
cmd.fishSubcommandHelper(previousCommands),
|
||||
strings.Join(command.Names(), " "),
|
||||
))
|
||||
|
||||
if command.Usage != "" {
|
||||
completion.WriteString(fmt.Sprintf(" -d '%s'",
|
||||
escapeSingleQuotes(command.Usage)))
|
||||
}
|
||||
|
||||
if !command.HideHelp {
|
||||
completions = append(
|
||||
completions,
|
||||
cmd.prepareFishFlags([]Flag{HelpFlag}, command.Names())...,
|
||||
)
|
||||
}
|
||||
|
||||
*allCommands = append(*allCommands, command.Names()...)
|
||||
completions = append(completions, completion.String())
|
||||
completions = append(
|
||||
completions,
|
||||
cmd.prepareFishFlags(command.VisibleFlags(), command.Names())...,
|
||||
)
|
||||
|
||||
// recursively iterate subcommands
|
||||
if len(command.Commands) > 0 {
|
||||
completions = append(
|
||||
completions,
|
||||
cmd.prepareFishCommands(
|
||||
command.Commands, allCommands, command.Names(),
|
||||
)...,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return completions
|
||||
}
|
||||
|
||||
func (cmd *Command) prepareFishFlags(flags []Flag, previousCommands []string) []string {
|
||||
completions := []string{}
|
||||
for _, f := range flags {
|
||||
completion := &strings.Builder{}
|
||||
completion.WriteString(fmt.Sprintf(
|
||||
"complete -c %s -n '%s'",
|
||||
cmd.Name,
|
||||
cmd.fishSubcommandHelper(previousCommands),
|
||||
))
|
||||
|
||||
fishAddFileFlag(f, completion)
|
||||
|
||||
for idx, opt := range f.Names() {
|
||||
if idx == 0 {
|
||||
completion.WriteString(fmt.Sprintf(
|
||||
" -l %s", strings.TrimSpace(opt),
|
||||
))
|
||||
} else {
|
||||
completion.WriteString(fmt.Sprintf(
|
||||
" -s %s", strings.TrimSpace(opt),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
if flag, ok := f.(DocGenerationFlag); ok {
|
||||
if flag.TakesValue() {
|
||||
completion.WriteString(" -r")
|
||||
}
|
||||
|
||||
if flag.GetUsage() != "" {
|
||||
completion.WriteString(fmt.Sprintf(" -d '%s'",
|
||||
escapeSingleQuotes(flag.GetUsage())))
|
||||
}
|
||||
}
|
||||
|
||||
completions = append(completions, completion.String())
|
||||
}
|
||||
|
||||
return completions
|
||||
}
|
||||
|
||||
func fishAddFileFlag(flag Flag, completion *strings.Builder) {
|
||||
switch f := flag.(type) {
|
||||
case *StringFlag:
|
||||
if f.TakesFile {
|
||||
return
|
||||
}
|
||||
case *StringSliceFlag:
|
||||
if f.TakesFile {
|
||||
return
|
||||
}
|
||||
}
|
||||
completion.WriteString(" -f")
|
||||
}
|
||||
|
||||
func (cmd *Command) fishSubcommandHelper(allCommands []string) string {
|
||||
fishHelper := fmt.Sprintf("__fish_%s_no_subcommand", cmd.Name)
|
||||
if len(allCommands) > 0 {
|
||||
fishHelper = fmt.Sprintf(
|
||||
"__fish_seen_subcommand_from %s",
|
||||
strings.Join(allCommands, " "),
|
||||
)
|
||||
}
|
||||
return fishHelper
|
||||
}
|
||||
|
||||
func escapeSingleQuotes(input string) string {
|
||||
return strings.Replace(input, `'`, `\'`, -1)
|
||||
}
|
349
vendor/github.com/urfave/cli/v3/flag.go
generated
vendored
Normal file
349
vendor/github.com/urfave/cli/v3/flag.go
generated
vendored
Normal file
@ -0,0 +1,349 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const defaultPlaceholder = "value"
|
||||
|
||||
var (
|
||||
defaultSliceFlagSeparator = ","
|
||||
defaultMapFlagKeyValueSeparator = "="
|
||||
disableSliceFlagSeparator = false
|
||||
)
|
||||
|
||||
var (
|
||||
slPfx = fmt.Sprintf("sl:::%d:::", time.Now().UTC().UnixNano())
|
||||
|
||||
commaWhitespace = regexp.MustCompile("[, ]+.*")
|
||||
)
|
||||
|
||||
// GenerateShellCompletionFlag enables shell completion
|
||||
var GenerateShellCompletionFlag Flag = &BoolFlag{
|
||||
Name: "generate-shell-completion",
|
||||
Hidden: true,
|
||||
}
|
||||
|
||||
// VersionFlag prints the version for the application
|
||||
var VersionFlag Flag = &BoolFlag{
|
||||
Name: "version",
|
||||
Aliases: []string{"v"},
|
||||
Usage: "print the version",
|
||||
HideDefault: true,
|
||||
Local: true,
|
||||
}
|
||||
|
||||
// HelpFlag prints the help for all commands and subcommands.
|
||||
// Set to nil to disable the flag. The subcommand
|
||||
// will still be added unless HideHelp or HideHelpCommand is set to true.
|
||||
var HelpFlag Flag = &BoolFlag{
|
||||
Name: "help",
|
||||
Aliases: []string{"h"},
|
||||
Usage: "show help",
|
||||
HideDefault: true,
|
||||
Local: true,
|
||||
}
|
||||
|
||||
// FlagStringer converts a flag definition to a string. This is used by help
|
||||
// to display a flag.
|
||||
var FlagStringer FlagStringFunc = stringifyFlag
|
||||
|
||||
// Serializer is used to circumvent the limitations of flag.FlagSet.Set
|
||||
type Serializer interface {
|
||||
Serialize() string
|
||||
}
|
||||
|
||||
// FlagNamePrefixer converts a full flag name and its placeholder into the help
|
||||
// message flag prefix. This is used by the default FlagStringer.
|
||||
var FlagNamePrefixer FlagNamePrefixFunc = prefixedNames
|
||||
|
||||
// FlagEnvHinter annotates flag help message with the environment variable
|
||||
// details. This is used by the default FlagStringer.
|
||||
var FlagEnvHinter FlagEnvHintFunc = withEnvHint
|
||||
|
||||
// FlagFileHinter annotates flag help message with the environment variable
|
||||
// details. This is used by the default FlagStringer.
|
||||
var FlagFileHinter FlagFileHintFunc = withFileHint
|
||||
|
||||
// FlagsByName is a slice of Flag.
|
||||
type FlagsByName []Flag
|
||||
|
||||
func (f FlagsByName) Len() int {
|
||||
return len(f)
|
||||
}
|
||||
|
||||
func (f FlagsByName) Less(i, j int) bool {
|
||||
if len(f[j].Names()) == 0 {
|
||||
return false
|
||||
} else if len(f[i].Names()) == 0 {
|
||||
return true
|
||||
}
|
||||
return lexicographicLess(f[i].Names()[0], f[j].Names()[0])
|
||||
}
|
||||
|
||||
func (f FlagsByName) Swap(i, j int) {
|
||||
f[i], f[j] = f[j], f[i]
|
||||
}
|
||||
|
||||
// ActionableFlag is an interface that wraps Flag interface and RunAction operation.
|
||||
type ActionableFlag interface {
|
||||
RunAction(context.Context, *Command) error
|
||||
}
|
||||
|
||||
// Flag is a common interface related to parsing flags in cli.
|
||||
// For more advanced flag parsing techniques, it is recommended that
|
||||
// this interface be implemented.
|
||||
type Flag interface {
|
||||
fmt.Stringer
|
||||
|
||||
// Apply Flag settings to the given flag set
|
||||
Apply(*flag.FlagSet) error
|
||||
|
||||
// All possible names for this flag
|
||||
Names() []string
|
||||
|
||||
// Whether the flag has been set or not
|
||||
IsSet() bool
|
||||
}
|
||||
|
||||
// RequiredFlag is an interface that allows us to mark flags as required
|
||||
// it allows flags required flags to be backwards compatible with the Flag interface
|
||||
type RequiredFlag interface {
|
||||
// whether the flag is a required flag or not
|
||||
IsRequired() bool
|
||||
}
|
||||
|
||||
// DocGenerationFlag is an interface that allows documentation generation for the flag
|
||||
type DocGenerationFlag interface {
|
||||
// TakesValue returns true if the flag takes a value, otherwise false
|
||||
TakesValue() bool
|
||||
|
||||
// GetUsage returns the usage string for the flag
|
||||
GetUsage() string
|
||||
|
||||
// GetValue returns the flags value as string representation and an empty
|
||||
// string if the flag takes no value at all.
|
||||
GetValue() string
|
||||
|
||||
// GetDefaultText returns the default text for this flag
|
||||
GetDefaultText() string
|
||||
|
||||
// GetEnvVars returns the env vars for this flag
|
||||
GetEnvVars() []string
|
||||
|
||||
// IsDefaultVisible returns whether the default value should be shown in
|
||||
// help text
|
||||
IsDefaultVisible() bool
|
||||
}
|
||||
|
||||
// DocGenerationMultiValueFlag extends DocGenerationFlag for slice/map based flags.
|
||||
type DocGenerationMultiValueFlag interface {
|
||||
DocGenerationFlag
|
||||
|
||||
// IsMultiValueFlag returns true for flags that can be given multiple times.
|
||||
IsMultiValueFlag() bool
|
||||
}
|
||||
|
||||
// Countable is an interface to enable detection of flag values which support
|
||||
// repetitive flags
|
||||
type Countable interface {
|
||||
Count() int
|
||||
}
|
||||
|
||||
// VisibleFlag is an interface that allows to check if a flag is visible
|
||||
type VisibleFlag interface {
|
||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||
IsVisible() bool
|
||||
}
|
||||
|
||||
// CategorizableFlag is an interface that allows us to potentially
|
||||
// use a flag in a categorized representation.
|
||||
type CategorizableFlag interface {
|
||||
// Returns the category of the flag
|
||||
GetCategory() string
|
||||
|
||||
// Sets the category of the flag
|
||||
SetCategory(string)
|
||||
}
|
||||
|
||||
// LocalFlag is an interface to enable detection of flags which are local
|
||||
// to current command
|
||||
type LocalFlag interface {
|
||||
IsLocal() bool
|
||||
}
|
||||
|
||||
func newFlagSet(name string, flags []Flag) (*flag.FlagSet, error) {
|
||||
set := flag.NewFlagSet(name, flag.ContinueOnError)
|
||||
|
||||
for _, f := range flags {
|
||||
if err := f.Apply(set); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
set.SetOutput(io.Discard)
|
||||
|
||||
return set, nil
|
||||
}
|
||||
|
||||
func visibleFlags(fl []Flag) []Flag {
|
||||
var visible []Flag
|
||||
for _, f := range fl {
|
||||
if vf, ok := f.(VisibleFlag); ok && vf.IsVisible() {
|
||||
visible = append(visible, f)
|
||||
}
|
||||
}
|
||||
return visible
|
||||
}
|
||||
|
||||
func prefixFor(name string) (prefix string) {
|
||||
if len(name) == 1 {
|
||||
prefix = "-"
|
||||
} else {
|
||||
prefix = "--"
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Returns the placeholder, if any, and the unquoted usage string.
|
||||
func unquoteUsage(usage string) (string, string) {
|
||||
for i := 0; i < len(usage); i++ {
|
||||
if usage[i] == '`' {
|
||||
for j := i + 1; j < len(usage); j++ {
|
||||
if usage[j] == '`' {
|
||||
name := usage[i+1 : j]
|
||||
usage = usage[:i] + name + usage[j+1:]
|
||||
return name, usage
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return "", usage
|
||||
}
|
||||
|
||||
func prefixedNames(names []string, placeholder string) string {
|
||||
var prefixed string
|
||||
for i, name := range names {
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
prefixed += prefixFor(name) + name
|
||||
if placeholder != "" {
|
||||
prefixed += " " + placeholder
|
||||
}
|
||||
if i < len(names)-1 {
|
||||
prefixed += ", "
|
||||
}
|
||||
}
|
||||
return prefixed
|
||||
}
|
||||
|
||||
func envFormat(envVars []string, prefix, sep, suffix string) string {
|
||||
if len(envVars) > 0 {
|
||||
return fmt.Sprintf(" [%s%s%s]", prefix, strings.Join(envVars, sep), suffix)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func defaultEnvFormat(envVars []string) string {
|
||||
return envFormat(envVars, "$", ", $", "")
|
||||
}
|
||||
|
||||
func withEnvHint(envVars []string, str string) string {
|
||||
envText := ""
|
||||
if runtime.GOOS != "windows" || os.Getenv("PSHOME") != "" {
|
||||
envText = defaultEnvFormat(envVars)
|
||||
} else {
|
||||
envText = envFormat(envVars, "%", "%, %", "%")
|
||||
}
|
||||
return str + envText
|
||||
}
|
||||
|
||||
func FlagNames(name string, aliases []string) []string {
|
||||
var ret []string
|
||||
|
||||
for _, part := range append([]string{name}, aliases...) {
|
||||
// v1 -> v2 migration warning zone:
|
||||
// Strip off anything after the first found comma or space, which
|
||||
// *hopefully* makes it a tiny bit more obvious that unexpected behavior is
|
||||
// caused by using the v1 form of stringly typed "Name".
|
||||
ret = append(ret, commaWhitespace.ReplaceAllString(part, ""))
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func withFileHint(filePath, str string) string {
|
||||
fileText := ""
|
||||
if filePath != "" {
|
||||
fileText = fmt.Sprintf(" [%s]", filePath)
|
||||
}
|
||||
return str + fileText
|
||||
}
|
||||
|
||||
func formatDefault(format string) string {
|
||||
return " (default: " + format + ")"
|
||||
}
|
||||
|
||||
func stringifyFlag(f Flag) string {
|
||||
// enforce DocGeneration interface on flags to avoid reflection
|
||||
df, ok := f.(DocGenerationFlag)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
placeholder, usage := unquoteUsage(df.GetUsage())
|
||||
needsPlaceholder := df.TakesValue()
|
||||
|
||||
if needsPlaceholder && placeholder == "" {
|
||||
placeholder = defaultPlaceholder
|
||||
}
|
||||
|
||||
defaultValueString := ""
|
||||
|
||||
// don't print default text for required flags
|
||||
if rf, ok := f.(RequiredFlag); !ok || !rf.IsRequired() {
|
||||
isVisible := df.IsDefaultVisible()
|
||||
if s := df.GetDefaultText(); isVisible && s != "" {
|
||||
defaultValueString = fmt.Sprintf(formatDefault("%s"), s)
|
||||
}
|
||||
}
|
||||
|
||||
usageWithDefault := strings.TrimSpace(usage + defaultValueString)
|
||||
|
||||
pn := prefixedNames(f.Names(), placeholder)
|
||||
sliceFlag, ok := f.(DocGenerationMultiValueFlag)
|
||||
if ok && sliceFlag.IsMultiValueFlag() {
|
||||
pn = pn + " [ " + pn + " ]"
|
||||
}
|
||||
|
||||
return withEnvHint(df.GetEnvVars(), fmt.Sprintf("%s\t%s", pn, usageWithDefault))
|
||||
}
|
||||
|
||||
func hasFlag(flags []Flag, fl Flag) bool {
|
||||
for _, existing := range flags {
|
||||
if fl == existing {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func flagSplitMultiValues(val string) []string {
|
||||
if disableSliceFlagSeparator {
|
||||
return []string{val}
|
||||
}
|
||||
|
||||
return strings.Split(val, defaultSliceFlagSeparator)
|
||||
}
|
81
vendor/github.com/urfave/cli/v3/flag_bool.go
generated
vendored
Normal file
81
vendor/github.com/urfave/cli/v3/flag_bool.go
generated
vendored
Normal file
@ -0,0 +1,81 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type BoolFlag = FlagBase[bool, BoolConfig, boolValue]
|
||||
|
||||
// BoolConfig defines the configuration for bool flags
|
||||
type BoolConfig struct {
|
||||
Count *int
|
||||
}
|
||||
|
||||
// boolValue needs to implement the boolFlag internal interface in flag
|
||||
// to be able to capture bool fields and values
|
||||
//
|
||||
// type boolFlag interface {
|
||||
// Value
|
||||
// IsBoolFlag() bool
|
||||
// }
|
||||
type boolValue struct {
|
||||
destination *bool
|
||||
count *int
|
||||
}
|
||||
|
||||
func (cmd *Command) Bool(name string) bool {
|
||||
if v, ok := cmd.Value(name).(bool); ok {
|
||||
tracef("bool available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
|
||||
return v
|
||||
}
|
||||
|
||||
tracef("bool NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
return false
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the ValueCreator interface
|
||||
|
||||
// Create creates the bool value
|
||||
func (b boolValue) Create(val bool, p *bool, c BoolConfig) Value {
|
||||
*p = val
|
||||
if c.Count == nil {
|
||||
c.Count = new(int)
|
||||
}
|
||||
return &boolValue{
|
||||
destination: p,
|
||||
count: c.Count,
|
||||
}
|
||||
}
|
||||
|
||||
// ToString formats the bool value
|
||||
func (b boolValue) ToString(value bool) string {
|
||||
return strconv.FormatBool(value)
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the flag.Value interface
|
||||
|
||||
func (b *boolValue) Set(s string) error {
|
||||
v, err := strconv.ParseBool(s)
|
||||
if err != nil {
|
||||
err = errors.New("parse error")
|
||||
return err
|
||||
}
|
||||
*b.destination = v
|
||||
if b.count != nil {
|
||||
*b.count = *b.count + 1
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (b *boolValue) Get() interface{} { return *b.destination }
|
||||
|
||||
func (b *boolValue) String() string {
|
||||
return strconv.FormatBool(*b.destination)
|
||||
}
|
||||
|
||||
func (b *boolValue) IsBoolFlag() bool { return true }
|
||||
|
||||
func (b *boolValue) Count() int {
|
||||
return *b.count
|
||||
}
|
187
vendor/github.com/urfave/cli/v3/flag_bool_with_inverse.go
generated
vendored
Normal file
187
vendor/github.com/urfave/cli/v3/flag_bool_with_inverse.go
generated
vendored
Normal file
@ -0,0 +1,187 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var DefaultInverseBoolPrefix = "no-"
|
||||
|
||||
type BoolWithInverseFlag struct {
|
||||
// The BoolFlag which the positive and negative flags are generated from
|
||||
*BoolFlag
|
||||
|
||||
// The prefix used to indicate a negative value
|
||||
// Default: `env` becomes `no-env`
|
||||
InversePrefix string
|
||||
|
||||
positiveFlag *BoolFlag
|
||||
negativeFlag *BoolFlag
|
||||
|
||||
// pointers obtained from the embedded bool flag
|
||||
posDest *bool
|
||||
posCount *int
|
||||
|
||||
negDest *bool
|
||||
}
|
||||
|
||||
func (parent *BoolWithInverseFlag) Flags() []Flag {
|
||||
return []Flag{parent.positiveFlag, parent.negativeFlag}
|
||||
}
|
||||
|
||||
func (parent *BoolWithInverseFlag) IsSet() bool {
|
||||
return (*parent.posCount > 0) || (parent.positiveFlag.IsSet() || parent.negativeFlag.IsSet())
|
||||
}
|
||||
|
||||
func (parent *BoolWithInverseFlag) Value() bool {
|
||||
return *parent.posDest
|
||||
}
|
||||
|
||||
func (parent *BoolWithInverseFlag) RunAction(ctx context.Context, cmd *Command) error {
|
||||
if *parent.negDest && *parent.posDest {
|
||||
return fmt.Errorf("cannot set both flags `--%s` and `--%s`", parent.positiveFlag.Name, parent.negativeFlag.Name)
|
||||
}
|
||||
|
||||
if *parent.negDest {
|
||||
_ = cmd.Set(parent.positiveFlag.Name, "false")
|
||||
}
|
||||
|
||||
if parent.BoolFlag.Action != nil {
|
||||
return parent.BoolFlag.Action(ctx, cmd, parent.Value())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Initialize creates a new BoolFlag that has an inverse flag
|
||||
//
|
||||
// consider a bool flag `--env`, there is no way to set it to false
|
||||
// this function allows you to set `--env` or `--no-env` and in the command action
|
||||
// it can be determined that BoolWithInverseFlag.IsSet().
|
||||
func (parent *BoolWithInverseFlag) initialize() {
|
||||
child := parent.BoolFlag
|
||||
|
||||
parent.negDest = new(bool)
|
||||
if child.Destination != nil {
|
||||
parent.posDest = child.Destination
|
||||
} else {
|
||||
parent.posDest = new(bool)
|
||||
}
|
||||
|
||||
if child.Config.Count != nil {
|
||||
parent.posCount = child.Config.Count
|
||||
} else {
|
||||
parent.posCount = new(int)
|
||||
}
|
||||
|
||||
parent.positiveFlag = child
|
||||
parent.positiveFlag.Destination = parent.posDest
|
||||
parent.positiveFlag.Config.Count = parent.posCount
|
||||
|
||||
parent.negativeFlag = &BoolFlag{
|
||||
Category: child.Category,
|
||||
DefaultText: child.DefaultText,
|
||||
Sources: NewValueSourceChain(child.Sources.Chain...),
|
||||
Usage: child.Usage,
|
||||
Required: child.Required,
|
||||
Hidden: child.Hidden,
|
||||
Local: child.Local,
|
||||
Value: child.Value,
|
||||
Destination: parent.negDest,
|
||||
TakesFile: child.TakesFile,
|
||||
OnlyOnce: child.OnlyOnce,
|
||||
hasBeenSet: child.hasBeenSet,
|
||||
applied: child.applied,
|
||||
creator: boolValue{},
|
||||
value: child.value,
|
||||
}
|
||||
|
||||
// Set inverse names ex: --env => --no-env
|
||||
parent.negativeFlag.Name = parent.inverseName()
|
||||
parent.negativeFlag.Aliases = parent.inverseAliases()
|
||||
|
||||
if len(child.Sources.EnvKeys()) > 0 {
|
||||
sources := []ValueSource{}
|
||||
|
||||
for _, envVar := range child.GetEnvVars() {
|
||||
sources = append(sources, EnvVar(strings.ToUpper(parent.InversePrefix)+envVar))
|
||||
}
|
||||
parent.negativeFlag.Sources = NewValueSourceChain(sources...)
|
||||
}
|
||||
}
|
||||
|
||||
func (parent *BoolWithInverseFlag) inverseName() string {
|
||||
return parent.inversePrefix() + parent.BoolFlag.Name
|
||||
}
|
||||
|
||||
func (parent *BoolWithInverseFlag) inversePrefix() string {
|
||||
if parent.InversePrefix == "" {
|
||||
parent.InversePrefix = DefaultInverseBoolPrefix
|
||||
}
|
||||
|
||||
return parent.InversePrefix
|
||||
}
|
||||
|
||||
func (parent *BoolWithInverseFlag) inverseAliases() (aliases []string) {
|
||||
if len(parent.BoolFlag.Aliases) > 0 {
|
||||
aliases = make([]string, len(parent.BoolFlag.Aliases))
|
||||
for idx, alias := range parent.BoolFlag.Aliases {
|
||||
aliases[idx] = parent.InversePrefix + alias
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (parent *BoolWithInverseFlag) Apply(set *flag.FlagSet) error {
|
||||
if parent.positiveFlag == nil {
|
||||
parent.initialize()
|
||||
}
|
||||
|
||||
if err := parent.positiveFlag.Apply(set); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := parent.negativeFlag.Apply(set); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (parent *BoolWithInverseFlag) Names() []string {
|
||||
// Get Names when flag has not been initialized
|
||||
if parent.positiveFlag == nil {
|
||||
return append(parent.BoolFlag.Names(), FlagNames(parent.inverseName(), parent.inverseAliases())...)
|
||||
}
|
||||
|
||||
if *parent.negDest {
|
||||
return parent.negativeFlag.Names()
|
||||
}
|
||||
|
||||
if *parent.posDest {
|
||||
return parent.positiveFlag.Names()
|
||||
}
|
||||
|
||||
return append(parent.negativeFlag.Names(), parent.positiveFlag.Names()...)
|
||||
}
|
||||
|
||||
// String implements the standard Stringer interface.
|
||||
//
|
||||
// Example for BoolFlag{Name: "env"}
|
||||
// --[no-]env (default: false)
|
||||
func (parent *BoolWithInverseFlag) String() string {
|
||||
out := FlagStringer(parent)
|
||||
i := strings.Index(out, "\t")
|
||||
|
||||
prefix := "--"
|
||||
|
||||
// single character flags are prefixed with `-` instead of `--`
|
||||
if len(parent.Name) == 1 {
|
||||
prefix = "-"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s[%s]%s%s", prefix, parent.inversePrefix(), parent.Name, out[i:])
|
||||
}
|
47
vendor/github.com/urfave/cli/v3/flag_duration.go
generated
vendored
Normal file
47
vendor/github.com/urfave/cli/v3/flag_duration.go
generated
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DurationFlag = FlagBase[time.Duration, NoConfig, durationValue]
|
||||
|
||||
// -- time.Duration Value
|
||||
type durationValue time.Duration
|
||||
|
||||
// Below functions are to satisfy the ValueCreator interface
|
||||
|
||||
func (d durationValue) Create(val time.Duration, p *time.Duration, c NoConfig) Value {
|
||||
*p = val
|
||||
return (*durationValue)(p)
|
||||
}
|
||||
|
||||
func (d durationValue) ToString(val time.Duration) string {
|
||||
return fmt.Sprintf("%v", val)
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the flag.Value interface
|
||||
|
||||
func (d *durationValue) Set(s string) error {
|
||||
v, err := time.ParseDuration(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*d = durationValue(v)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *durationValue) Get() any { return time.Duration(*d) }
|
||||
|
||||
func (d *durationValue) String() string { return (*time.Duration)(d).String() }
|
||||
|
||||
func (cmd *Command) Duration(name string) time.Duration {
|
||||
if v, ok := cmd.Value(name).(time.Duration); ok {
|
||||
tracef("duration available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
|
||||
return v
|
||||
}
|
||||
|
||||
tracef("bool NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
return 0
|
||||
}
|
48
vendor/github.com/urfave/cli/v3/flag_ext.go
generated
vendored
Normal file
48
vendor/github.com/urfave/cli/v3/flag_ext.go
generated
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
package cli
|
||||
|
||||
import "flag"
|
||||
|
||||
type extFlag struct {
|
||||
f *flag.Flag
|
||||
}
|
||||
|
||||
func (e *extFlag) Apply(fs *flag.FlagSet) error {
|
||||
fs.Var(e.f.Value, e.f.Name, e.f.Usage)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *extFlag) Names() []string {
|
||||
return []string{e.f.Name}
|
||||
}
|
||||
|
||||
func (e *extFlag) IsSet() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (e *extFlag) String() string {
|
||||
return FlagStringer(e)
|
||||
}
|
||||
|
||||
func (e *extFlag) IsVisible() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (e *extFlag) TakesValue() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (e *extFlag) GetUsage() string {
|
||||
return e.f.Usage
|
||||
}
|
||||
|
||||
func (e *extFlag) GetValue() string {
|
||||
return e.f.Value.String()
|
||||
}
|
||||
|
||||
func (e *extFlag) GetDefaultText() string {
|
||||
return e.f.DefValue
|
||||
}
|
||||
|
||||
func (e *extFlag) GetEnvVars() []string {
|
||||
return nil
|
||||
}
|
48
vendor/github.com/urfave/cli/v3/flag_float.go
generated
vendored
Normal file
48
vendor/github.com/urfave/cli/v3/flag_float.go
generated
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type FloatFlag = FlagBase[float64, NoConfig, floatValue]
|
||||
|
||||
// -- float64 Value
|
||||
type floatValue float64
|
||||
|
||||
// Below functions are to satisfy the ValueCreator interface
|
||||
|
||||
func (f floatValue) Create(val float64, p *float64, c NoConfig) Value {
|
||||
*p = val
|
||||
return (*floatValue)(p)
|
||||
}
|
||||
|
||||
func (f floatValue) ToString(b float64) string {
|
||||
return strconv.FormatFloat(b, 'g', -1, 64)
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the flag.Value interface
|
||||
|
||||
func (f *floatValue) Set(s string) error {
|
||||
v, err := strconv.ParseFloat(s, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*f = floatValue(v)
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *floatValue) Get() any { return float64(*f) }
|
||||
|
||||
func (f *floatValue) String() string { return strconv.FormatFloat(float64(*f), 'g', -1, 64) }
|
||||
|
||||
// Float looks up the value of a local FloatFlag, returns
|
||||
// 0 if not found
|
||||
func (cmd *Command) Float(name string) float64 {
|
||||
if v, ok := cmd.Value(name).(float64); ok {
|
||||
tracef("float available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
|
||||
return v
|
||||
}
|
||||
|
||||
tracef("float NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
return 0
|
||||
}
|
20
vendor/github.com/urfave/cli/v3/flag_float_slice.go
generated
vendored
Normal file
20
vendor/github.com/urfave/cli/v3/flag_float_slice.go
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
package cli
|
||||
|
||||
type (
|
||||
FloatSlice = SliceBase[float64, NoConfig, floatValue]
|
||||
FloatSliceFlag = FlagBase[[]float64, NoConfig, FloatSlice]
|
||||
)
|
||||
|
||||
var NewFloatSlice = NewSliceBase[float64, NoConfig, floatValue]
|
||||
|
||||
// FloatSlice looks up the value of a local FloatSliceFlag, returns
|
||||
// nil if not found
|
||||
func (cmd *Command) FloatSlice(name string) []float64 {
|
||||
if v, ok := cmd.Value(name).([]float64); ok {
|
||||
tracef("float slice available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
|
||||
return v
|
||||
}
|
||||
|
||||
tracef("float slice NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
return nil
|
||||
}
|
67
vendor/github.com/urfave/cli/v3/flag_generic.go
generated
vendored
Normal file
67
vendor/github.com/urfave/cli/v3/flag_generic.go
generated
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
package cli
|
||||
|
||||
type GenericFlag = FlagBase[Value, NoConfig, genericValue]
|
||||
|
||||
// -- Value Value
|
||||
type genericValue struct {
|
||||
val Value
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the ValueCreator interface
|
||||
|
||||
func (f genericValue) Create(val Value, p *Value, c NoConfig) Value {
|
||||
*p = val
|
||||
return &genericValue{
|
||||
val: *p,
|
||||
}
|
||||
}
|
||||
|
||||
func (f genericValue) ToString(b Value) string {
|
||||
if b != nil {
|
||||
return b.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the flag.Value interface
|
||||
|
||||
func (f *genericValue) Set(s string) error {
|
||||
if f.val != nil {
|
||||
return f.val.Set(s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *genericValue) Get() any {
|
||||
if f.val != nil {
|
||||
return f.val.Get()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *genericValue) String() string {
|
||||
if f.val != nil {
|
||||
return f.val.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (f *genericValue) IsBoolFlag() bool {
|
||||
if f.val == nil {
|
||||
return false
|
||||
}
|
||||
bf, ok := f.val.(boolFlag)
|
||||
return ok && bf.IsBoolFlag()
|
||||
}
|
||||
|
||||
// Generic looks up the value of a local GenericFlag, returns
|
||||
// nil if not found
|
||||
func (cmd *Command) Generic(name string) Value {
|
||||
if v, ok := cmd.Value(name).(Value); ok {
|
||||
tracef("generic available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
|
||||
return v
|
||||
}
|
||||
|
||||
tracef("generic NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
return nil
|
||||
}
|
264
vendor/github.com/urfave/cli/v3/flag_impl.go
generated
vendored
Normal file
264
vendor/github.com/urfave/cli/v3/flag_impl.go
generated
vendored
Normal file
@ -0,0 +1,264 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Value represents a value as used by cli.
|
||||
// For now it implements the golang flag.Value interface
|
||||
type Value interface {
|
||||
flag.Value
|
||||
flag.Getter
|
||||
}
|
||||
|
||||
type boolFlag interface {
|
||||
IsBoolFlag() bool
|
||||
}
|
||||
|
||||
type fnValue struct {
|
||||
fn func(string) error
|
||||
isBool bool
|
||||
v Value
|
||||
}
|
||||
|
||||
func (f *fnValue) Get() any { return f.v.Get() }
|
||||
func (f *fnValue) Set(s string) error { return f.fn(s) }
|
||||
func (f *fnValue) String() string {
|
||||
if f.v == nil {
|
||||
return ""
|
||||
}
|
||||
return f.v.String()
|
||||
}
|
||||
|
||||
func (f *fnValue) IsBoolFlag() bool { return f.isBool }
|
||||
func (f *fnValue) Count() int {
|
||||
if s, ok := f.v.(Countable); ok {
|
||||
return s.Count()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ValueCreator is responsible for creating a flag.Value emulation
|
||||
// as well as custom formatting
|
||||
//
|
||||
// T specifies the type
|
||||
// C specifies the config for the type
|
||||
type ValueCreator[T any, C any] interface {
|
||||
Create(T, *T, C) Value
|
||||
ToString(T) string
|
||||
}
|
||||
|
||||
// NoConfig is for flags which dont need a custom configuration
|
||||
type NoConfig struct{}
|
||||
|
||||
// FlagBase [T,C,VC] is a generic flag base which can be used
|
||||
// as a boilerplate to implement the most common interfaces
|
||||
// used by urfave/cli.
|
||||
//
|
||||
// T specifies the type
|
||||
// C specifies the configuration required(if any for that flag type)
|
||||
// VC specifies the value creator which creates the flag.Value emulation
|
||||
type FlagBase[T any, C any, VC ValueCreator[T, C]] struct {
|
||||
Name string `json:"name"` // name of the flag
|
||||
Category string `json:"category"` // category of the flag, if any
|
||||
DefaultText string `json:"defaultText"` // default text of the flag for usage purposes
|
||||
HideDefault bool `json:"hideDefault"` // whether to hide the default value in output
|
||||
Usage string `json:"usage"` // usage string for help output
|
||||
Sources ValueSourceChain `json:"-"` // sources to load flag value from
|
||||
Required bool `json:"required"` // whether the flag is required or not
|
||||
Hidden bool `json:"hidden"` // whether to hide the flag in help output
|
||||
Local bool `json:"local"` // whether the flag needs to be applied to subcommands as well
|
||||
Value T `json:"defaultValue"` // default value for this flag if not set by from any source
|
||||
Destination *T `json:"-"` // destination pointer for value when set
|
||||
Aliases []string `json:"aliases"` // Aliases that are allowed for this flag
|
||||
TakesFile bool `json:"takesFileArg"` // whether this flag takes a file argument, mainly for shell completion purposes
|
||||
Action func(context.Context, *Command, T) error `json:"-"` // Action callback to be called when flag is set
|
||||
Config C `json:"config"` // Additional/Custom configuration associated with this flag type
|
||||
OnlyOnce bool `json:"onlyOnce"` // whether this flag can be duplicated on the command line
|
||||
Validator func(T) error `json:"-"` // custom function to validate this flag value
|
||||
ValidateDefaults bool `json:"validateDefaults"` // whether to validate defaults or not
|
||||
|
||||
// unexported fields for internal use
|
||||
count int // number of times the flag has been set
|
||||
hasBeenSet bool // whether the flag has been set from env or file
|
||||
applied bool // whether the flag has been applied to a flag set already
|
||||
creator VC // value creator for this flag type
|
||||
value Value // value representing this flag's value
|
||||
}
|
||||
|
||||
// GetValue returns the flags value as string representation and an empty
|
||||
// string if the flag takes no value at all.
|
||||
func (f *FlagBase[T, C, V]) GetValue() string {
|
||||
if !f.TakesValue() {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%v", f.Value)
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
func (f *FlagBase[T, C, V]) Apply(set *flag.FlagSet) error {
|
||||
tracef("apply (flag=%[1]q)", f.Name)
|
||||
|
||||
// TODO move this phase into a separate flag initialization function
|
||||
// if flag has been applied previously then it would have already been set
|
||||
// from env or file. So no need to apply the env set again. However
|
||||
// lots of units tests prior to persistent flags assumed that the
|
||||
// flag can be applied to different flag sets multiple times while still
|
||||
// keeping the env set.
|
||||
if !f.applied || f.Local {
|
||||
newVal := f.Value
|
||||
|
||||
if val, source, found := f.Sources.LookupWithSource(); found {
|
||||
tmpVal := f.creator.Create(f.Value, new(T), f.Config)
|
||||
if val != "" || reflect.TypeOf(f.Value).Kind() == reflect.String {
|
||||
if err := tmpVal.Set(val); err != nil {
|
||||
return fmt.Errorf(
|
||||
"could not parse %[1]q as %[2]T value from %[3]s for flag %[4]s: %[5]s",
|
||||
val, f.Value, source, f.Name, err,
|
||||
)
|
||||
}
|
||||
} else if val == "" && reflect.TypeOf(f.Value).Kind() == reflect.Bool {
|
||||
_ = tmpVal.Set("false")
|
||||
}
|
||||
|
||||
newVal = tmpVal.Get().(T)
|
||||
f.hasBeenSet = true
|
||||
}
|
||||
|
||||
if f.Destination == nil {
|
||||
f.value = f.creator.Create(newVal, new(T), f.Config)
|
||||
} else {
|
||||
f.value = f.creator.Create(newVal, f.Destination, f.Config)
|
||||
}
|
||||
|
||||
// Validate the given default or values set from external sources as well
|
||||
if f.Validator != nil && f.ValidateDefaults {
|
||||
if err := f.Validator(f.value.Get().(T)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isBool := false
|
||||
if b, ok := f.value.(boolFlag); ok && b.IsBoolFlag() {
|
||||
isBool = true
|
||||
}
|
||||
|
||||
for _, name := range f.Names() {
|
||||
set.Var(&fnValue{
|
||||
fn: func(val string) error {
|
||||
if f.count == 1 && f.OnlyOnce {
|
||||
return fmt.Errorf("cant duplicate this flag")
|
||||
}
|
||||
f.count++
|
||||
if err := f.value.Set(val); err != nil {
|
||||
return err
|
||||
}
|
||||
f.hasBeenSet = true
|
||||
if f.Validator != nil {
|
||||
if err := f.Validator(f.value.Get().(T)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
isBool: isBool,
|
||||
v: f.value,
|
||||
}, name, f.Usage)
|
||||
}
|
||||
|
||||
f.applied = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsDefaultVisible returns true if the flag is not hidden, otherwise false
|
||||
func (f *FlagBase[T, C, V]) IsDefaultVisible() bool {
|
||||
return !f.HideDefault
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value (for usage defaults)
|
||||
func (f *FlagBase[T, C, V]) String() string {
|
||||
return FlagStringer(f)
|
||||
}
|
||||
|
||||
// IsSet returns whether or not the flag has been set through env or file
|
||||
func (f *FlagBase[T, C, V]) IsSet() bool {
|
||||
return f.hasBeenSet
|
||||
}
|
||||
|
||||
// Names returns the names of the flag
|
||||
func (f *FlagBase[T, C, V]) Names() []string {
|
||||
return FlagNames(f.Name, f.Aliases)
|
||||
}
|
||||
|
||||
// IsRequired returns whether or not the flag is required
|
||||
func (f *FlagBase[T, C, V]) IsRequired() bool {
|
||||
return f.Required
|
||||
}
|
||||
|
||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||
func (f *FlagBase[T, C, V]) IsVisible() bool {
|
||||
return !f.Hidden
|
||||
}
|
||||
|
||||
// GetCategory returns the category of the flag
|
||||
func (f *FlagBase[T, C, V]) GetCategory() string {
|
||||
return f.Category
|
||||
}
|
||||
|
||||
func (f *FlagBase[T, C, V]) SetCategory(c string) {
|
||||
f.Category = c
|
||||
}
|
||||
|
||||
// GetUsage returns the usage string for the flag
|
||||
func (f *FlagBase[T, C, V]) GetUsage() string {
|
||||
return f.Usage
|
||||
}
|
||||
|
||||
// GetEnvVars returns the env vars for this flag
|
||||
func (f *FlagBase[T, C, V]) GetEnvVars() []string {
|
||||
return f.Sources.EnvKeys()
|
||||
}
|
||||
|
||||
// TakesValue returns true if the flag takes a value, otherwise false
|
||||
func (f *FlagBase[T, C, V]) TakesValue() bool {
|
||||
var t T
|
||||
return reflect.TypeOf(t) == nil || reflect.TypeOf(t).Kind() != reflect.Bool
|
||||
}
|
||||
|
||||
// GetDefaultText returns the default text for this flag
|
||||
func (f *FlagBase[T, C, V]) GetDefaultText() string {
|
||||
if f.DefaultText != "" {
|
||||
return f.DefaultText
|
||||
}
|
||||
var v V
|
||||
return v.ToString(f.Value)
|
||||
}
|
||||
|
||||
// RunAction executes flag action if set
|
||||
func (f *FlagBase[T, C, V]) RunAction(ctx context.Context, cmd *Command) error {
|
||||
if f.Action != nil {
|
||||
return f.Action(ctx, cmd, cmd.Value(f.Name).(T))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsMultiValueFlag returns true if the value type T can take multiple
|
||||
// values from cmd line. This is true for slice and map type flags
|
||||
func (f *FlagBase[T, C, VC]) IsMultiValueFlag() bool {
|
||||
// TBD how to specify
|
||||
if reflect.TypeOf(f.Value) == nil {
|
||||
return false
|
||||
}
|
||||
kind := reflect.TypeOf(f.Value).Kind()
|
||||
return kind == reflect.Slice || kind == reflect.Map
|
||||
}
|
||||
|
||||
// IsLocal returns false if flag needs to be persistent across subcommands
|
||||
func (f *FlagBase[T, C, VC]) IsLocal() bool {
|
||||
return f.Local
|
||||
}
|
59
vendor/github.com/urfave/cli/v3/flag_int.go
generated
vendored
Normal file
59
vendor/github.com/urfave/cli/v3/flag_int.go
generated
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type IntFlag = FlagBase[int64, IntegerConfig, intValue]
|
||||
|
||||
// IntegerConfig is the configuration for all integer type flags
|
||||
type IntegerConfig struct {
|
||||
Base int
|
||||
}
|
||||
|
||||
// -- int64 Value
|
||||
type intValue struct {
|
||||
val *int64
|
||||
base int
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the ValueCreator interface
|
||||
|
||||
func (i intValue) Create(val int64, p *int64, c IntegerConfig) Value {
|
||||
*p = val
|
||||
return &intValue{
|
||||
val: p,
|
||||
base: c.Base,
|
||||
}
|
||||
}
|
||||
|
||||
func (i intValue) ToString(b int64) string {
|
||||
return strconv.FormatInt(b, 10)
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the flag.Value interface
|
||||
|
||||
func (i *intValue) Set(s string) error {
|
||||
v, err := strconv.ParseInt(s, i.base, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*i.val = v
|
||||
return err
|
||||
}
|
||||
|
||||
func (i *intValue) Get() any { return int64(*i.val) }
|
||||
|
||||
func (i *intValue) String() string { return strconv.FormatInt(int64(*i.val), 10) }
|
||||
|
||||
// Int looks up the value of a local Int64Flag, returns
|
||||
// 0 if not found
|
||||
func (cmd *Command) Int(name string) int64 {
|
||||
if v, ok := cmd.Value(name).(int64); ok {
|
||||
tracef("int available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
|
||||
return v
|
||||
}
|
||||
|
||||
tracef("int NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
return 0
|
||||
}
|
20
vendor/github.com/urfave/cli/v3/flag_int_slice.go
generated
vendored
Normal file
20
vendor/github.com/urfave/cli/v3/flag_int_slice.go
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
package cli
|
||||
|
||||
type (
|
||||
IntSlice = SliceBase[int64, IntegerConfig, intValue]
|
||||
IntSliceFlag = FlagBase[[]int64, IntegerConfig, IntSlice]
|
||||
)
|
||||
|
||||
var NewIntSlice = NewSliceBase[int64, IntegerConfig, intValue]
|
||||
|
||||
// IntSlice looks up the value of a local IntSliceFlag, returns
|
||||
// nil if not found
|
||||
func (cmd *Command) IntSlice(name string) []int64 {
|
||||
if v, ok := cmd.Value(name).([]int64); ok {
|
||||
tracef("int slice available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
|
||||
return v
|
||||
}
|
||||
|
||||
tracef("int slice NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
return nil
|
||||
}
|
112
vendor/github.com/urfave/cli/v3/flag_map_impl.go
generated
vendored
Normal file
112
vendor/github.com/urfave/cli/v3/flag_map_impl.go
generated
vendored
Normal file
@ -0,0 +1,112 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// MapBase wraps map[string]T to satisfy flag.Value
|
||||
type MapBase[T any, C any, VC ValueCreator[T, C]] struct {
|
||||
dict *map[string]T
|
||||
hasBeenSet bool
|
||||
value Value
|
||||
}
|
||||
|
||||
func (i MapBase[T, C, VC]) Create(val map[string]T, p *map[string]T, c C) Value {
|
||||
*p = map[string]T{}
|
||||
for k, v := range val {
|
||||
(*p)[k] = v
|
||||
}
|
||||
var t T
|
||||
np := new(T)
|
||||
var vc VC
|
||||
return &MapBase[T, C, VC]{
|
||||
dict: p,
|
||||
value: vc.Create(t, np, c),
|
||||
}
|
||||
}
|
||||
|
||||
// NewMapBase makes a *MapBase with default values
|
||||
func NewMapBase[T any, C any, VC ValueCreator[T, C]](defaults map[string]T) *MapBase[T, C, VC] {
|
||||
return &MapBase[T, C, VC]{
|
||||
dict: &defaults,
|
||||
}
|
||||
}
|
||||
|
||||
// Set parses the value and appends it to the list of values
|
||||
func (i *MapBase[T, C, VC]) Set(value string) error {
|
||||
if !i.hasBeenSet {
|
||||
*i.dict = map[string]T{}
|
||||
i.hasBeenSet = true
|
||||
}
|
||||
|
||||
if strings.HasPrefix(value, slPfx) {
|
||||
// Deserializing assumes overwrite
|
||||
_ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &i.dict)
|
||||
i.hasBeenSet = true
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, item := range flagSplitMultiValues(value) {
|
||||
key, value, ok := strings.Cut(item, defaultMapFlagKeyValueSeparator)
|
||||
if !ok {
|
||||
return fmt.Errorf("item %q is missing separator %q", item, defaultMapFlagKeyValueSeparator)
|
||||
}
|
||||
if err := i.value.Set(value); err != nil {
|
||||
return err
|
||||
}
|
||||
(*i.dict)[key] = i.value.Get().(T)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value (for usage defaults)
|
||||
func (i *MapBase[T, C, VC]) String() string {
|
||||
v := i.Value()
|
||||
var t T
|
||||
if reflect.TypeOf(t).Kind() == reflect.String {
|
||||
return fmt.Sprintf("%v", v)
|
||||
}
|
||||
return fmt.Sprintf("%T{%s}", v, i.ToString(v))
|
||||
}
|
||||
|
||||
// Serialize allows MapBase to fulfill Serializer
|
||||
func (i *MapBase[T, C, VC]) Serialize() string {
|
||||
jsonBytes, _ := json.Marshal(i.dict)
|
||||
return fmt.Sprintf("%s%s", slPfx, string(jsonBytes))
|
||||
}
|
||||
|
||||
// Value returns the mapping of values set by this flag
|
||||
func (i *MapBase[T, C, VC]) Value() map[string]T {
|
||||
if i.dict == nil {
|
||||
return map[string]T{}
|
||||
}
|
||||
return *i.dict
|
||||
}
|
||||
|
||||
// Get returns the mapping of values set by this flag
|
||||
func (i *MapBase[T, C, VC]) Get() interface{} {
|
||||
return *i.dict
|
||||
}
|
||||
|
||||
func (i MapBase[T, C, VC]) ToString(t map[string]T) string {
|
||||
var defaultVals []string
|
||||
var vc VC
|
||||
for _, k := range sortedKeys(t) {
|
||||
defaultVals = append(defaultVals, k+defaultMapFlagKeyValueSeparator+vc.ToString(t[k]))
|
||||
}
|
||||
return strings.Join(defaultVals, ", ")
|
||||
}
|
||||
|
||||
func sortedKeys[T any](dict map[string]T) []string {
|
||||
keys := make([]string, 0, len(dict))
|
||||
for k := range dict {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
}
|
56
vendor/github.com/urfave/cli/v3/flag_mutex.go
generated
vendored
Normal file
56
vendor/github.com/urfave/cli/v3/flag_mutex.go
generated
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
package cli
|
||||
|
||||
// MutuallyExclusiveFlags defines a mutually exclusive flag group
|
||||
// Multiple option paths can be provided out of which
|
||||
// only one can be defined on cmdline
|
||||
// So for example
|
||||
// [ --foo | [ --bar something --darth somethingelse ] ]
|
||||
type MutuallyExclusiveFlags struct {
|
||||
// Flag list
|
||||
Flags [][]Flag
|
||||
|
||||
// whether this group is required
|
||||
Required bool
|
||||
|
||||
// Category to apply to all flags within group
|
||||
Category string
|
||||
}
|
||||
|
||||
func (grp MutuallyExclusiveFlags) check(cmd *Command) error {
|
||||
oneSet := false
|
||||
e := &mutuallyExclusiveGroup{}
|
||||
|
||||
for _, grpf := range grp.Flags {
|
||||
for _, f := range grpf {
|
||||
for _, name := range f.Names() {
|
||||
if cmd.IsSet(name) {
|
||||
if oneSet {
|
||||
e.flag2Name = name
|
||||
return e
|
||||
}
|
||||
e.flag1Name = name
|
||||
oneSet = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if oneSet {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !oneSet && grp.Required {
|
||||
return &mutuallyExclusiveGroupRequiredFlag{flags: &grp}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (grp MutuallyExclusiveFlags) propagateCategory() {
|
||||
for _, grpf := range grp.Flags {
|
||||
for _, f := range grpf {
|
||||
if cf, ok := f.(CategorizableFlag); ok {
|
||||
cf.SetCategory(grp.Category)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
96
vendor/github.com/urfave/cli/v3/flag_slice_base.go
generated
vendored
Normal file
96
vendor/github.com/urfave/cli/v3/flag_slice_base.go
generated
vendored
Normal file
@ -0,0 +1,96 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SliceBase wraps []T to satisfy flag.Value
|
||||
type SliceBase[T any, C any, VC ValueCreator[T, C]] struct {
|
||||
slice *[]T
|
||||
hasBeenSet bool
|
||||
value Value
|
||||
}
|
||||
|
||||
func (i SliceBase[T, C, VC]) Create(val []T, p *[]T, c C) Value {
|
||||
*p = []T{}
|
||||
*p = append(*p, val...)
|
||||
var t T
|
||||
np := new(T)
|
||||
var vc VC
|
||||
return &SliceBase[T, C, VC]{
|
||||
slice: p,
|
||||
value: vc.Create(t, np, c),
|
||||
}
|
||||
}
|
||||
|
||||
// NewSliceBase makes a *SliceBase with default values
|
||||
func NewSliceBase[T any, C any, VC ValueCreator[T, C]](defaults ...T) *SliceBase[T, C, VC] {
|
||||
return &SliceBase[T, C, VC]{
|
||||
slice: &defaults,
|
||||
}
|
||||
}
|
||||
|
||||
// Set parses the value and appends it to the list of values
|
||||
func (i *SliceBase[T, C, VC]) Set(value string) error {
|
||||
if !i.hasBeenSet {
|
||||
*i.slice = []T{}
|
||||
i.hasBeenSet = true
|
||||
}
|
||||
|
||||
if strings.HasPrefix(value, slPfx) {
|
||||
// Deserializing assumes overwrite
|
||||
_ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &i.slice)
|
||||
i.hasBeenSet = true
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, s := range flagSplitMultiValues(value) {
|
||||
if err := i.value.Set(strings.TrimSpace(s)); err != nil {
|
||||
return err
|
||||
}
|
||||
*i.slice = append(*i.slice, i.value.Get().(T))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value (for usage defaults)
|
||||
func (i *SliceBase[T, C, VC]) String() string {
|
||||
v := i.Value()
|
||||
var t T
|
||||
if reflect.TypeOf(t).Kind() == reflect.String {
|
||||
return fmt.Sprintf("%v", v)
|
||||
}
|
||||
return fmt.Sprintf("%T{%s}", v, i.ToString(v))
|
||||
}
|
||||
|
||||
// Serialize allows SliceBase to fulfill Serializer
|
||||
func (i *SliceBase[T, C, VC]) Serialize() string {
|
||||
jsonBytes, _ := json.Marshal(i.slice)
|
||||
return fmt.Sprintf("%s%s", slPfx, string(jsonBytes))
|
||||
}
|
||||
|
||||
// Value returns the slice of values set by this flag
|
||||
func (i *SliceBase[T, C, VC]) Value() []T {
|
||||
if i.slice == nil {
|
||||
return nil
|
||||
}
|
||||
return *i.slice
|
||||
}
|
||||
|
||||
// Get returns the slice of values set by this flag
|
||||
func (i *SliceBase[T, C, VC]) Get() interface{} {
|
||||
return *i.slice
|
||||
}
|
||||
|
||||
func (i SliceBase[T, C, VC]) ToString(t []T) string {
|
||||
var defaultVals []string
|
||||
var v VC
|
||||
for _, s := range t {
|
||||
defaultVals = append(defaultVals, v.ToString(s))
|
||||
}
|
||||
return strings.Join(defaultVals, ", ")
|
||||
}
|
66
vendor/github.com/urfave/cli/v3/flag_string.go
generated
vendored
Normal file
66
vendor/github.com/urfave/cli/v3/flag_string.go
generated
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type StringFlag = FlagBase[string, StringConfig, stringValue]
|
||||
|
||||
// StringConfig defines the configuration for string flags
|
||||
type StringConfig struct {
|
||||
// Whether to trim whitespace of parsed value
|
||||
TrimSpace bool
|
||||
}
|
||||
|
||||
// -- string Value
|
||||
type stringValue struct {
|
||||
destination *string
|
||||
trimSpace bool
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the ValueCreator interface
|
||||
|
||||
func (s stringValue) Create(val string, p *string, c StringConfig) Value {
|
||||
*p = val
|
||||
return &stringValue{
|
||||
destination: p,
|
||||
trimSpace: c.TrimSpace,
|
||||
}
|
||||
}
|
||||
|
||||
func (s stringValue) ToString(val string) string {
|
||||
if val == "" {
|
||||
return val
|
||||
}
|
||||
return fmt.Sprintf("%q", val)
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the flag.Value interface
|
||||
|
||||
func (s *stringValue) Set(val string) error {
|
||||
if s.trimSpace {
|
||||
val = strings.TrimSpace(val)
|
||||
}
|
||||
*s.destination = val
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stringValue) Get() any { return *s.destination }
|
||||
|
||||
func (s *stringValue) String() string {
|
||||
if s.destination != nil {
|
||||
return *s.destination
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (cmd *Command) String(name string) string {
|
||||
if v, ok := cmd.Value(name).(string); ok {
|
||||
tracef("string available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
|
||||
return v
|
||||
}
|
||||
|
||||
tracef("string NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
return ""
|
||||
}
|
20
vendor/github.com/urfave/cli/v3/flag_string_map.go
generated
vendored
Normal file
20
vendor/github.com/urfave/cli/v3/flag_string_map.go
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
package cli
|
||||
|
||||
type (
|
||||
StringMap = MapBase[string, StringConfig, stringValue]
|
||||
StringMapFlag = FlagBase[map[string]string, StringConfig, StringMap]
|
||||
)
|
||||
|
||||
var NewStringMap = NewMapBase[string, StringConfig, stringValue]
|
||||
|
||||
// StringMap looks up the value of a local StringMapFlag, returns
|
||||
// nil if not found
|
||||
func (cmd *Command) StringMap(name string) map[string]string {
|
||||
if v, ok := cmd.Value(name).(map[string]string); ok {
|
||||
tracef("string map available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
|
||||
return v
|
||||
}
|
||||
|
||||
tracef("string map NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
return nil
|
||||
}
|
20
vendor/github.com/urfave/cli/v3/flag_string_slice.go
generated
vendored
Normal file
20
vendor/github.com/urfave/cli/v3/flag_string_slice.go
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
package cli
|
||||
|
||||
type (
|
||||
StringSlice = SliceBase[string, StringConfig, stringValue]
|
||||
StringSliceFlag = FlagBase[[]string, StringConfig, StringSlice]
|
||||
)
|
||||
|
||||
var NewStringSlice = NewSliceBase[string, StringConfig, stringValue]
|
||||
|
||||
// StringSlice looks up the value of a local StringSliceFlag, returns
|
||||
// nil if not found
|
||||
func (cmd *Command) StringSlice(name string) []string {
|
||||
if v, ok := cmd.Value(name).([]string); ok {
|
||||
tracef("string slice available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
|
||||
return v
|
||||
}
|
||||
|
||||
tracef("string slice NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
return nil
|
||||
}
|
152
vendor/github.com/urfave/cli/v3/flag_timestamp.go
generated
vendored
Normal file
152
vendor/github.com/urfave/cli/v3/flag_timestamp.go
generated
vendored
Normal file
@ -0,0 +1,152 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TimestampFlag = FlagBase[time.Time, TimestampConfig, timestampValue]
|
||||
|
||||
// TimestampConfig defines the config for timestamp flags
|
||||
type TimestampConfig struct {
|
||||
Timezone *time.Location
|
||||
// Available layouts for flag value.
|
||||
//
|
||||
// Note that value for formats with missing year/date will be interpreted as current year/date respectively.
|
||||
//
|
||||
// Read more about time layouts: https://pkg.go.dev/time#pkg-constants
|
||||
Layouts []string
|
||||
}
|
||||
|
||||
// timestampValue wrap to satisfy golang's flag interface.
|
||||
type timestampValue struct {
|
||||
timestamp *time.Time
|
||||
hasBeenSet bool
|
||||
layouts []string
|
||||
location *time.Location
|
||||
}
|
||||
|
||||
var _ ValueCreator[time.Time, TimestampConfig] = timestampValue{}
|
||||
|
||||
// Below functions are to satisfy the ValueCreator interface
|
||||
|
||||
func (t timestampValue) Create(val time.Time, p *time.Time, c TimestampConfig) Value {
|
||||
*p = val
|
||||
return ×tampValue{
|
||||
timestamp: p,
|
||||
layouts: c.Layouts,
|
||||
location: c.Timezone,
|
||||
}
|
||||
}
|
||||
|
||||
func (t timestampValue) ToString(b time.Time) string {
|
||||
if b.IsZero() {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%v", b)
|
||||
}
|
||||
|
||||
// Timestamp constructor(for internal testing only)
|
||||
func newTimestamp(timestamp time.Time) *timestampValue {
|
||||
return ×tampValue{timestamp: ×tamp}
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the flag.Value interface
|
||||
|
||||
// Parses the string value to timestamp
|
||||
func (t *timestampValue) Set(value string) error {
|
||||
var timestamp time.Time
|
||||
var err error
|
||||
|
||||
if t.location == nil {
|
||||
t.location = time.UTC
|
||||
}
|
||||
|
||||
if len(t.layouts) == 0 {
|
||||
return errors.New("got nil/empty layouts slice")
|
||||
}
|
||||
|
||||
for _, layout := range t.layouts {
|
||||
var locErr error
|
||||
|
||||
timestamp, locErr = time.ParseInLocation(layout, value, t.location)
|
||||
if locErr != nil {
|
||||
if err == nil {
|
||||
err = locErr
|
||||
continue
|
||||
}
|
||||
|
||||
err = newMultiError(err, locErr)
|
||||
continue
|
||||
}
|
||||
|
||||
err = nil
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defaultTS, _ := time.ParseInLocation(time.TimeOnly, time.TimeOnly, timestamp.Location())
|
||||
|
||||
n := time.Now()
|
||||
|
||||
// If format is missing date (or year only), set it explicitly to current
|
||||
if timestamp.Truncate(time.Hour*24).UnixNano() == defaultTS.Truncate(time.Hour*24).UnixNano() {
|
||||
timestamp = time.Date(
|
||||
n.Year(),
|
||||
n.Month(),
|
||||
n.Day(),
|
||||
timestamp.Hour(),
|
||||
timestamp.Minute(),
|
||||
timestamp.Second(),
|
||||
timestamp.Nanosecond(),
|
||||
timestamp.Location(),
|
||||
)
|
||||
} else if timestamp.Year() == 0 {
|
||||
timestamp = time.Date(
|
||||
n.Year(),
|
||||
timestamp.Month(),
|
||||
timestamp.Day(),
|
||||
timestamp.Hour(),
|
||||
timestamp.Minute(),
|
||||
timestamp.Second(),
|
||||
timestamp.Nanosecond(),
|
||||
timestamp.Location(),
|
||||
)
|
||||
}
|
||||
|
||||
if t.timestamp != nil {
|
||||
*t.timestamp = timestamp
|
||||
}
|
||||
t.hasBeenSet = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value (for usage defaults)
|
||||
func (t *timestampValue) String() string {
|
||||
return fmt.Sprintf("%#v", t.timestamp)
|
||||
}
|
||||
|
||||
// Value returns the timestamp value stored in the flag
|
||||
func (t *timestampValue) Value() *time.Time {
|
||||
return t.timestamp
|
||||
}
|
||||
|
||||
// Get returns the flag structure
|
||||
func (t *timestampValue) Get() any {
|
||||
return *t.timestamp
|
||||
}
|
||||
|
||||
// Timestamp gets the timestamp from a flag name
|
||||
func (cmd *Command) Timestamp(name string) time.Time {
|
||||
if v, ok := cmd.Value(name).(time.Time); ok {
|
||||
tracef("time.Time available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
|
||||
return v
|
||||
}
|
||||
|
||||
tracef("time.Time NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
return time.Time{}
|
||||
}
|
54
vendor/github.com/urfave/cli/v3/flag_uint.go
generated
vendored
Normal file
54
vendor/github.com/urfave/cli/v3/flag_uint.go
generated
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type UintFlag = FlagBase[uint64, IntegerConfig, uintValue]
|
||||
|
||||
// -- uint64 Value
|
||||
type uintValue struct {
|
||||
val *uint64
|
||||
base int
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the ValueCreator interface
|
||||
|
||||
func (i uintValue) Create(val uint64, p *uint64, c IntegerConfig) Value {
|
||||
*p = val
|
||||
return &uintValue{
|
||||
val: p,
|
||||
base: c.Base,
|
||||
}
|
||||
}
|
||||
|
||||
func (i uintValue) ToString(b uint64) string {
|
||||
return strconv.FormatUint(b, 10)
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the flag.Value interface
|
||||
|
||||
func (i *uintValue) Set(s string) error {
|
||||
v, err := strconv.ParseUint(s, i.base, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*i.val = v
|
||||
return err
|
||||
}
|
||||
|
||||
func (i *uintValue) Get() any { return uint64(*i.val) }
|
||||
|
||||
func (i *uintValue) String() string { return strconv.FormatUint(uint64(*i.val), 10) }
|
||||
|
||||
// Uint looks up the value of a local Uint64Flag, returns
|
||||
// 0 if not found
|
||||
func (cmd *Command) Uint(name string) uint64 {
|
||||
if v, ok := cmd.Value(name).(uint64); ok {
|
||||
tracef("uint available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
|
||||
return v
|
||||
}
|
||||
|
||||
tracef("uint NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
return 0
|
||||
}
|
20
vendor/github.com/urfave/cli/v3/flag_uint_slice.go
generated
vendored
Normal file
20
vendor/github.com/urfave/cli/v3/flag_uint_slice.go
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
package cli
|
||||
|
||||
type (
|
||||
UintSlice = SliceBase[uint64, IntegerConfig, uintValue]
|
||||
UintSliceFlag = FlagBase[[]uint64, IntegerConfig, UintSlice]
|
||||
)
|
||||
|
||||
var NewUintSlice = NewSliceBase[uint64, IntegerConfig, uintValue]
|
||||
|
||||
// UintSlice looks up the value of a local UintSliceFlag, returns
|
||||
// nil if not found
|
||||
func (cmd *Command) UintSlice(name string) []uint64 {
|
||||
if v, ok := cmd.Value(name).([]uint64); ok {
|
||||
tracef("uint slice available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
|
||||
return v
|
||||
}
|
||||
|
||||
tracef("uint slice NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
return nil
|
||||
}
|
50
vendor/github.com/urfave/cli/v3/funcs.go
generated
vendored
Normal file
50
vendor/github.com/urfave/cli/v3/funcs.go
generated
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
package cli
|
||||
|
||||
import "context"
|
||||
|
||||
// ShellCompleteFunc is an action to execute when the shell completion flag is set
|
||||
type ShellCompleteFunc func(context.Context, *Command)
|
||||
|
||||
// BeforeFunc is an action that executes prior to any subcommands being run once
|
||||
// the context is ready. If a non-nil error is returned, no subcommands are
|
||||
// run.
|
||||
type BeforeFunc func(context.Context, *Command) (context.Context, error)
|
||||
|
||||
// AfterFunc is an action that executes after any subcommands are run and have
|
||||
// finished. The AfterFunc is run even if Action() panics.
|
||||
type AfterFunc func(context.Context, *Command) error
|
||||
|
||||
// ActionFunc is the action to execute when no subcommands are specified
|
||||
type ActionFunc func(context.Context, *Command) error
|
||||
|
||||
// CommandNotFoundFunc is executed if the proper command cannot be found
|
||||
type CommandNotFoundFunc func(context.Context, *Command, string)
|
||||
|
||||
// OnUsageErrorFunc is executed if a usage error occurs. This is useful for displaying
|
||||
// customized usage error messages. This function is able to replace the
|
||||
// original error messages. If this function is not set, the "Incorrect usage"
|
||||
// is displayed and the execution is interrupted.
|
||||
type OnUsageErrorFunc func(ctx context.Context, cmd *Command, err error, isSubcommand bool) error
|
||||
|
||||
// InvalidFlagAccessFunc is executed when an invalid flag is accessed from the context.
|
||||
type InvalidFlagAccessFunc func(context.Context, *Command, string)
|
||||
|
||||
// ExitErrHandlerFunc is executed if provided in order to handle exitError values
|
||||
// returned by Actions and Before/After functions.
|
||||
type ExitErrHandlerFunc func(context.Context, *Command, error)
|
||||
|
||||
// FlagStringFunc is used by the help generation to display a flag, which is
|
||||
// expected to be a single line.
|
||||
type FlagStringFunc func(Flag) string
|
||||
|
||||
// FlagNamePrefixFunc is used by the default FlagStringFunc to create prefix
|
||||
// text for a flag's full name.
|
||||
type FlagNamePrefixFunc func(fullName []string, placeholder string) string
|
||||
|
||||
// FlagEnvHintFunc is used by the default FlagStringFunc to annotate flag help
|
||||
// with the environment variable details.
|
||||
type FlagEnvHintFunc func(envVars []string, str string) string
|
||||
|
||||
// FlagFileHintFunc is used by the default FlagStringFunc to annotate flag help
|
||||
// with the file path details.
|
||||
type FlagFileHintFunc func(filePath, str string) string
|
1066
vendor/github.com/urfave/cli/v3/godoc-current.txt
generated
vendored
Normal file
1066
vendor/github.com/urfave/cli/v3/godoc-current.txt
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
596
vendor/github.com/urfave/cli/v3/help.go
generated
vendored
Normal file
596
vendor/github.com/urfave/cli/v3/help.go
generated
vendored
Normal file
@ -0,0 +1,596 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"text/template"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
const (
|
||||
helpName = "help"
|
||||
helpAlias = "h"
|
||||
)
|
||||
|
||||
// Prints help for the App or Command
|
||||
type helpPrinter func(w io.Writer, templ string, data interface{})
|
||||
|
||||
// Prints help for the App or Command with custom template function.
|
||||
type helpPrinterCustom func(w io.Writer, templ string, data interface{}, customFunc map[string]interface{})
|
||||
|
||||
// HelpPrinter is a function that writes the help output. If not set explicitly,
|
||||
// this calls HelpPrinterCustom using only the default template functions.
|
||||
//
|
||||
// If custom logic for printing help is required, this function can be
|
||||
// overridden. If the ExtraInfo field is defined on an App, this function
|
||||
// should not be modified, as HelpPrinterCustom will be used directly in order
|
||||
// to capture the extra information.
|
||||
var HelpPrinter helpPrinter = printHelp
|
||||
|
||||
// HelpPrinterCustom is a function that writes the help output. It is used as
|
||||
// the default implementation of HelpPrinter, and may be called directly if
|
||||
// the ExtraInfo field is set on an App.
|
||||
//
|
||||
// In the default implementation, if the customFuncs argument contains a
|
||||
// "wrapAt" key, which is a function which takes no arguments and returns
|
||||
// an int, this int value will be used to produce a "wrap" function used
|
||||
// by the default template to wrap long lines.
|
||||
var HelpPrinterCustom helpPrinterCustom = printHelpCustom
|
||||
|
||||
// VersionPrinter prints the version for the App
|
||||
var VersionPrinter = printVersion
|
||||
|
||||
func buildHelpCommand(withAction bool) *Command {
|
||||
cmd := &Command{
|
||||
Name: helpName,
|
||||
Aliases: []string{helpAlias},
|
||||
Usage: "Shows a list of commands or help for one command",
|
||||
ArgsUsage: "[command]",
|
||||
HideHelp: true,
|
||||
}
|
||||
|
||||
if withAction {
|
||||
cmd.Action = helpCommandAction
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func helpCommandAction(ctx context.Context, cmd *Command) error {
|
||||
args := cmd.Args()
|
||||
firstArg := args.First()
|
||||
|
||||
tracef("doing help for cmd %[1]q with args %[2]q", cmd, args)
|
||||
|
||||
// This action can be triggered by a "default" action of a command
|
||||
// or via cmd.Run when cmd == helpCmd. So we have following possibilities
|
||||
//
|
||||
// 1 $ app
|
||||
// 2 $ app help
|
||||
// 3 $ app foo
|
||||
// 4 $ app help foo
|
||||
// 5 $ app foo help
|
||||
|
||||
// Case 4. when executing a help command set the context to parent
|
||||
// to allow resolution of subsequent args. This will transform
|
||||
// $ app help foo
|
||||
// to
|
||||
// $ app foo
|
||||
// which will then be handled as case 3
|
||||
if cmd.parent != nil && (cmd.HasName(helpName) || cmd.HasName(helpAlias)) {
|
||||
tracef("setting cmd to cmd.parent")
|
||||
cmd = cmd.parent
|
||||
}
|
||||
|
||||
// Case 4. $ app help foo
|
||||
// foo is the command for which help needs to be shown
|
||||
if firstArg != "" {
|
||||
if firstArg == "--" {
|
||||
return nil
|
||||
}
|
||||
tracef("returning ShowCommandHelp with %[1]q", firstArg)
|
||||
return ShowCommandHelp(ctx, cmd, firstArg)
|
||||
}
|
||||
|
||||
// Case 1 & 2
|
||||
// Special case when running help on main app itself as opposed to individual
|
||||
// commands/subcommands
|
||||
if cmd.parent == nil {
|
||||
tracef("returning ShowAppHelp")
|
||||
_ = ShowAppHelp(cmd)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Case 3, 5
|
||||
if (len(cmd.Commands) == 1 && !cmd.HideHelp) ||
|
||||
(len(cmd.Commands) == 0 && cmd.HideHelp) {
|
||||
|
||||
tmpl := cmd.CustomHelpTemplate
|
||||
if tmpl == "" {
|
||||
tmpl = CommandHelpTemplate
|
||||
}
|
||||
|
||||
tracef("running HelpPrinter with command %[1]q", cmd.Name)
|
||||
HelpPrinter(cmd.Root().Writer, tmpl, cmd)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
tracef("running ShowSubcommandHelp")
|
||||
return ShowSubcommandHelp(cmd)
|
||||
}
|
||||
|
||||
// ShowAppHelpAndExit - Prints the list of subcommands for the app and exits with exit code.
|
||||
func ShowAppHelpAndExit(cmd *Command, exitCode int) {
|
||||
_ = ShowAppHelp(cmd)
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
|
||||
// ShowAppHelp is an action that displays the help.
|
||||
func ShowAppHelp(cmd *Command) error {
|
||||
tmpl := cmd.CustomRootCommandHelpTemplate
|
||||
if tmpl == "" {
|
||||
tracef("using RootCommandHelpTemplate")
|
||||
tmpl = RootCommandHelpTemplate
|
||||
}
|
||||
|
||||
if cmd.ExtraInfo == nil {
|
||||
HelpPrinter(cmd.Root().Writer, tmpl, cmd.Root())
|
||||
return nil
|
||||
}
|
||||
|
||||
tracef("setting ExtraInfo in customAppData")
|
||||
customAppData := func() map[string]any {
|
||||
return map[string]any{
|
||||
"ExtraInfo": cmd.ExtraInfo,
|
||||
}
|
||||
}
|
||||
HelpPrinterCustom(cmd.Root().Writer, tmpl, cmd.Root(), customAppData())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DefaultAppComplete prints the list of subcommands as the default app completion method
|
||||
func DefaultAppComplete(ctx context.Context, cmd *Command) {
|
||||
DefaultCompleteWithFlags(ctx, cmd)
|
||||
}
|
||||
|
||||
func printCommandSuggestions(commands []*Command, writer io.Writer) {
|
||||
for _, command := range commands {
|
||||
if command.Hidden {
|
||||
continue
|
||||
}
|
||||
if strings.HasSuffix(os.Getenv("SHELL"), "zsh") {
|
||||
_, _ = fmt.Fprintf(writer, "%s:%s\n", command.Name, command.Usage)
|
||||
} else {
|
||||
_, _ = fmt.Fprintf(writer, "%s\n", command.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cliArgContains(flagName string, args []string) bool {
|
||||
for _, name := range strings.Split(flagName, ",") {
|
||||
name = strings.TrimSpace(name)
|
||||
count := utf8.RuneCountInString(name)
|
||||
if count > 2 {
|
||||
count = 2
|
||||
}
|
||||
flag := fmt.Sprintf("%s%s", strings.Repeat("-", count), name)
|
||||
for _, a := range args {
|
||||
if a == flag {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func printFlagSuggestions(lastArg string, flags []Flag, writer io.Writer) {
|
||||
// Trim to handle both "-short" and "--long" flags.
|
||||
cur := strings.TrimLeft(lastArg, "-")
|
||||
for _, flag := range flags {
|
||||
if bflag, ok := flag.(*BoolFlag); ok && bflag.Hidden {
|
||||
continue
|
||||
}
|
||||
|
||||
usage := ""
|
||||
if docFlag, ok := flag.(DocGenerationFlag); ok {
|
||||
usage = docFlag.GetUsage()
|
||||
}
|
||||
|
||||
name := strings.TrimSpace(flag.Names()[0])
|
||||
// this will get total count utf8 letters in flag name
|
||||
count := utf8.RuneCountInString(name)
|
||||
if count > 2 {
|
||||
count = 2 // reuse this count to generate single - or -- in flag completion
|
||||
}
|
||||
// if flag name has more than one utf8 letter and last argument in cli has -- prefix then
|
||||
// skip flag completion for short flags example -v or -x
|
||||
if strings.HasPrefix(lastArg, "--") && count == 1 {
|
||||
continue
|
||||
}
|
||||
// match if last argument matches this flag and it is not repeated
|
||||
if strings.HasPrefix(name, cur) && cur != name && !cliArgContains(name, os.Args) {
|
||||
flagCompletion := fmt.Sprintf("%s%s", strings.Repeat("-", count), name)
|
||||
if usage != "" && strings.HasSuffix(os.Getenv("SHELL"), "zsh") {
|
||||
flagCompletion = fmt.Sprintf("%s:%s", flagCompletion, usage)
|
||||
}
|
||||
fmt.Fprintln(writer, flagCompletion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func DefaultCompleteWithFlags(ctx context.Context, cmd *Command) {
|
||||
args := os.Args
|
||||
if cmd != nil && cmd.flagSet != nil && cmd.parent != nil {
|
||||
args = cmd.Args().Slice()
|
||||
tracef("running default complete with flags[%v] on command %[2]q", args, cmd.Name)
|
||||
} else {
|
||||
tracef("running default complete with os.Args flags[%v]", args)
|
||||
}
|
||||
argsLen := len(args)
|
||||
lastArg := ""
|
||||
// parent command will have --generate-shell-completion so we need
|
||||
// to account for that
|
||||
if argsLen > 1 {
|
||||
lastArg = args[argsLen-2]
|
||||
} else if argsLen > 0 {
|
||||
lastArg = args[argsLen-1]
|
||||
}
|
||||
|
||||
if lastArg == "--" {
|
||||
tracef("not printing flag suggestion as last arg is --")
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(lastArg, "-") {
|
||||
tracef("printing flag suggestion for flag[%v] on command %[1]q", lastArg, cmd.Name)
|
||||
printFlagSuggestions(lastArg, cmd.Flags, cmd.Root().Writer)
|
||||
return
|
||||
}
|
||||
|
||||
if cmd != nil {
|
||||
tracef("printing command suggestions on command %[1]q", cmd.Name)
|
||||
printCommandSuggestions(cmd.Commands, cmd.Root().Writer)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// ShowCommandHelpAndExit - exits with code after showing help
|
||||
func ShowCommandHelpAndExit(ctx context.Context, cmd *Command, command string, code int) {
|
||||
_ = ShowCommandHelp(ctx, cmd, command)
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
// ShowCommandHelp prints help for the given command
|
||||
func ShowCommandHelp(ctx context.Context, cmd *Command, commandName string) error {
|
||||
for _, subCmd := range cmd.Commands {
|
||||
if !subCmd.HasName(commandName) {
|
||||
continue
|
||||
}
|
||||
|
||||
tmpl := subCmd.CustomHelpTemplate
|
||||
if tmpl == "" {
|
||||
if len(subCmd.Commands) == 0 {
|
||||
tracef("using CommandHelpTemplate")
|
||||
tmpl = CommandHelpTemplate
|
||||
} else {
|
||||
tracef("using SubcommandHelpTemplate")
|
||||
tmpl = SubcommandHelpTemplate
|
||||
}
|
||||
}
|
||||
|
||||
tracef("running HelpPrinter")
|
||||
HelpPrinter(cmd.Root().Writer, tmpl, subCmd)
|
||||
|
||||
tracef("returning nil after printing help")
|
||||
return nil
|
||||
}
|
||||
|
||||
tracef("no matching command found")
|
||||
|
||||
if cmd.CommandNotFound == nil {
|
||||
errMsg := fmt.Sprintf("No help topic for '%v'", commandName)
|
||||
|
||||
if cmd.Suggest {
|
||||
if suggestion := SuggestCommand(cmd.Commands, commandName); suggestion != "" {
|
||||
errMsg += ". " + suggestion
|
||||
}
|
||||
}
|
||||
|
||||
tracef("exiting 3 with errMsg %[1]q", errMsg)
|
||||
return Exit(errMsg, 3)
|
||||
}
|
||||
|
||||
tracef("running CommandNotFound func for %[1]q", commandName)
|
||||
cmd.CommandNotFound(ctx, cmd, commandName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ShowSubcommandHelpAndExit - Prints help for the given subcommand and exits with exit code.
|
||||
func ShowSubcommandHelpAndExit(cmd *Command, exitCode int) {
|
||||
_ = ShowSubcommandHelp(cmd)
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
|
||||
// ShowSubcommandHelp prints help for the given subcommand
|
||||
func ShowSubcommandHelp(cmd *Command) error {
|
||||
HelpPrinter(cmd.Root().Writer, SubcommandHelpTemplate, cmd)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ShowVersion prints the version number of the App
|
||||
func ShowVersion(cmd *Command) {
|
||||
tracef("showing version via VersionPrinter (cmd=%[1]q)", cmd.Name)
|
||||
VersionPrinter(cmd)
|
||||
}
|
||||
|
||||
func printVersion(cmd *Command) {
|
||||
_, _ = fmt.Fprintf(cmd.Root().Writer, "%v version %v\n", cmd.Name, cmd.Version)
|
||||
}
|
||||
|
||||
func handleTemplateError(err error) {
|
||||
if err != nil {
|
||||
tracef("error encountered during template parse: %[1]v", err)
|
||||
// If the writer is closed, t.Execute will fail, and there's nothing
|
||||
// we can do to recover.
|
||||
if os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != "" {
|
||||
_, _ = fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR: %#v\n", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// printHelpCustom is the default implementation of HelpPrinterCustom.
|
||||
//
|
||||
// The customFuncs map will be combined with a default template.FuncMap to
|
||||
// allow using arbitrary functions in template rendering.
|
||||
func printHelpCustom(out io.Writer, templ string, data interface{}, customFuncs map[string]interface{}) {
|
||||
const maxLineLength = 10000
|
||||
|
||||
tracef("building default funcMap")
|
||||
funcMap := template.FuncMap{
|
||||
"join": strings.Join,
|
||||
"subtract": subtract,
|
||||
"indent": indent,
|
||||
"nindent": nindent,
|
||||
"trim": strings.TrimSpace,
|
||||
"wrap": func(input string, offset int) string { return wrap(input, offset, maxLineLength) },
|
||||
"offset": offset,
|
||||
"offsetCommands": offsetCommands,
|
||||
}
|
||||
|
||||
if wa, ok := customFuncs["wrapAt"]; ok {
|
||||
if wrapAtFunc, ok := wa.(func() int); ok {
|
||||
wrapAt := wrapAtFunc()
|
||||
customFuncs["wrap"] = func(input string, offset int) string {
|
||||
return wrap(input, offset, wrapAt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for key, value := range customFuncs {
|
||||
funcMap[key] = value
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0)
|
||||
t := template.Must(template.New("help").Funcs(funcMap).Parse(templ))
|
||||
|
||||
if _, err := t.New("helpNameTemplate").Parse(helpNameTemplate); err != nil {
|
||||
handleTemplateError(err)
|
||||
}
|
||||
|
||||
if _, err := t.New("argsTemplate").Parse(argsTemplate); err != nil {
|
||||
handleTemplateError(err)
|
||||
}
|
||||
|
||||
if _, err := t.New("usageTemplate").Parse(usageTemplate); err != nil {
|
||||
handleTemplateError(err)
|
||||
}
|
||||
|
||||
if _, err := t.New("descriptionTemplate").Parse(descriptionTemplate); err != nil {
|
||||
handleTemplateError(err)
|
||||
}
|
||||
|
||||
if _, err := t.New("visibleCommandTemplate").Parse(visibleCommandTemplate); err != nil {
|
||||
handleTemplateError(err)
|
||||
}
|
||||
|
||||
if _, err := t.New("copyrightTemplate").Parse(copyrightTemplate); err != nil {
|
||||
handleTemplateError(err)
|
||||
}
|
||||
|
||||
if _, err := t.New("versionTemplate").Parse(versionTemplate); err != nil {
|
||||
handleTemplateError(err)
|
||||
}
|
||||
|
||||
if _, err := t.New("visibleFlagCategoryTemplate").Parse(visibleFlagCategoryTemplate); err != nil {
|
||||
handleTemplateError(err)
|
||||
}
|
||||
|
||||
if _, err := t.New("visibleFlagTemplate").Parse(visibleFlagTemplate); err != nil {
|
||||
handleTemplateError(err)
|
||||
}
|
||||
|
||||
if _, err := t.New("visiblePersistentFlagTemplate").Parse(visiblePersistentFlagTemplate); err != nil {
|
||||
handleTemplateError(err)
|
||||
}
|
||||
|
||||
if _, err := t.New("visibleGlobalFlagCategoryTemplate").Parse(strings.Replace(visibleFlagCategoryTemplate, "OPTIONS", "GLOBAL OPTIONS", -1)); err != nil {
|
||||
handleTemplateError(err)
|
||||
}
|
||||
|
||||
if _, err := t.New("authorsTemplate").Parse(authorsTemplate); err != nil {
|
||||
handleTemplateError(err)
|
||||
}
|
||||
|
||||
if _, err := t.New("visibleCommandCategoryTemplate").Parse(visibleCommandCategoryTemplate); err != nil {
|
||||
handleTemplateError(err)
|
||||
}
|
||||
|
||||
tracef("executing template")
|
||||
handleTemplateError(t.Execute(w, data))
|
||||
|
||||
_ = w.Flush()
|
||||
}
|
||||
|
||||
func printHelp(out io.Writer, templ string, data interface{}) {
|
||||
HelpPrinterCustom(out, templ, data, nil)
|
||||
}
|
||||
|
||||
func checkVersion(cmd *Command) bool {
|
||||
found := false
|
||||
for _, name := range VersionFlag.Names() {
|
||||
if cmd.Bool(name) {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
func checkShellCompleteFlag(c *Command, arguments []string) (bool, []string) {
|
||||
if (c.parent == nil && !c.EnableShellCompletion) || (c.parent != nil && !c.Root().shellCompletion) {
|
||||
return false, arguments
|
||||
}
|
||||
|
||||
pos := len(arguments) - 1
|
||||
lastArg := arguments[pos]
|
||||
|
||||
if lastArg != completionFlag {
|
||||
return false, arguments
|
||||
}
|
||||
|
||||
for _, arg := range arguments {
|
||||
// If arguments include "--", shell completion is disabled
|
||||
// because after "--" only positional arguments are accepted.
|
||||
// https://unix.stackexchange.com/a/11382
|
||||
if arg == "--" {
|
||||
return false, arguments[:pos]
|
||||
}
|
||||
}
|
||||
|
||||
return true, arguments[:pos]
|
||||
}
|
||||
|
||||
func checkCompletions(ctx context.Context, cmd *Command) bool {
|
||||
tracef("checking completions on command %[1]q", cmd.Name)
|
||||
|
||||
if !cmd.Root().shellCompletion {
|
||||
tracef("completion not enabled skipping %[1]q", cmd.Name)
|
||||
return false
|
||||
}
|
||||
|
||||
if argsArguments := cmd.Args(); argsArguments.Present() {
|
||||
name := argsArguments.First()
|
||||
if cmd := cmd.Command(name); cmd != nil {
|
||||
// let the command handle the completion
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
tracef("no subcommand found for completiot %[1]q", cmd.Name)
|
||||
|
||||
if cmd.ShellComplete != nil {
|
||||
tracef("running shell completion func for command %[1]q", cmd.Name)
|
||||
cmd.ShellComplete(ctx, cmd)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func subtract(a, b int) int {
|
||||
return a - b
|
||||
}
|
||||
|
||||
func indent(spaces int, v string) string {
|
||||
pad := strings.Repeat(" ", spaces)
|
||||
return pad + strings.Replace(v, "\n", "\n"+pad, -1)
|
||||
}
|
||||
|
||||
func nindent(spaces int, v string) string {
|
||||
return "\n" + indent(spaces, v)
|
||||
}
|
||||
|
||||
func wrap(input string, offset int, wrapAt int) string {
|
||||
var ss []string
|
||||
|
||||
lines := strings.Split(input, "\n")
|
||||
|
||||
padding := strings.Repeat(" ", offset)
|
||||
|
||||
for i, line := range lines {
|
||||
if line == "" {
|
||||
ss = append(ss, line)
|
||||
} else {
|
||||
wrapped := wrapLine(line, offset, wrapAt, padding)
|
||||
if i == 0 {
|
||||
ss = append(ss, wrapped)
|
||||
} else {
|
||||
ss = append(ss, padding+wrapped)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(ss, "\n")
|
||||
}
|
||||
|
||||
func wrapLine(input string, offset int, wrapAt int, padding string) string {
|
||||
if wrapAt <= offset || len(input) <= wrapAt-offset {
|
||||
return input
|
||||
}
|
||||
|
||||
lineWidth := wrapAt - offset
|
||||
words := strings.Fields(input)
|
||||
if len(words) == 0 {
|
||||
return input
|
||||
}
|
||||
|
||||
wrapped := words[0]
|
||||
spaceLeft := lineWidth - len(wrapped)
|
||||
for _, word := range words[1:] {
|
||||
if len(word)+1 > spaceLeft {
|
||||
wrapped += "\n" + padding + word
|
||||
spaceLeft = lineWidth - len(word)
|
||||
} else {
|
||||
wrapped += " " + word
|
||||
spaceLeft -= 1 + len(word)
|
||||
}
|
||||
}
|
||||
|
||||
return wrapped
|
||||
}
|
||||
|
||||
func offset(input string, fixed int) int {
|
||||
return len(input) + fixed
|
||||
}
|
||||
|
||||
// this function tries to find the max width of the names column
|
||||
// so say we have the following rows for help
|
||||
//
|
||||
// foo1, foo2, foo3 some string here
|
||||
// bar1, b2 some other string here
|
||||
//
|
||||
// We want to offset the 2nd row usage by some amount so that everything
|
||||
// is aligned
|
||||
//
|
||||
// foo1, foo2, foo3 some string here
|
||||
// bar1, b2 some other string here
|
||||
//
|
||||
// to find that offset we find the length of all the rows and use the max
|
||||
// to calculate the offset
|
||||
func offsetCommands(cmds []*Command, fixed int) int {
|
||||
max := 0
|
||||
for _, cmd := range cmds {
|
||||
s := strings.Join(cmd.Names(), ", ")
|
||||
if len(s) > max {
|
||||
max = len(s)
|
||||
}
|
||||
}
|
||||
return max + fixed
|
||||
}
|
4
vendor/github.com/urfave/cli/v3/mkdocs-reqs.txt
generated
vendored
Normal file
4
vendor/github.com/urfave/cli/v3/mkdocs-reqs.txt
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
mkdocs-git-revision-date-localized-plugin~=1.2
|
||||
mkdocs-material~=9.5
|
||||
mkdocs~=1.6
|
||||
pygments~=2.18
|
107
vendor/github.com/urfave/cli/v3/mkdocs.yml
generated
vendored
Normal file
107
vendor/github.com/urfave/cli/v3/mkdocs.yml
generated
vendored
Normal file
@ -0,0 +1,107 @@
|
||||
# NOTE: the mkdocs dependencies will need to be installed out of
|
||||
# band until this whole thing gets more automated:
|
||||
#
|
||||
# pip install -r mkdocs-reqs.txt
|
||||
#
|
||||
|
||||
site_name: urfave/cli
|
||||
site_url: https://cli.urfave.org/
|
||||
repo_url: https://github.com/urfave/cli
|
||||
edit_uri: edit/main/docs/
|
||||
nav:
|
||||
- Home:
|
||||
- Welcome: index.md
|
||||
- Contributing: CONTRIBUTING.md
|
||||
- Code of Conduct: CODE_OF_CONDUCT.md
|
||||
- Releasing: RELEASING.md
|
||||
- Security: SECURITY.md
|
||||
- Migrate v1 to v2: migrate-v1-to-v2.md
|
||||
- v2 Manual:
|
||||
- Getting Started: v2/getting-started.md
|
||||
- Migrating From Older Releases: v2/migrating-from-older-releases.md
|
||||
- Examples:
|
||||
- Greet: v2/examples/greet.md
|
||||
- Arguments: v2/examples/arguments.md
|
||||
- Flags: v2/examples/flags.md
|
||||
- Subcommands: v2/examples/subcommands.md
|
||||
- Subcommands Categories: v2/examples/subcommands-categories.md
|
||||
- Exit Codes: v2/examples/exit-codes.md
|
||||
- Combining Short Options: v2/examples/combining-short-options.md
|
||||
- Bash Completions: v2/examples/bash-completions.md
|
||||
- Generated Help Text: v2/examples/generated-help-text.md
|
||||
- Version Flag: v2/examples/version-flag.md
|
||||
- Timestamp Flag: v2/examples/timestamp-flag.md
|
||||
- Suggestions: v2/examples/suggestions.md
|
||||
- Full API Example: v2/examples/full-api-example.md
|
||||
- v1 Manual:
|
||||
- Getting Started: v1/getting-started.md
|
||||
- Migrating to v2: v1/migrating-to-v2.md
|
||||
- Examples:
|
||||
- Greet: v1/examples/greet.md
|
||||
- Arguments: v1/examples/arguments.md
|
||||
- Flags: v1/examples/flags.md
|
||||
- Subcommands: v1/examples/subcommands.md
|
||||
- Subcommands (Categories): v1/examples/subcommands-categories.md
|
||||
- Exit Codes: v1/examples/exit-codes.md
|
||||
- Combining Short Options: v1/examples/combining-short-options.md
|
||||
- Bash Completions: v1/examples/bash-completions.md
|
||||
- Generated Help Text: v1/examples/generated-help-text.md
|
||||
- Version Flag: v1/examples/version-flag.md
|
||||
|
||||
theme:
|
||||
name: material
|
||||
palette:
|
||||
- media: "(prefers-color-scheme: light)"
|
||||
scheme: default
|
||||
toggle:
|
||||
icon: material/brightness-4
|
||||
name: dark mode
|
||||
- media: "(prefers-color-scheme: dark)"
|
||||
scheme: slate
|
||||
toggle:
|
||||
icon: material/brightness-7
|
||||
name: light mode
|
||||
features:
|
||||
- content.code.annotate
|
||||
- navigation.top
|
||||
- navigation.instant
|
||||
- navigation.expand
|
||||
- navigation.sections
|
||||
- navigation.tabs
|
||||
- navigation.tabs.sticky
|
||||
plugins:
|
||||
- git-revision-date-localized
|
||||
- search
|
||||
- tags
|
||||
# NOTE: this is the recommended configuration from
|
||||
# https://squidfunk.github.io/mkdocs-material/setup/extensions/#recommended-configuration
|
||||
markdown_extensions:
|
||||
- abbr
|
||||
- admonition
|
||||
- attr_list
|
||||
- def_list
|
||||
- footnotes
|
||||
- meta
|
||||
- md_in_html
|
||||
- toc:
|
||||
permalink: true
|
||||
- pymdownx.arithmatex:
|
||||
generic: true
|
||||
- pymdownx.betterem:
|
||||
smart_enable: all
|
||||
- pymdownx.caret
|
||||
- pymdownx.details
|
||||
- pymdownx.emoji:
|
||||
emoji_index: !!python/name:material.extensions.emoji.twemoji
|
||||
emoji_generator: !!python/name:material.extensions.emoji.to_svg
|
||||
- pymdownx.highlight
|
||||
- pymdownx.inlinehilite
|
||||
- pymdownx.keys
|
||||
- pymdownx.mark
|
||||
- pymdownx.smartsymbols
|
||||
- pymdownx.superfences
|
||||
- pymdownx.tabbed:
|
||||
alternate_style: true
|
||||
- pymdownx.tasklist:
|
||||
custom_checkbox: true
|
||||
- pymdownx.tilde
|
118
vendor/github.com/urfave/cli/v3/parse.go
generated
vendored
Normal file
118
vendor/github.com/urfave/cli/v3/parse.go
generated
vendored
Normal file
@ -0,0 +1,118 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type iterativeParser interface {
|
||||
useShortOptionHandling() bool
|
||||
}
|
||||
|
||||
// To enable short-option handling (e.g., "-it" vs "-i -t") we have to
|
||||
// iteratively catch parsing errors. This way we achieve LR parsing without
|
||||
// transforming any arguments. Otherwise, there is no way we can discriminate
|
||||
// combined short options from common arguments that should be left untouched.
|
||||
// Pass `shellComplete` to continue parsing options on failure during shell
|
||||
// completion when, the user-supplied options may be incomplete.
|
||||
func parseIter(set *flag.FlagSet, ip iterativeParser, args []string, shellComplete bool) error {
|
||||
for {
|
||||
tracef("parsing args %[1]q with %[2]T (name=%[3]q)", args, set, set.Name())
|
||||
|
||||
err := set.Parse(args)
|
||||
if !ip.useShortOptionHandling() || err == nil {
|
||||
if shellComplete {
|
||||
tracef("returning nil due to shellComplete=true")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
tracef("returning err %[1]q", err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
tracef("finding flag from error %[1]q", err)
|
||||
|
||||
trimmed, trimErr := flagFromError(err)
|
||||
if trimErr != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tracef("regenerating the initial args with the split short opts")
|
||||
|
||||
argsWereSplit := false
|
||||
for i, arg := range args {
|
||||
tracef("skipping args that are not part of the error message (i=%[1]v arg=%[2]q)", i, arg)
|
||||
|
||||
if name := strings.TrimLeft(arg, "-"); name != trimmed {
|
||||
continue
|
||||
}
|
||||
|
||||
tracef("trying to split short option (arg=%[1]q)", arg)
|
||||
|
||||
shortOpts := splitShortOptions(set, arg)
|
||||
if len(shortOpts) == 1 {
|
||||
return err
|
||||
}
|
||||
|
||||
tracef(
|
||||
"swapping current argument with the split version (shortOpts=%[1]q args=%[2]q)",
|
||||
shortOpts, args,
|
||||
)
|
||||
|
||||
// do not include args that parsed correctly so far as it would
|
||||
// trigger Value.Set() on those args and would result in
|
||||
// duplicates for slice type flags
|
||||
args = append(shortOpts, args[i+1:]...)
|
||||
argsWereSplit = true
|
||||
break
|
||||
}
|
||||
|
||||
tracef("this should be an impossible to reach code path")
|
||||
// but in case the arg splitting failed to happen, this
|
||||
// will prevent infinite loops
|
||||
if !argsWereSplit {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const providedButNotDefinedErrMsg = "flag provided but not defined: -"
|
||||
|
||||
// flagFromError tries to parse a provided flag from an error message. If the
|
||||
// parsing fails, it returns the input error and an empty string
|
||||
func flagFromError(err error) (string, error) {
|
||||
errStr := err.Error()
|
||||
trimmed := strings.TrimPrefix(errStr, providedButNotDefinedErrMsg)
|
||||
if errStr == trimmed {
|
||||
return "", err
|
||||
}
|
||||
return trimmed, nil
|
||||
}
|
||||
|
||||
func splitShortOptions(set *flag.FlagSet, arg string) []string {
|
||||
shortFlagsExist := func(s string) bool {
|
||||
for _, c := range s[1:] {
|
||||
if f := set.Lookup(string(c)); f == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if !isSplittable(arg) || !shortFlagsExist(arg) {
|
||||
return []string{arg}
|
||||
}
|
||||
|
||||
separated := make([]string, 0, len(arg)-1)
|
||||
for _, flagChar := range arg[1:] {
|
||||
separated = append(separated, "-"+string(flagChar))
|
||||
}
|
||||
|
||||
return separated
|
||||
}
|
||||
|
||||
func isSplittable(flagArg string) bool {
|
||||
return strings.HasPrefix(flagArg, "-") && !strings.HasPrefix(flagArg, "--") && len(flagArg) > 2
|
||||
}
|
29
vendor/github.com/urfave/cli/v3/sort.go
generated
vendored
Normal file
29
vendor/github.com/urfave/cli/v3/sort.go
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
package cli
|
||||
|
||||
import "unicode"
|
||||
|
||||
// lexicographicLess compares strings alphabetically considering case.
|
||||
func lexicographicLess(i, j string) bool {
|
||||
iRunes := []rune(i)
|
||||
jRunes := []rune(j)
|
||||
|
||||
lenShared := len(iRunes)
|
||||
if lenShared > len(jRunes) {
|
||||
lenShared = len(jRunes)
|
||||
}
|
||||
|
||||
for index := 0; index < lenShared; index++ {
|
||||
ir := iRunes[index]
|
||||
jr := jRunes[index]
|
||||
|
||||
if lir, ljr := unicode.ToLower(ir), unicode.ToLower(jr); lir != ljr {
|
||||
return lir < ljr
|
||||
}
|
||||
|
||||
if ir != jr {
|
||||
return ir < jr
|
||||
}
|
||||
}
|
||||
|
||||
return i < j
|
||||
}
|
1
vendor/github.com/urfave/cli/v3/staticcheck.conf
generated
vendored
Normal file
1
vendor/github.com/urfave/cli/v3/staticcheck.conf
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
checks=["all"]
|
147
vendor/github.com/urfave/cli/v3/suggestions.go
generated
vendored
Normal file
147
vendor/github.com/urfave/cli/v3/suggestions.go
generated
vendored
Normal file
@ -0,0 +1,147 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
const suggestDidYouMeanTemplate = "Did you mean %q?"
|
||||
|
||||
var (
|
||||
SuggestFlag SuggestFlagFunc = suggestFlag
|
||||
SuggestCommand SuggestCommandFunc = suggestCommand
|
||||
SuggestDidYouMeanTemplate string = suggestDidYouMeanTemplate
|
||||
)
|
||||
|
||||
type SuggestFlagFunc func(flags []Flag, provided string, hideHelp bool) string
|
||||
|
||||
type SuggestCommandFunc func(commands []*Command, provided string) string
|
||||
|
||||
// jaroDistance is the measure of similarity between two strings. It returns a
|
||||
// value between 0 and 1, where 1 indicates identical strings and 0 indicates
|
||||
// completely different strings.
|
||||
//
|
||||
// Adapted from https://github.com/xrash/smetrics/blob/5f08fbb34913bc8ab95bb4f2a89a0637ca922666/jaro.go.
|
||||
func jaroDistance(a, b string) float64 {
|
||||
if len(a) == 0 && len(b) == 0 {
|
||||
return 1
|
||||
}
|
||||
if len(a) == 0 || len(b) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
lenA := float64(len(a))
|
||||
lenB := float64(len(b))
|
||||
hashA := make([]bool, len(a))
|
||||
hashB := make([]bool, len(b))
|
||||
maxDistance := int(math.Max(0, math.Floor(math.Max(lenA, lenB)/2.0)-1))
|
||||
|
||||
var matches float64
|
||||
for i := 0; i < len(a); i++ {
|
||||
start := int(math.Max(0, float64(i-maxDistance)))
|
||||
end := int(math.Min(lenB-1, float64(i+maxDistance)))
|
||||
|
||||
for j := start; j <= end; j++ {
|
||||
if hashB[j] {
|
||||
continue
|
||||
}
|
||||
if a[i] == b[j] {
|
||||
hashA[i] = true
|
||||
hashB[j] = true
|
||||
matches++
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if matches == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
var transpositions float64
|
||||
var j int
|
||||
for i := 0; i < len(a); i++ {
|
||||
if !hashA[i] {
|
||||
continue
|
||||
}
|
||||
for !hashB[j] {
|
||||
j++
|
||||
}
|
||||
if a[i] != b[j] {
|
||||
transpositions++
|
||||
}
|
||||
j++
|
||||
}
|
||||
|
||||
transpositions /= 2
|
||||
return ((matches / lenA) + (matches / lenB) + ((matches - transpositions) / matches)) / 3.0
|
||||
}
|
||||
|
||||
// jaroWinkler is more accurate when strings have a common prefix up to a
|
||||
// defined maximum length.
|
||||
//
|
||||
// Adapted from https://github.com/xrash/smetrics/blob/5f08fbb34913bc8ab95bb4f2a89a0637ca922666/jaro-winkler.go.
|
||||
func jaroWinkler(a, b string) float64 {
|
||||
const (
|
||||
boostThreshold = 0.7
|
||||
prefixSize = 4
|
||||
)
|
||||
jaroDist := jaroDistance(a, b)
|
||||
if jaroDist <= boostThreshold {
|
||||
return jaroDist
|
||||
}
|
||||
|
||||
prefix := int(math.Min(float64(len(a)), math.Min(float64(prefixSize), float64(len(b)))))
|
||||
|
||||
var prefixMatch float64
|
||||
for i := 0; i < prefix; i++ {
|
||||
if a[i] == b[i] {
|
||||
prefixMatch++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return jaroDist + 0.1*prefixMatch*(1.0-jaroDist)
|
||||
}
|
||||
|
||||
func suggestFlag(flags []Flag, provided string, hideHelp bool) string {
|
||||
distance := 0.0
|
||||
suggestion := ""
|
||||
|
||||
for _, flag := range flags {
|
||||
flagNames := flag.Names()
|
||||
if !hideHelp && HelpFlag != nil {
|
||||
flagNames = append(flagNames, HelpFlag.Names()...)
|
||||
}
|
||||
for _, name := range flagNames {
|
||||
newDistance := jaroWinkler(name, provided)
|
||||
if newDistance > distance {
|
||||
distance = newDistance
|
||||
suggestion = name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(suggestion) == 1 {
|
||||
suggestion = "-" + suggestion
|
||||
} else if len(suggestion) > 1 {
|
||||
suggestion = "--" + suggestion
|
||||
}
|
||||
|
||||
return suggestion
|
||||
}
|
||||
|
||||
// suggestCommand takes a list of commands and a provided string to suggest a
|
||||
// command name
|
||||
func suggestCommand(commands []*Command, provided string) (suggestion string) {
|
||||
distance := 0.0
|
||||
for _, command := range commands {
|
||||
for _, name := range append(command.Names(), helpName, helpAlias) {
|
||||
newDistance := jaroWinkler(name, provided)
|
||||
if newDistance > distance {
|
||||
distance = newDistance
|
||||
suggestion = name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return suggestion
|
||||
}
|
125
vendor/github.com/urfave/cli/v3/template.go
generated
vendored
Normal file
125
vendor/github.com/urfave/cli/v3/template.go
generated
vendored
Normal file
@ -0,0 +1,125 @@
|
||||
package cli
|
||||
|
||||
var (
|
||||
helpNameTemplate = `{{$v := offset .FullName 6}}{{wrap .FullName 3}}{{if .Usage}} - {{wrap .Usage $v}}{{end}}`
|
||||
argsTemplate = `{{if .Arguments}}{{range .Arguments}}{{.Usage}}{{end}}{{end}}`
|
||||
usageTemplate = `{{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.FullName}}{{if .VisibleFlags}} [command [command options]]{{end}}{{if .ArgsUsage}} {{.ArgsUsage}}{{else}}{{if .Arguments}} {{template "argsTemplate" .}}{{end}}{{end}}{{end}}`
|
||||
descriptionTemplate = `{{wrap .Description 3}}`
|
||||
authorsTemplate = `{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}:
|
||||
{{range $index, $author := .Authors}}{{if $index}}
|
||||
{{end}}{{$author}}{{end}}`
|
||||
)
|
||||
|
||||
var visibleCommandTemplate = `{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}}
|
||||
{{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}`
|
||||
|
||||
var visibleCommandCategoryTemplate = `{{range .VisibleCategories}}{{if .Name}}
|
||||
|
||||
{{.Name}}:{{range .VisibleCommands}}
|
||||
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{template "visibleCommandTemplate" .}}{{end}}{{end}}`
|
||||
|
||||
var visibleFlagCategoryTemplate = `{{range .VisibleFlagCategories}}
|
||||
{{if .Name}}{{.Name}}
|
||||
|
||||
{{end}}{{$flglen := len .Flags}}{{range $i, $e := .Flags}}{{if eq (subtract $flglen $i) 1}}{{$e}}
|
||||
{{else}}{{$e}}
|
||||
{{end}}{{end}}{{end}}`
|
||||
|
||||
var visibleFlagTemplate = `{{range $i, $e := .VisibleFlags}}
|
||||
{{wrap $e.String 6}}{{end}}`
|
||||
|
||||
var visiblePersistentFlagTemplate = `{{range $i, $e := .VisiblePersistentFlags}}
|
||||
{{wrap $e.String 6}}{{end}}`
|
||||
|
||||
var versionTemplate = `{{if .Version}}{{if not .HideVersion}}
|
||||
|
||||
VERSION:
|
||||
{{.Version}}{{end}}{{end}}`
|
||||
|
||||
var copyrightTemplate = `{{wrap .Copyright 3}}`
|
||||
|
||||
// RootCommandHelpTemplate is the text template for the Default help topic.
|
||||
// cli.go uses text/template to render templates. You can
|
||||
// render custom help text by setting this variable.
|
||||
var RootCommandHelpTemplate = `NAME:
|
||||
{{template "helpNameTemplate" .}}
|
||||
|
||||
USAGE:
|
||||
{{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.FullName}} {{if .VisibleFlags}}[global options]{{end}}{{if .VisibleCommands}} [command [command options]]{{end}}{{if .ArgsUsage}} {{.ArgsUsage}}{{else}}{{if .Arguments}} [arguments...]{{end}}{{end}}{{end}}{{if .Version}}{{if not .HideVersion}}
|
||||
|
||||
VERSION:
|
||||
{{.Version}}{{end}}{{end}}{{if .Description}}
|
||||
|
||||
DESCRIPTION:
|
||||
{{template "descriptionTemplate" .}}{{end}}
|
||||
{{- if len .Authors}}
|
||||
|
||||
AUTHOR{{template "authorsTemplate" .}}{{end}}{{if .VisibleCommands}}
|
||||
|
||||
COMMANDS:{{template "visibleCommandCategoryTemplate" .}}{{end}}{{if .VisibleFlagCategories}}
|
||||
|
||||
GLOBAL OPTIONS:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}}
|
||||
|
||||
GLOBAL OPTIONS:{{template "visibleFlagTemplate" .}}{{end}}{{if .Copyright}}
|
||||
|
||||
COPYRIGHT:
|
||||
{{template "copyrightTemplate" .}}{{end}}
|
||||
`
|
||||
|
||||
// CommandHelpTemplate is the text template for the command help topic.
|
||||
// cli.go uses text/template to render templates. You can
|
||||
// render custom help text by setting this variable.
|
||||
var CommandHelpTemplate = `NAME:
|
||||
{{template "helpNameTemplate" .}}
|
||||
|
||||
USAGE:
|
||||
{{template "usageTemplate" .}}{{if .Category}}
|
||||
|
||||
CATEGORY:
|
||||
{{.Category}}{{end}}{{if .Description}}
|
||||
|
||||
DESCRIPTION:
|
||||
{{template "descriptionTemplate" .}}{{end}}{{if .VisibleFlagCategories}}
|
||||
|
||||
OPTIONS:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}}
|
||||
|
||||
OPTIONS:{{template "visibleFlagTemplate" .}}{{end}}{{if .VisiblePersistentFlags}}
|
||||
|
||||
GLOBAL OPTIONS:{{template "visiblePersistentFlagTemplate" .}}{{end}}
|
||||
`
|
||||
|
||||
// SubcommandHelpTemplate is the text template for the subcommand help topic.
|
||||
// cli.go uses text/template to render templates. You can
|
||||
// render custom help text by setting this variable.
|
||||
var SubcommandHelpTemplate = `NAME:
|
||||
{{template "helpNameTemplate" .}}
|
||||
|
||||
USAGE:
|
||||
{{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.FullName}}{{if .VisibleCommands}} [command [command options]] {{end}}{{if .ArgsUsage}} {{.ArgsUsage}}{{else}}{{if .Arguments}} [arguments...]{{end}}{{end}}{{end}}{{if .Category}}
|
||||
|
||||
CATEGORY:
|
||||
{{.Category}}{{end}}{{if .Description}}
|
||||
|
||||
DESCRIPTION:
|
||||
{{template "descriptionTemplate" .}}{{end}}{{if .VisibleCommands}}
|
||||
|
||||
COMMANDS:{{template "visibleCommandTemplate" .}}{{end}}{{if .VisibleFlagCategories}}
|
||||
|
||||
OPTIONS:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}}
|
||||
|
||||
OPTIONS:{{template "visibleFlagTemplate" .}}{{end}}
|
||||
`
|
||||
|
||||
var FishCompletionTemplate = `# {{ .Command.Name }} fish shell completion
|
||||
|
||||
function __fish_{{ .Command.Name }}_no_subcommand --description 'Test if there has been any subcommand yet'
|
||||
for i in (commandline -opc)
|
||||
if contains -- $i{{ range $v := .AllCommands }} {{ $v }}{{ end }}
|
||||
return 1
|
||||
end
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
{{ range $v := .Completions }}{{ $v }}
|
||||
{{ end }}`
|
250
vendor/github.com/urfave/cli/v3/value_source.go
generated
vendored
Normal file
250
vendor/github.com/urfave/cli/v3/value_source.go
generated
vendored
Normal file
@ -0,0 +1,250 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ValueSource is a source which can be used to look up a value,
|
||||
// typically for use with a cli.Flag
|
||||
type ValueSource interface {
|
||||
fmt.Stringer
|
||||
fmt.GoStringer
|
||||
|
||||
// Lookup returns the value from the source and if it was found
|
||||
// or returns an empty string and false
|
||||
Lookup() (string, bool)
|
||||
}
|
||||
|
||||
// EnvValueSource is to specifically detect env sources when
|
||||
// printing help text
|
||||
type EnvValueSource interface {
|
||||
IsFromEnv() bool
|
||||
Key() string
|
||||
}
|
||||
|
||||
// MapSource is a source which can be used to look up a value
|
||||
// based on a key
|
||||
// typically for use with a cli.Flag
|
||||
type MapSource interface {
|
||||
fmt.Stringer
|
||||
fmt.GoStringer
|
||||
|
||||
// Lookup returns the value from the source based on key
|
||||
// and if it was found
|
||||
// or returns an empty string and false
|
||||
Lookup(string) (any, bool)
|
||||
}
|
||||
|
||||
// ValueSourceChain contains an ordered series of ValueSource that
|
||||
// allows for lookup where the first ValueSource to resolve is
|
||||
// returned
|
||||
type ValueSourceChain struct {
|
||||
Chain []ValueSource
|
||||
}
|
||||
|
||||
func NewValueSourceChain(src ...ValueSource) ValueSourceChain {
|
||||
return ValueSourceChain{
|
||||
Chain: src,
|
||||
}
|
||||
}
|
||||
|
||||
func (vsc *ValueSourceChain) Append(other ValueSourceChain) {
|
||||
vsc.Chain = append(vsc.Chain, other.Chain...)
|
||||
}
|
||||
|
||||
func (vsc *ValueSourceChain) EnvKeys() []string {
|
||||
vals := []string{}
|
||||
|
||||
for _, src := range vsc.Chain {
|
||||
if v, ok := src.(EnvValueSource); ok && v.IsFromEnv() {
|
||||
vals = append(vals, v.Key())
|
||||
}
|
||||
}
|
||||
|
||||
return vals
|
||||
}
|
||||
|
||||
func (vsc *ValueSourceChain) String() string {
|
||||
s := []string{}
|
||||
|
||||
for _, vs := range vsc.Chain {
|
||||
s = append(s, vs.String())
|
||||
}
|
||||
|
||||
return strings.Join(s, ",")
|
||||
}
|
||||
|
||||
func (vsc *ValueSourceChain) GoString() string {
|
||||
s := []string{}
|
||||
|
||||
for _, vs := range vsc.Chain {
|
||||
s = append(s, vs.GoString())
|
||||
}
|
||||
|
||||
return fmt.Sprintf("&ValueSourceChain{Chain:{%[1]s}}", strings.Join(s, ","))
|
||||
}
|
||||
|
||||
func (vsc *ValueSourceChain) Lookup() (string, bool) {
|
||||
s, _, ok := vsc.LookupWithSource()
|
||||
return s, ok
|
||||
}
|
||||
|
||||
func (vsc *ValueSourceChain) LookupWithSource() (string, ValueSource, bool) {
|
||||
for _, src := range vsc.Chain {
|
||||
if value, found := src.Lookup(); found {
|
||||
return value, src, true
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil, false
|
||||
}
|
||||
|
||||
// envVarValueSource encapsulates a ValueSource from an environment variable
|
||||
type envVarValueSource struct {
|
||||
key string
|
||||
}
|
||||
|
||||
func (e *envVarValueSource) Lookup() (string, bool) {
|
||||
return os.LookupEnv(strings.TrimSpace(string(e.key)))
|
||||
}
|
||||
|
||||
func (e *envVarValueSource) IsFromEnv() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (e *envVarValueSource) Key() string {
|
||||
return e.key
|
||||
}
|
||||
|
||||
func (e *envVarValueSource) String() string { return fmt.Sprintf("environment variable %[1]q", e.key) }
|
||||
func (e *envVarValueSource) GoString() string {
|
||||
return fmt.Sprintf("&envVarValueSource{Key:%[1]q}", e.key)
|
||||
}
|
||||
|
||||
func EnvVar(key string) ValueSource {
|
||||
return &envVarValueSource{
|
||||
key: key,
|
||||
}
|
||||
}
|
||||
|
||||
// EnvVars is a helper function to encapsulate a number of
|
||||
// envVarValueSource together as a ValueSourceChain
|
||||
func EnvVars(keys ...string) ValueSourceChain {
|
||||
vsc := ValueSourceChain{Chain: []ValueSource{}}
|
||||
|
||||
for _, key := range keys {
|
||||
vsc.Chain = append(vsc.Chain, EnvVar(key))
|
||||
}
|
||||
|
||||
return vsc
|
||||
}
|
||||
|
||||
// fileValueSource encapsulates a ValueSource from a file
|
||||
type fileValueSource struct {
|
||||
Path string
|
||||
}
|
||||
|
||||
func (f *fileValueSource) Lookup() (string, bool) {
|
||||
data, err := os.ReadFile(f.Path)
|
||||
return string(data), err == nil
|
||||
}
|
||||
|
||||
func (f *fileValueSource) String() string { return fmt.Sprintf("file %[1]q", f.Path) }
|
||||
func (f *fileValueSource) GoString() string {
|
||||
return fmt.Sprintf("&fileValueSource{Path:%[1]q}", f.Path)
|
||||
}
|
||||
|
||||
func File(path string) ValueSource {
|
||||
return &fileValueSource{Path: path}
|
||||
}
|
||||
|
||||
// Files is a helper function to encapsulate a number of
|
||||
// fileValueSource together as a ValueSourceChain
|
||||
func Files(paths ...string) ValueSourceChain {
|
||||
vsc := ValueSourceChain{Chain: []ValueSource{}}
|
||||
|
||||
for _, path := range paths {
|
||||
vsc.Chain = append(vsc.Chain, File(path))
|
||||
}
|
||||
|
||||
return vsc
|
||||
}
|
||||
|
||||
type mapSource struct {
|
||||
name string
|
||||
m map[any]any
|
||||
}
|
||||
|
||||
func NewMapSource(name string, m map[any]any) MapSource {
|
||||
return &mapSource{
|
||||
name: name,
|
||||
m: m,
|
||||
}
|
||||
}
|
||||
|
||||
func (ms *mapSource) String() string { return fmt.Sprintf("map source %[1]q", ms.name) }
|
||||
func (ms *mapSource) GoString() string {
|
||||
return fmt.Sprintf("&mapSource{name:%[1]q}", ms.name)
|
||||
}
|
||||
|
||||
func (ms *mapSource) Lookup(name string) (any, bool) {
|
||||
// nestedVal checks if the name has '.' delimiters.
|
||||
// If so, it tries to traverse the tree by the '.' delimited sections to find
|
||||
// a nested value for the key.
|
||||
if sections := strings.Split(name, "."); len(sections) > 1 {
|
||||
node := ms.m
|
||||
for _, section := range sections[:len(sections)-1] {
|
||||
child, ok := node[section]
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
switch child := child.(type) {
|
||||
case map[string]any:
|
||||
node = make(map[any]any, len(child))
|
||||
for k, v := range child {
|
||||
node[k] = v
|
||||
}
|
||||
case map[any]any:
|
||||
node = child
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
if val, ok := node[sections[len(sections)-1]]; ok {
|
||||
return val, true
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
type mapValueSource struct {
|
||||
key string
|
||||
ms MapSource
|
||||
}
|
||||
|
||||
func NewMapValueSource(key string, ms MapSource) ValueSource {
|
||||
return &mapValueSource{
|
||||
key: key,
|
||||
ms: ms,
|
||||
}
|
||||
}
|
||||
|
||||
func (mvs *mapValueSource) String() string {
|
||||
return fmt.Sprintf("key %[1]q from %[2]s", mvs.key, mvs.ms.String())
|
||||
}
|
||||
|
||||
func (mvs *mapValueSource) GoString() string {
|
||||
return fmt.Sprintf("&mapValueSource{key:%[1]q, src:%[2]s}", mvs.key, mvs.ms.GoString())
|
||||
}
|
||||
|
||||
func (mvs *mapValueSource) Lookup() (string, bool) {
|
||||
if v, ok := mvs.ms.Lookup(mvs.key); !ok {
|
||||
return "", false
|
||||
} else {
|
||||
return fmt.Sprintf("%+v", v), true
|
||||
}
|
||||
}
|
11
vendor/modules.txt
vendored
Normal file
11
vendor/modules.txt
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
# github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874
|
||||
## explicit; go 1.24
|
||||
github.com/go-json-experiment/json
|
||||
github.com/go-json-experiment/json/internal
|
||||
github.com/go-json-experiment/json/internal/jsonflags
|
||||
github.com/go-json-experiment/json/internal/jsonopts
|
||||
github.com/go-json-experiment/json/internal/jsonwire
|
||||
github.com/go-json-experiment/json/jsontext
|
||||
# github.com/urfave/cli/v3 v3.0.0-beta1
|
||||
## explicit; go 1.18
|
||||
github.com/urfave/cli/v3
|
Loading…
Reference in New Issue
Block a user