feat: gitea client

This commit is contained in:
2026-02-12 20:58:55 +01:00
parent 8583ab48ce
commit 9bd7d363ba
1693 changed files with 653995 additions and 49 deletions

3
vendor/github.com/42wim/httpsig/.travis.yml generated vendored Normal file
View File

@@ -0,0 +1,3 @@
language: go
go:
- 1.15

29
vendor/github.com/42wim/httpsig/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2018, go-fed
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 the copyright holder 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 HOLDER 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.

115
vendor/github.com/42wim/httpsig/README.md generated vendored Normal file
View File

@@ -0,0 +1,115 @@
# httpsig
Forked from <https://github.com/go-fed/httpsig>
> HTTP Signatures made simple
[![Build Status][Build-Status-Image]][Build-Status-Url] [![Go Reference][Go-Reference-Image]][Go-Reference-Url]
[![Go Report Card][Go-Report-Card-Image]][Go-Report-Card-Url] [![License][License-Image]][License-Url]
[![Chat][Chat-Image]][Chat-Url] [![OpenCollective][OpenCollective-Image]][OpenCollective-Url]
`go get github.com/42wim/httpsig`
Implementation of [HTTP Signatures](https://tools.ietf.org/html/draft-cavage-http-signatures).
Supports many different combinations of MAC, HMAC signing of hash, or RSA
signing of hash schemes. Its goals are:
* Have a very simple interface for signing and validating
* Support a variety of signing algorithms and combinations
* Support setting either headers (`Authorization` or `Signature`)
* Remaining flexible with headers included in the signing string
* Support both HTTP requests and responses
* Explicitly not support known-cryptographically weak algorithms
* Support automatic signing and validating Digest headers
## How to use
`import "github.com/42wim/httpsig"`
### Signing
Signing a request or response requires creating a new `Signer` and using it:
```
func sign(privateKey crypto.PrivateKey, pubKeyId string, r *http.Request) error {
prefs := []httpsig.Algorithm{httpsig.RSA_SHA512, httpsig.RSA_SHA256}
digestAlgorithm := DigestSha256
// The "Date" and "Digest" headers must already be set on r, as well as r.URL.
headersToSign := []string{httpsig.RequestTarget, "date", "digest"}
signer, chosenAlgo, err := httpsig.NewSigner(prefs, digestAlgorithm, headersToSign, httpsig.Signature)
if err != nil {
return err
}
// To sign the digest, we need to give the signer a copy of the body...
// ...but it is optional, no digest will be signed if given "nil"
body := ...
// If r were a http.ResponseWriter, call SignResponse instead.
return signer.SignRequest(privateKey, pubKeyId, r, body)
}
```
`Signer`s are not safe for concurrent use by goroutines, so be sure to guard
access:
```
type server struct {
signer httpsig.Signer
mu *sync.Mutex
}
func (s *server) handlerFunc(w http.ResponseWriter, r *http.Request) {
privateKey := ...
pubKeyId := ...
// Set headers and such on w
s.mu.Lock()
defer s.mu.Unlock()
// To sign the digest, we need to give the signer a copy of the response body...
// ...but it is optional, no digest will be signed if given "nil"
body := ...
err := s.signer.SignResponse(privateKey, pubKeyId, w, body)
if err != nil {
...
}
...
}
```
The `pubKeyId` will be used at verification time.
### Verifying
Verifying requires an application to use the `pubKeyId` to both retrieve the key
needed for verification as well as determine the algorithm to use. Use a
`Verifier`:
```
func verify(r *http.Request) error {
verifier, err := httpsig.NewVerifier(r)
if err != nil {
return err
}
pubKeyId := verifier.KeyId()
var algo httpsig.Algorithm = ...
var pubKey crypto.PublicKey = ...
// The verifier will verify the Digest in addition to the HTTP signature
return verifier.Verify(pubKey, algo)
}
```
`Verifier`s are not safe for concurrent use by goroutines, but since they are
constructed on a per-request or per-response basis it should not be a common
restriction.
[Build-Status-Image]: https://travis-ci.org/42wim/httpsig.svg?branch=master
[Build-Status-Url]: https://travis-ci.org/42wim/httpsig
[Go-Reference-Image]: https://pkg.go.dev/badge/github.com/42wim/httpsig
[Go-Reference-Url]: https://pkg.go.dev/github.com/42wim/httpsig
[Go-Report-Card-Image]: https://goreportcard.com/badge/github.com/42wim/httpsig
[Go-Report-Card-Url]: https://goreportcard.com/report/github.com/42wim/httpsig
[License-Image]: https://img.shields.io/github/license/42wim/httpsig?color=blue
[License-Url]: https://opensource.org/licenses/BSD-3-Clause
[Chat-Image]: https://img.shields.io/matrix/42wim:feneas.org?server_fqdn=matrix.org
[Chat-Url]: https://matrix.to/#/!BLOSvIyKTDLIVjRKSc:feneas.org?via=feneas.org&via=matrix.org
[OpenCollective-Image]: https://img.shields.io/opencollective/backers/42wim-activitypub-labs
[OpenCollective-Url]: https://opencollective.com/42wim-activitypub-labs

550
vendor/github.com/42wim/httpsig/algorithms.go generated vendored Normal file
View File

@@ -0,0 +1,550 @@
package httpsig
import (
"crypto"
"crypto/ecdsa"
"crypto/hmac"
"crypto/rsa"
"crypto/sha256"
"crypto/sha512"
"crypto/subtle" // Use should trigger great care
"encoding/asn1"
"errors"
"fmt"
"hash"
"io"
"math/big"
"strings"
"golang.org/x/crypto/blake2b"
"golang.org/x/crypto/blake2s"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/sha3"
"golang.org/x/crypto/ssh"
)
const (
hmacPrefix = "hmac"
rsaPrefix = "rsa"
sshPrefix = "ssh"
ecdsaPrefix = "ecdsa"
ed25519Prefix = "ed25519"
sha224String = "sha224"
sha256String = "sha256"
sha384String = "sha384"
sha512String = "sha512"
sha3_224String = "sha3-224"
sha3_256String = "sha3-256"
sha3_384String = "sha3-384"
sha3_512String = "sha3-512"
sha512_224String = "sha512-224"
sha512_256String = "sha512-256"
blake2s_256String = "blake2s-256"
blake2b_256String = "blake2b-256"
blake2b_384String = "blake2b-384"
blake2b_512String = "blake2b-512"
)
var blake2Algorithms = map[crypto.Hash]bool{
crypto.BLAKE2s_256: true,
crypto.BLAKE2b_256: true,
crypto.BLAKE2b_384: true,
crypto.BLAKE2b_512: true,
}
var hashToDef = map[crypto.Hash]struct {
name string
new func(key []byte) (hash.Hash, error) // Only MACers will accept a key
}{
// Which standard names these?
// The spec lists the following as a canonical reference, which is dead:
// http://www.iana.org/assignments/signature-algorithms
//
// Note that the forbidden hashes have an invalid 'new' function.
crypto.SHA224: {sha224String, func(key []byte) (hash.Hash, error) { return sha256.New224(), nil }},
crypto.SHA256: {sha256String, func(key []byte) (hash.Hash, error) { return sha256.New(), nil }},
crypto.SHA384: {sha384String, func(key []byte) (hash.Hash, error) { return sha512.New384(), nil }},
crypto.SHA512: {sha512String, func(key []byte) (hash.Hash, error) { return sha512.New(), nil }},
crypto.SHA3_224: {sha3_224String, func(key []byte) (hash.Hash, error) { return sha3.New224(), nil }},
crypto.SHA3_256: {sha3_256String, func(key []byte) (hash.Hash, error) { return sha3.New256(), nil }},
crypto.SHA3_384: {sha3_384String, func(key []byte) (hash.Hash, error) { return sha3.New384(), nil }},
crypto.SHA3_512: {sha3_512String, func(key []byte) (hash.Hash, error) { return sha3.New512(), nil }},
crypto.SHA512_224: {sha512_224String, func(key []byte) (hash.Hash, error) { return sha512.New512_224(), nil }},
crypto.SHA512_256: {sha512_256String, func(key []byte) (hash.Hash, error) { return sha512.New512_256(), nil }},
crypto.BLAKE2s_256: {blake2s_256String, func(key []byte) (hash.Hash, error) { return blake2s.New256(key) }},
crypto.BLAKE2b_256: {blake2b_256String, func(key []byte) (hash.Hash, error) { return blake2b.New256(key) }},
crypto.BLAKE2b_384: {blake2b_384String, func(key []byte) (hash.Hash, error) { return blake2b.New384(key) }},
crypto.BLAKE2b_512: {blake2b_512String, func(key []byte) (hash.Hash, error) { return blake2b.New512(key) }},
}
var stringToHash map[string]crypto.Hash
const (
defaultAlgorithm = RSA_SHA256
defaultAlgorithmHashing = sha256String
)
func init() {
stringToHash = make(map[string]crypto.Hash, len(hashToDef))
for k, v := range hashToDef {
stringToHash[v.name] = k
}
// This should guarantee that at runtime the defaultAlgorithm will not
// result in errors when fetching a macer or signer (see algorithms.go)
if ok, err := isAvailable(string(defaultAlgorithmHashing)); err != nil {
panic(err)
} else if !ok {
panic(fmt.Sprintf("the default httpsig algorithm is unavailable: %q", defaultAlgorithm))
}
}
func isForbiddenHash(h crypto.Hash) bool {
return false
}
// signer is an internally public type.
type signer interface {
Sign(rand io.Reader, p crypto.PrivateKey, sig []byte) ([]byte, error)
Verify(pub crypto.PublicKey, toHash, signature []byte) error
String() string
}
// macer is an internally public type.
type macer interface {
Sign(sig, key []byte) ([]byte, error)
Equal(sig, actualMAC, key []byte) (bool, error)
String() string
}
var _ macer = &hmacAlgorithm{}
type hmacAlgorithm struct {
fn func(key []byte) (hash.Hash, error)
kind crypto.Hash
}
func (h *hmacAlgorithm) Sign(sig, key []byte) ([]byte, error) {
hs, err := h.fn(key)
if err != nil {
return nil, err
}
if err = setSig(hs, sig); err != nil {
return nil, err
}
return hs.Sum(nil), nil
}
func (h *hmacAlgorithm) Equal(sig, actualMAC, key []byte) (bool, error) {
hs, err := h.fn(key)
if err != nil {
return false, err
}
defer hs.Reset()
err = setSig(hs, sig)
if err != nil {
return false, err
}
expected := hs.Sum(nil)
return hmac.Equal(actualMAC, expected), nil
}
func (h *hmacAlgorithm) String() string {
return fmt.Sprintf("%s-%s", hmacPrefix, hashToDef[h.kind].name)
}
var _ signer = &rsaAlgorithm{}
type rsaAlgorithm struct {
hash.Hash
kind crypto.Hash
sshSigner ssh.Signer
}
func (r *rsaAlgorithm) setSig(b []byte) error {
n, err := r.Write(b)
if err != nil {
r.Reset()
return err
} else if n != len(b) {
r.Reset()
return fmt.Errorf("could only write %d of %d bytes of signature to hash", n, len(b))
}
return nil
}
// Code from https://github.com/cloudtools/ssh-cert-authority/pull/49/files
// This interface provides a way to reach the exported, but not accessible SignWithOpts() method
// in x/crypto/ssh/agent. Access to this is needed to sign with more secure signing algorithms
type agentKeyringSigner interface {
SignWithOpts(rand io.Reader, data []byte, opts crypto.SignerOpts) (*ssh.Signature, error)
}
// A struct to wrap an SSH Signer with one that will switch to SHA256 Signatures.
// Replaces the call to Sign() with a call to SignWithOpts using HashFunc() algorithm.
type Sha256Signer struct {
ssh.Signer
}
func (s Sha256Signer) HashFunc() crypto.Hash {
return crypto.SHA256
}
func (s Sha256Signer) Sign(rand io.Reader, data []byte) (*ssh.Signature, error) {
if aks, ok := s.Signer.(agentKeyringSigner); !ok {
return nil, fmt.Errorf("ssh: can't wrap a non ssh agentKeyringSigner")
} else {
return aks.SignWithOpts(rand, data, s)
}
}
func (r *rsaAlgorithm) Sign(rand io.Reader, p crypto.PrivateKey, sig []byte) ([]byte, error) {
if r.sshSigner != nil {
var (
sshsig *ssh.Signature
err error
)
// are we using an SSH Agent
if _, ok := r.sshSigner.(agentKeyringSigner); ok {
signer := Sha256Signer{r.sshSigner}
sshsig, err = signer.Sign(rand, sig)
if err != nil {
return nil, err
}
} else {
sshsig, err = r.sshSigner.(ssh.AlgorithmSigner).SignWithAlgorithm(rand, sig, ssh.SigAlgoRSASHA2256)
if err != nil {
return nil, err
}
}
return sshsig.Blob, nil
}
defer r.Reset()
if err := r.setSig(sig); err != nil {
return nil, err
}
rsaK, ok := p.(*rsa.PrivateKey)
if !ok {
return nil, errors.New("crypto.PrivateKey is not *rsa.PrivateKey")
}
return rsa.SignPKCS1v15(rand, rsaK, r.kind, r.Sum(nil))
}
func (r *rsaAlgorithm) Verify(pub crypto.PublicKey, toHash, signature []byte) error {
defer r.Reset()
rsaK, ok := pub.(*rsa.PublicKey)
if !ok {
return errors.New("crypto.PublicKey is not *rsa.PublicKey")
}
if err := r.setSig(toHash); err != nil {
return err
}
return rsa.VerifyPKCS1v15(rsaK, r.kind, r.Sum(nil), signature)
}
func (r *rsaAlgorithm) String() string {
return fmt.Sprintf("%s-%s", rsaPrefix, hashToDef[r.kind].name)
}
var _ signer = &ed25519Algorithm{}
type ed25519Algorithm struct {
sshSigner ssh.Signer
}
func (r *ed25519Algorithm) Sign(rand io.Reader, p crypto.PrivateKey, sig []byte) ([]byte, error) {
if r.sshSigner != nil {
sshsig, err := r.sshSigner.Sign(rand, sig)
if err != nil {
return nil, err
}
return sshsig.Blob, nil
}
ed25519K, ok := p.(ed25519.PrivateKey)
if !ok {
return nil, errors.New("crypto.PrivateKey is not ed25519.PrivateKey")
}
return ed25519.Sign(ed25519K, sig), nil
}
func (r *ed25519Algorithm) Verify(pub crypto.PublicKey, toHash, signature []byte) error {
ed25519K, ok := pub.(ed25519.PublicKey)
if !ok {
return errors.New("crypto.PublicKey is not ed25519.PublicKey")
}
if ed25519.Verify(ed25519K, toHash, signature) {
return nil
}
return errors.New("ed25519 verify failed")
}
func (r *ed25519Algorithm) String() string {
return fmt.Sprintf("%s", ed25519Prefix)
}
var _ signer = &ecdsaAlgorithm{}
type ecdsaAlgorithm struct {
hash.Hash
kind crypto.Hash
}
func (r *ecdsaAlgorithm) setSig(b []byte) error {
n, err := r.Write(b)
if err != nil {
r.Reset()
return err
} else if n != len(b) {
r.Reset()
return fmt.Errorf("could only write %d of %d bytes of signature to hash", n, len(b))
}
return nil
}
type ECDSASignature struct {
R, S *big.Int
}
func (r *ecdsaAlgorithm) Sign(rand io.Reader, p crypto.PrivateKey, sig []byte) ([]byte, error) {
defer r.Reset()
if err := r.setSig(sig); err != nil {
return nil, err
}
ecdsaK, ok := p.(*ecdsa.PrivateKey)
if !ok {
return nil, errors.New("crypto.PrivateKey is not *ecdsa.PrivateKey")
}
R, S, err := ecdsa.Sign(rand, ecdsaK, r.Sum(nil))
if err != nil {
return nil, err
}
signature := ECDSASignature{R: R, S: S}
bytes, err := asn1.Marshal(signature)
return bytes, err
}
func (r *ecdsaAlgorithm) Verify(pub crypto.PublicKey, toHash, signature []byte) error {
defer r.Reset()
ecdsaK, ok := pub.(*ecdsa.PublicKey)
if !ok {
return errors.New("crypto.PublicKey is not *ecdsa.PublicKey")
}
if err := r.setSig(toHash); err != nil {
return err
}
sig := new(ECDSASignature)
_, err := asn1.Unmarshal(signature, sig)
if err != nil {
return err
}
if ecdsa.Verify(ecdsaK, r.Sum(nil), sig.R, sig.S) {
return nil
} else {
return errors.New("Invalid signature")
}
}
func (r *ecdsaAlgorithm) String() string {
return fmt.Sprintf("%s-%s", ecdsaPrefix, hashToDef[r.kind].name)
}
var _ macer = &blakeMacAlgorithm{}
type blakeMacAlgorithm struct {
fn func(key []byte) (hash.Hash, error)
kind crypto.Hash
}
func (r *blakeMacAlgorithm) Sign(sig, key []byte) ([]byte, error) {
hs, err := r.fn(key)
if err != nil {
return nil, err
}
if err = setSig(hs, sig); err != nil {
return nil, err
}
return hs.Sum(nil), nil
}
func (r *blakeMacAlgorithm) Equal(sig, actualMAC, key []byte) (bool, error) {
hs, err := r.fn(key)
if err != nil {
return false, err
}
defer hs.Reset()
err = setSig(hs, sig)
if err != nil {
return false, err
}
expected := hs.Sum(nil)
return subtle.ConstantTimeCompare(actualMAC, expected) == 1, nil
}
func (r *blakeMacAlgorithm) String() string {
return fmt.Sprintf("%s", hashToDef[r.kind].name)
}
func setSig(a hash.Hash, b []byte) error {
n, err := a.Write(b)
if err != nil {
a.Reset()
return err
} else if n != len(b) {
a.Reset()
return fmt.Errorf("could only write %d of %d bytes of signature to hash", n, len(b))
}
return nil
}
// IsSupportedHttpSigAlgorithm returns true if the string is supported by this
// library, is not a hash known to be weak, and is supported by the hardware.
func IsSupportedHttpSigAlgorithm(algo string) bool {
a, err := isAvailable(algo)
return a && err == nil
}
// isAvailable is an internally public function
func isAvailable(algo string) (bool, error) {
c, ok := stringToHash[algo]
if !ok {
return false, fmt.Errorf("no match for %q", algo)
}
if isForbiddenHash(c) {
return false, fmt.Errorf("forbidden hash type in %q", algo)
}
return c.Available(), nil
}
func newAlgorithmConstructor(algo string) (fn func(k []byte) (hash.Hash, error), c crypto.Hash, e error) {
ok := false
c, ok = stringToHash[algo]
if !ok {
e = fmt.Errorf("no match for %q", algo)
return
}
if isForbiddenHash(c) {
e = fmt.Errorf("forbidden hash type in %q", algo)
return
}
algoDef, ok := hashToDef[c]
if !ok {
e = fmt.Errorf("have crypto.Hash %v but no definition", c)
return
}
fn = func(key []byte) (hash.Hash, error) {
h, err := algoDef.new(key)
if err != nil {
return nil, err
}
return h, nil
}
return
}
func newAlgorithm(algo string, key []byte) (hash.Hash, crypto.Hash, error) {
fn, c, err := newAlgorithmConstructor(algo)
if err != nil {
return nil, c, err
}
h, err := fn(key)
return h, c, err
}
func signerFromSSHSigner(sshSigner ssh.Signer, s string) (signer, error) {
switch {
case strings.HasPrefix(s, rsaPrefix):
return &rsaAlgorithm{
sshSigner: sshSigner,
}, nil
case strings.HasPrefix(s, ed25519Prefix):
return &ed25519Algorithm{
sshSigner: sshSigner,
}, nil
default:
return nil, fmt.Errorf("no signer matching %q", s)
}
}
// signerFromString is an internally public method constructor
func signerFromString(s string) (signer, error) {
s = strings.ToLower(s)
isEcdsa := false
isEd25519 := false
var algo string = ""
if strings.HasPrefix(s, ecdsaPrefix) {
algo = strings.TrimPrefix(s, ecdsaPrefix+"-")
isEcdsa = true
} else if strings.HasPrefix(s, rsaPrefix) {
algo = strings.TrimPrefix(s, rsaPrefix+"-")
} else if strings.HasPrefix(s, ed25519Prefix) {
isEd25519 = true
algo = "sha512"
} else {
return nil, fmt.Errorf("no signer matching %q", s)
}
hash, cHash, err := newAlgorithm(algo, nil)
if err != nil {
return nil, err
}
if isEd25519 {
return &ed25519Algorithm{}, nil
}
if isEcdsa {
return &ecdsaAlgorithm{
Hash: hash,
kind: cHash,
}, nil
}
return &rsaAlgorithm{
Hash: hash,
kind: cHash,
}, nil
}
// macerFromString is an internally public method constructor
func macerFromString(s string) (macer, error) {
s = strings.ToLower(s)
if strings.HasPrefix(s, hmacPrefix) {
algo := strings.TrimPrefix(s, hmacPrefix+"-")
hashFn, cHash, err := newAlgorithmConstructor(algo)
if err != nil {
return nil, err
}
// Ensure below does not panic
_, err = hashFn(nil)
if err != nil {
return nil, err
}
return &hmacAlgorithm{
fn: func(key []byte) (hash.Hash, error) {
return hmac.New(func() hash.Hash {
h, e := hashFn(nil)
if e != nil {
panic(e)
}
return h
}, key), nil
},
kind: cHash,
}, nil
} else if bl, ok := stringToHash[s]; ok && blake2Algorithms[bl] {
hashFn, cHash, err := newAlgorithmConstructor(s)
if err != nil {
return nil, err
}
return &blakeMacAlgorithm{
fn: hashFn,
kind: cHash,
}, nil
} else {
return nil, fmt.Errorf("no MACer matching %q", s)
}
}

120
vendor/github.com/42wim/httpsig/digest.go generated vendored Normal file
View File

@@ -0,0 +1,120 @@
package httpsig
import (
"bytes"
"crypto"
"encoding/base64"
"fmt"
"hash"
"net/http"
"strings"
)
type DigestAlgorithm string
const (
DigestSha256 DigestAlgorithm = "SHA-256"
DigestSha512 = "SHA-512"
)
var digestToDef = map[DigestAlgorithm]crypto.Hash{
DigestSha256: crypto.SHA256,
DigestSha512: crypto.SHA512,
}
// IsSupportedDigestAlgorithm returns true if hte string is supported by this
// library, is not a hash known to be weak, and is supported by the hardware.
func IsSupportedDigestAlgorithm(algo string) bool {
uc := DigestAlgorithm(strings.ToUpper(algo))
c, ok := digestToDef[uc]
return ok && c.Available()
}
func getHash(alg DigestAlgorithm) (h hash.Hash, toUse DigestAlgorithm, err error) {
upper := DigestAlgorithm(strings.ToUpper(string(alg)))
c, ok := digestToDef[upper]
if !ok {
err = fmt.Errorf("unknown or unsupported Digest algorithm: %s", alg)
} else if !c.Available() {
err = fmt.Errorf("unavailable Digest algorithm: %s", alg)
} else {
h = c.New()
toUse = upper
}
return
}
const (
digestHeader = "Digest"
digestDelim = "="
)
func addDigest(r *http.Request, algo DigestAlgorithm, b []byte) (err error) {
_, ok := r.Header[digestHeader]
if ok {
err = fmt.Errorf("cannot add Digest: Digest is already set")
return
}
var h hash.Hash
var a DigestAlgorithm
h, a, err = getHash(algo)
if err != nil {
return
}
h.Write(b)
sum := h.Sum(nil)
r.Header.Add(digestHeader,
fmt.Sprintf("%s%s%s",
a,
digestDelim,
base64.StdEncoding.EncodeToString(sum[:])))
return
}
func addDigestResponse(r http.ResponseWriter, algo DigestAlgorithm, b []byte) (err error) {
_, ok := r.Header()[digestHeader]
if ok {
err = fmt.Errorf("cannot add Digest: Digest is already set")
return
}
var h hash.Hash
var a DigestAlgorithm
h, a, err = getHash(algo)
if err != nil {
return
}
h.Write(b)
sum := h.Sum(nil)
r.Header().Add(digestHeader,
fmt.Sprintf("%s%s%s",
a,
digestDelim,
base64.StdEncoding.EncodeToString(sum[:])))
return
}
func verifyDigest(r *http.Request, body *bytes.Buffer) (err error) {
d := r.Header.Get(digestHeader)
if len(d) == 0 {
err = fmt.Errorf("cannot verify Digest: request has no Digest header")
return
}
elem := strings.SplitN(d, digestDelim, 2)
if len(elem) != 2 {
err = fmt.Errorf("cannot verify Digest: malformed Digest: %s", d)
return
}
var h hash.Hash
h, _, err = getHash(DigestAlgorithm(elem[0]))
if err != nil {
return
}
h.Write(body.Bytes())
sum := h.Sum(nil)
encSum := base64.StdEncoding.EncodeToString(sum[:])
if encSum != elem[1] {
err = fmt.Errorf("cannot verify Digest: header Digest does not match the digest of the request body")
return
}
return
}

356
vendor/github.com/42wim/httpsig/httpsig.go generated vendored Normal file
View File

@@ -0,0 +1,356 @@
// Implements HTTP request and response signing and verification. Supports the
// major MAC and asymmetric key signature algorithms. It has several safety
// restrictions: One, none of the widely known non-cryptographically safe
// algorithms are permitted; Two, the RSA SHA256 algorithms must be available in
// the binary (and it should, barring export restrictions); Finally, the library
// assumes either the 'Authorizationn' or 'Signature' headers are to be set (but
// not both).
package httpsig
import (
"crypto"
"fmt"
"net/http"
"strings"
"time"
"golang.org/x/crypto/ssh"
)
// Algorithm specifies a cryptography secure algorithm for signing HTTP requests
// and responses.
type Algorithm string
const (
// MAC-based algoirthms.
HMAC_SHA224 Algorithm = hmacPrefix + "-" + sha224String
HMAC_SHA256 Algorithm = hmacPrefix + "-" + sha256String
HMAC_SHA384 Algorithm = hmacPrefix + "-" + sha384String
HMAC_SHA512 Algorithm = hmacPrefix + "-" + sha512String
HMAC_SHA3_224 Algorithm = hmacPrefix + "-" + sha3_224String
HMAC_SHA3_256 Algorithm = hmacPrefix + "-" + sha3_256String
HMAC_SHA3_384 Algorithm = hmacPrefix + "-" + sha3_384String
HMAC_SHA3_512 Algorithm = hmacPrefix + "-" + sha3_512String
HMAC_SHA512_224 Algorithm = hmacPrefix + "-" + sha512_224String
HMAC_SHA512_256 Algorithm = hmacPrefix + "-" + sha512_256String
HMAC_BLAKE2S_256 Algorithm = hmacPrefix + "-" + blake2s_256String
HMAC_BLAKE2B_256 Algorithm = hmacPrefix + "-" + blake2b_256String
HMAC_BLAKE2B_384 Algorithm = hmacPrefix + "-" + blake2b_384String
HMAC_BLAKE2B_512 Algorithm = hmacPrefix + "-" + blake2b_512String
BLAKE2S_256 Algorithm = blake2s_256String
BLAKE2B_256 Algorithm = blake2b_256String
BLAKE2B_384 Algorithm = blake2b_384String
BLAKE2B_512 Algorithm = blake2b_512String
// RSA-based algorithms.
RSA_SHA224 Algorithm = rsaPrefix + "-" + sha224String
// RSA_SHA256 is the default algorithm.
RSA_SHA256 Algorithm = rsaPrefix + "-" + sha256String
RSA_SHA384 Algorithm = rsaPrefix + "-" + sha384String
RSA_SHA512 Algorithm = rsaPrefix + "-" + sha512String
// ECDSA algorithms
ECDSA_SHA224 Algorithm = ecdsaPrefix + "-" + sha224String
ECDSA_SHA256 Algorithm = ecdsaPrefix + "-" + sha256String
ECDSA_SHA384 Algorithm = ecdsaPrefix + "-" + sha384String
ECDSA_SHA512 Algorithm = ecdsaPrefix + "-" + sha512String
// ED25519 algorithms
// can only be SHA512
ED25519 Algorithm = ed25519Prefix
// Just because you can glue things together, doesn't mean they will
// work. The following options are not supported.
rsa_SHA3_224 Algorithm = rsaPrefix + "-" + sha3_224String
rsa_SHA3_256 Algorithm = rsaPrefix + "-" + sha3_256String
rsa_SHA3_384 Algorithm = rsaPrefix + "-" + sha3_384String
rsa_SHA3_512 Algorithm = rsaPrefix + "-" + sha3_512String
rsa_SHA512_224 Algorithm = rsaPrefix + "-" + sha512_224String
rsa_SHA512_256 Algorithm = rsaPrefix + "-" + sha512_256String
rsa_BLAKE2S_256 Algorithm = rsaPrefix + "-" + blake2s_256String
rsa_BLAKE2B_256 Algorithm = rsaPrefix + "-" + blake2b_256String
rsa_BLAKE2B_384 Algorithm = rsaPrefix + "-" + blake2b_384String
rsa_BLAKE2B_512 Algorithm = rsaPrefix + "-" + blake2b_512String
)
// HTTP Signatures can be applied to different HTTP headers, depending on the
// expected application behavior.
type SignatureScheme string
const (
// Signature will place the HTTP Signature into the 'Signature' HTTP
// header.
Signature SignatureScheme = "Signature"
// Authorization will place the HTTP Signature into the 'Authorization'
// HTTP header.
Authorization SignatureScheme = "Authorization"
)
const (
// The HTTP Signatures specification uses the "Signature" auth-scheme
// for the Authorization header. This is coincidentally named, but not
// semantically the same, as the "Signature" HTTP header value.
signatureAuthScheme = "Signature"
)
// There are subtle differences to the values in the header. The Authorization
// header has an 'auth-scheme' value that must be prefixed to the rest of the
// key and values.
func (s SignatureScheme) authScheme() string {
switch s {
case Authorization:
return signatureAuthScheme
default:
return ""
}
}
// Signers will sign HTTP requests or responses based on the algorithms and
// headers selected at creation time.
//
// Signers are not safe to use between multiple goroutines.
//
// Note that signatures do set the deprecated 'algorithm' parameter for
// backwards compatibility.
type Signer interface {
// SignRequest signs the request using a private key. The public key id
// is used by the HTTP server to identify which key to use to verify the
// signature.
//
// If the Signer was created using a MAC based algorithm, then the key
// is expected to be of type []byte. If the Signer was created using an
// RSA based algorithm, then the private key is expected to be of type
// *rsa.PrivateKey.
//
// A Digest (RFC 3230) will be added to the request. The body provided
// must match the body used in the request, and is allowed to be nil.
// The Digest ensures the request body is not tampered with in flight,
// and if the signer is created to also sign the "Digest" header, the
// HTTP Signature will then ensure both the Digest and body are not both
// modified to maliciously represent different content.
SignRequest(pKey crypto.PrivateKey, pubKeyId string, r *http.Request, body []byte) error
// SignResponse signs the response using a private key. The public key
// id is used by the HTTP client to identify which key to use to verify
// the signature.
//
// If the Signer was created using a MAC based algorithm, then the key
// is expected to be of type []byte. If the Signer was created using an
// RSA based algorithm, then the private key is expected to be of type
// *rsa.PrivateKey.
//
// A Digest (RFC 3230) will be added to the response. The body provided
// must match the body written in the response, and is allowed to be
// nil. The Digest ensures the response body is not tampered with in
// flight, and if the signer is created to also sign the "Digest"
// header, the HTTP Signature will then ensure both the Digest and body
// are not both modified to maliciously represent different content.
SignResponse(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter, body []byte) error
}
// NewSigner creates a new Signer with the provided algorithm preferences to
// make HTTP signatures. Only the first available algorithm will be used, which
// is returned by this function along with the Signer. If none of the preferred
// algorithms were available, then the default algorithm is used. The headers
// specified will be included into the HTTP signatures.
//
// The Digest will also be calculated on a request's body using the provided
// digest algorithm, if "Digest" is one of the headers listed.
//
// The provided scheme determines which header is populated with the HTTP
// Signature.
//
// An error is returned if an unknown or a known cryptographically insecure
// Algorithm is provided.
func NewSigner(prefs []Algorithm, dAlgo DigestAlgorithm, headers []string, scheme SignatureScheme, expiresIn int64) (Signer, Algorithm, error) {
for _, pref := range prefs {
s, err := newSigner(pref, dAlgo, headers, scheme, expiresIn)
if err != nil {
continue
}
return s, pref, err
}
s, err := newSigner(defaultAlgorithm, dAlgo, headers, scheme, expiresIn)
return s, defaultAlgorithm, err
}
// Signers will sign HTTP requests or responses based on the algorithms and
// headers selected at creation time.
//
// Signers are not safe to use between multiple goroutines.
//
// Note that signatures do set the deprecated 'algorithm' parameter for
// backwards compatibility.
type SSHSigner interface {
// SignRequest signs the request using ssh.Signer.
// The public key id is used by the HTTP server to identify which key to use
// to verify the signature.
//
// A Digest (RFC 3230) will be added to the request. The body provided
// must match the body used in the request, and is allowed to be nil.
// The Digest ensures the request body is not tampered with in flight,
// and if the signer is created to also sign the "Digest" header, the
// HTTP Signature will then ensure both the Digest and body are not both
// modified to maliciously represent different content.
SignRequest(pubKeyId string, r *http.Request, body []byte) error
// SignResponse signs the response using ssh.Signer. The public key
// id is used by the HTTP client to identify which key to use to verify
// the signature.
//
// A Digest (RFC 3230) will be added to the response. The body provided
// must match the body written in the response, and is allowed to be
// nil. The Digest ensures the response body is not tampered with in
// flight, and if the signer is created to also sign the "Digest"
// header, the HTTP Signature will then ensure both the Digest and body
// are not both modified to maliciously represent different content.
SignResponse(pubKeyId string, r http.ResponseWriter, body []byte) error
}
// NewwSSHSigner creates a new Signer using the specified ssh.Signer
// At the moment only ed25519 ssh keys are supported.
// The headers specified will be included into the HTTP signatures.
//
// The Digest will also be calculated on a request's body using the provided
// digest algorithm, if "Digest" is one of the headers listed.
//
// The provided scheme determines which header is populated with the HTTP
// Signature.
func NewSSHSigner(s ssh.Signer, dAlgo DigestAlgorithm, headers []string, scheme SignatureScheme, expiresIn int64) (SSHSigner, Algorithm, error) {
sshAlgo := getSSHAlgorithm(s.PublicKey().Type())
if sshAlgo == "" {
return nil, "", fmt.Errorf("key type: %s not supported yet.", s.PublicKey().Type())
}
signer, err := newSSHSigner(s, sshAlgo, dAlgo, headers, scheme, expiresIn)
if err != nil {
return nil, "", err
}
return signer, sshAlgo, nil
}
func getSSHAlgorithm(pkType string) Algorithm {
switch {
case strings.HasPrefix(pkType, sshPrefix+"-"+ed25519Prefix):
return ED25519
case strings.HasPrefix(pkType, sshPrefix+"-"+rsaPrefix):
return RSA_SHA256
}
return ""
}
// Verifier verifies HTTP Signatures.
//
// It will determine which of the supported headers has the parameters
// that define the signature.
//
// Verifiers are not safe to use between multiple goroutines.
//
// Note that verification ignores the deprecated 'algorithm' parameter.
type Verifier interface {
// KeyId gets the public key id that the signature is signed with.
//
// Note that the application is expected to determine the algorithm
// used based on metadata or out-of-band information for this key id.
KeyId() string
// Verify accepts the public key specified by KeyId and returns an
// error if verification fails or if the signature is malformed. The
// algorithm must be the one used to create the signature in order to
// pass verification. The algorithm is determined based on metadata or
// out-of-band information for the key id.
//
// If the signature was created using a MAC based algorithm, then the
// key is expected to be of type []byte. If the signature was created
// using an RSA based algorithm, then the public key is expected to be
// of type *rsa.PublicKey.
Verify(pKey crypto.PublicKey, algo Algorithm) error
}
const (
// host is treated specially because golang may not include it in the
// request header map on the server side of a request.
hostHeader = "Host"
)
// NewVerifier verifies the given request. It returns an error if the HTTP
// Signature parameters are not present in any headers, are present in more than
// one header, are malformed, or are missing required parameters. It ignores
// unknown HTTP Signature parameters.
func NewVerifier(r *http.Request) (Verifier, error) {
h := r.Header
if _, hasHostHeader := h[hostHeader]; len(r.Host) > 0 && !hasHostHeader {
h[hostHeader] = []string{r.Host}
}
return newVerifier(h, func(h http.Header, toInclude []string, created int64, expires int64) (string, error) {
return signatureString(h, toInclude, addRequestTarget(r), created, expires)
})
}
// NewResponseVerifier verifies the given response. It returns errors under the
// same conditions as NewVerifier.
func NewResponseVerifier(r *http.Response) (Verifier, error) {
return newVerifier(r.Header, func(h http.Header, toInclude []string, created int64, expires int64) (string, error) {
return signatureString(h, toInclude, requestTargetNotPermitted, created, expires)
})
}
func newSSHSigner(sshSigner ssh.Signer, algo Algorithm, dAlgo DigestAlgorithm, headers []string, scheme SignatureScheme, expiresIn int64) (SSHSigner, error) {
var expires, created int64 = 0, 0
if expiresIn != 0 {
created = time.Now().Unix()
expires = created + expiresIn
}
s, err := signerFromSSHSigner(sshSigner, string(algo))
if err != nil {
return nil, fmt.Errorf("no crypto implementation available for ssh algo %q: %s", algo, err)
}
a := &asymmSSHSigner{
asymmSigner: &asymmSigner{
s: s,
dAlgo: dAlgo,
headers: headers,
targetHeader: scheme,
prefix: scheme.authScheme(),
created: created,
expires: expires,
},
}
return a, nil
}
func newSigner(algo Algorithm, dAlgo DigestAlgorithm, headers []string, scheme SignatureScheme, expiresIn int64) (Signer, error) {
var expires, created int64 = 0, 0
if expiresIn != 0 {
created = time.Now().Unix()
expires = created + expiresIn
}
s, err := signerFromString(string(algo))
if err == nil {
a := &asymmSigner{
s: s,
dAlgo: dAlgo,
headers: headers,
targetHeader: scheme,
prefix: scheme.authScheme(),
created: created,
expires: expires,
}
return a, nil
}
m, err := macerFromString(string(algo))
if err != nil {
return nil, fmt.Errorf("no crypto implementation available for %q: %s", algo, err)
}
c := &macSigner{
m: m,
dAlgo: dAlgo,
headers: headers,
targetHeader: scheme,
prefix: scheme.authScheme(),
created: created,
expires: expires,
}
return c, nil
}

334
vendor/github.com/42wim/httpsig/signing.go generated vendored Normal file
View File

@@ -0,0 +1,334 @@
package httpsig
import (
"bytes"
"crypto"
"crypto/rand"
"encoding/base64"
"fmt"
"net/http"
"net/textproto"
"strconv"
"strings"
)
const (
// Signature Parameters
keyIdParameter = "keyId"
algorithmParameter = "algorithm"
headersParameter = "headers"
signatureParameter = "signature"
prefixSeparater = " "
parameterKVSeparater = "="
parameterValueDelimiter = "\""
parameterSeparater = ","
headerParameterValueDelim = " "
// RequestTarget specifies to include the http request method and
// entire URI in the signature. Pass it as a header to NewSigner.
RequestTarget = "(request-target)"
createdKey = "created"
expiresKey = "expires"
dateHeader = "date"
// Signature String Construction
headerFieldDelimiter = ": "
headersDelimiter = "\n"
headerValueDelimiter = ", "
requestTargetSeparator = " "
)
var defaultHeaders = []string{dateHeader}
var _ Signer = &macSigner{}
type macSigner struct {
m macer
makeDigest bool
dAlgo DigestAlgorithm
headers []string
targetHeader SignatureScheme
prefix string
created int64
expires int64
}
func (m *macSigner) SignRequest(pKey crypto.PrivateKey, pubKeyId string, r *http.Request, body []byte) error {
if body != nil {
err := addDigest(r, m.dAlgo, body)
if err != nil {
return err
}
}
s, err := m.signatureString(r)
if err != nil {
return err
}
enc, err := m.signSignature(pKey, s)
if err != nil {
return err
}
setSignatureHeader(r.Header, string(m.targetHeader), m.prefix, pubKeyId, m.m.String(), enc, m.headers, m.created, m.expires)
return nil
}
func (m *macSigner) SignResponse(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter, body []byte) error {
if body != nil {
err := addDigestResponse(r, m.dAlgo, body)
if err != nil {
return err
}
}
s, err := m.signatureStringResponse(r)
if err != nil {
return err
}
enc, err := m.signSignature(pKey, s)
if err != nil {
return err
}
setSignatureHeader(r.Header(), string(m.targetHeader), m.prefix, pubKeyId, m.m.String(), enc, m.headers, m.created, m.expires)
return nil
}
func (m *macSigner) signSignature(pKey crypto.PrivateKey, s string) (string, error) {
pKeyBytes, ok := pKey.([]byte)
if !ok {
return "", fmt.Errorf("private key for MAC signing must be of type []byte")
}
sig, err := m.m.Sign([]byte(s), pKeyBytes)
if err != nil {
return "", err
}
enc := base64.StdEncoding.EncodeToString(sig)
return enc, nil
}
func (m *macSigner) signatureString(r *http.Request) (string, error) {
return signatureString(r.Header, m.headers, addRequestTarget(r), m.created, m.expires)
}
func (m *macSigner) signatureStringResponse(r http.ResponseWriter) (string, error) {
return signatureString(r.Header(), m.headers, requestTargetNotPermitted, m.created, m.expires)
}
var _ Signer = &asymmSigner{}
type asymmSigner struct {
s signer
makeDigest bool
dAlgo DigestAlgorithm
headers []string
targetHeader SignatureScheme
prefix string
created int64
expires int64
}
func (a *asymmSigner) SignRequest(pKey crypto.PrivateKey, pubKeyId string, r *http.Request, body []byte) error {
if body != nil {
err := addDigest(r, a.dAlgo, body)
if err != nil {
return err
}
}
s, err := a.signatureString(r)
if err != nil {
return err
}
enc, err := a.signSignature(pKey, s)
if err != nil {
return err
}
setSignatureHeader(r.Header, string(a.targetHeader), a.prefix, pubKeyId, a.s.String(), enc, a.headers, a.created, a.expires)
return nil
}
func (a *asymmSigner) SignResponse(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter, body []byte) error {
if body != nil {
err := addDigestResponse(r, a.dAlgo, body)
if err != nil {
return err
}
}
s, err := a.signatureStringResponse(r)
if err != nil {
return err
}
enc, err := a.signSignature(pKey, s)
if err != nil {
return err
}
setSignatureHeader(r.Header(), string(a.targetHeader), a.prefix, pubKeyId, a.s.String(), enc, a.headers, a.created, a.expires)
return nil
}
func (a *asymmSigner) signSignature(pKey crypto.PrivateKey, s string) (string, error) {
sig, err := a.s.Sign(rand.Reader, pKey, []byte(s))
if err != nil {
return "", err
}
enc := base64.StdEncoding.EncodeToString(sig)
return enc, nil
}
func (a *asymmSigner) signatureString(r *http.Request) (string, error) {
return signatureString(r.Header, a.headers, addRequestTarget(r), a.created, a.expires)
}
func (a *asymmSigner) signatureStringResponse(r http.ResponseWriter) (string, error) {
return signatureString(r.Header(), a.headers, requestTargetNotPermitted, a.created, a.expires)
}
var _ SSHSigner = &asymmSSHSigner{}
type asymmSSHSigner struct {
*asymmSigner
}
func (a *asymmSSHSigner) SignRequest(pubKeyId string, r *http.Request, body []byte) error {
return a.asymmSigner.SignRequest(nil, pubKeyId, r, body)
}
func (a *asymmSSHSigner) SignResponse(pubKeyId string, r http.ResponseWriter, body []byte) error {
return a.asymmSigner.SignResponse(nil, pubKeyId, r, body)
}
func setSignatureHeader(h http.Header, targetHeader, prefix, pubKeyId, algo, enc string, headers []string, created int64, expires int64) {
if len(headers) == 0 {
headers = defaultHeaders
}
var b bytes.Buffer
// KeyId
b.WriteString(prefix)
if len(prefix) > 0 {
b.WriteString(prefixSeparater)
}
b.WriteString(keyIdParameter)
b.WriteString(parameterKVSeparater)
b.WriteString(parameterValueDelimiter)
b.WriteString(pubKeyId)
b.WriteString(parameterValueDelimiter)
b.WriteString(parameterSeparater)
// Algorithm
b.WriteString(algorithmParameter)
b.WriteString(parameterKVSeparater)
b.WriteString(parameterValueDelimiter)
b.WriteString("hs2019") //real algorithm is hidden, see newest version of spec draft
b.WriteString(parameterValueDelimiter)
b.WriteString(parameterSeparater)
hasCreated := false
hasExpires := false
for _, h := range headers {
val := strings.ToLower(h)
if val == "("+createdKey+")" {
hasCreated = true
} else if val == "("+expiresKey+")" {
hasExpires = true
}
}
// Created
if hasCreated == true {
b.WriteString(createdKey)
b.WriteString(parameterKVSeparater)
b.WriteString(strconv.FormatInt(created, 10))
b.WriteString(parameterSeparater)
}
// Expires
if hasExpires == true {
b.WriteString(expiresKey)
b.WriteString(parameterKVSeparater)
b.WriteString(strconv.FormatInt(expires, 10))
b.WriteString(parameterSeparater)
}
// Headers
b.WriteString(headersParameter)
b.WriteString(parameterKVSeparater)
b.WriteString(parameterValueDelimiter)
for i, h := range headers {
b.WriteString(strings.ToLower(h))
if i != len(headers)-1 {
b.WriteString(headerParameterValueDelim)
}
}
b.WriteString(parameterValueDelimiter)
b.WriteString(parameterSeparater)
// Signature
b.WriteString(signatureParameter)
b.WriteString(parameterKVSeparater)
b.WriteString(parameterValueDelimiter)
b.WriteString(enc)
b.WriteString(parameterValueDelimiter)
h.Add(targetHeader, b.String())
}
func requestTargetNotPermitted(b *bytes.Buffer) error {
return fmt.Errorf("cannot sign with %q on anything other than an http request", RequestTarget)
}
func addRequestTarget(r *http.Request) func(b *bytes.Buffer) error {
return func(b *bytes.Buffer) error {
b.WriteString(RequestTarget)
b.WriteString(headerFieldDelimiter)
b.WriteString(strings.ToLower(r.Method))
b.WriteString(requestTargetSeparator)
b.WriteString(r.URL.Path)
if r.URL.RawQuery != "" {
b.WriteString("?")
b.WriteString(r.URL.RawQuery)
}
return nil
}
}
func signatureString(values http.Header, include []string, requestTargetFn func(b *bytes.Buffer) error, created int64, expires int64) (string, error) {
if len(include) == 0 {
include = defaultHeaders
}
var b bytes.Buffer
for n, i := range include {
i := strings.ToLower(i)
if i == RequestTarget {
err := requestTargetFn(&b)
if err != nil {
return "", err
}
} else if i == "("+expiresKey+")" {
if expires == 0 {
return "", fmt.Errorf("missing expires value")
}
b.WriteString(i)
b.WriteString(headerFieldDelimiter)
b.WriteString(strconv.FormatInt(expires, 10))
} else if i == "("+createdKey+")" {
if created == 0 {
return "", fmt.Errorf("missing created value")
}
b.WriteString(i)
b.WriteString(headerFieldDelimiter)
b.WriteString(strconv.FormatInt(created, 10))
} else {
hv, ok := values[textproto.CanonicalMIMEHeaderKey(i)]
if !ok {
return "", fmt.Errorf("missing header %q", i)
}
b.WriteString(i)
b.WriteString(headerFieldDelimiter)
for i, v := range hv {
b.WriteString(strings.TrimSpace(v))
if i < len(hv)-1 {
b.WriteString(headerValueDelimiter)
}
}
}
if n < len(include)-1 {
b.WriteString(headersDelimiter)
}
}
return b.String(), nil
}

184
vendor/github.com/42wim/httpsig/verifying.go generated vendored Normal file
View File

@@ -0,0 +1,184 @@
package httpsig
import (
"crypto"
"encoding/base64"
"errors"
"fmt"
"net/http"
"strconv"
"strings"
"time"
)
var _ Verifier = &verifier{}
type verifier struct {
header http.Header
kId string
signature string
created int64
expires int64
headers []string
sigStringFn func(http.Header, []string, int64, int64) (string, error)
}
func newVerifier(h http.Header, sigStringFn func(http.Header, []string, int64, int64) (string, error)) (*verifier, error) {
scheme, s, err := getSignatureScheme(h)
if err != nil {
return nil, err
}
kId, sig, headers, created, expires, err := getSignatureComponents(scheme, s)
if created != 0 {
//check if created is not in the future, we assume a maximum clock offset of 10 seconds
now := time.Now().Unix()
if created-now > 10 {
return nil, errors.New("created is in the future")
}
}
if expires != 0 {
//check if expires is in the past, we assume a maximum clock offset of 10 seconds
now := time.Now().Unix()
if now-expires > 10 {
return nil, errors.New("signature expired")
}
}
if err != nil {
return nil, err
}
return &verifier{
header: h,
kId: kId,
signature: sig,
created: created,
expires: expires,
headers: headers,
sigStringFn: sigStringFn,
}, nil
}
func (v *verifier) KeyId() string {
return v.kId
}
func (v *verifier) Verify(pKey crypto.PublicKey, algo Algorithm) error {
s, err := signerFromString(string(algo))
if err == nil {
return v.asymmVerify(s, pKey)
}
m, err := macerFromString(string(algo))
if err == nil {
return v.macVerify(m, pKey)
}
return fmt.Errorf("no crypto implementation available for %q: %s", algo, err)
}
func (v *verifier) macVerify(m macer, pKey crypto.PublicKey) error {
key, ok := pKey.([]byte)
if !ok {
return fmt.Errorf("public key for MAC verifying must be of type []byte")
}
signature, err := v.sigStringFn(v.header, v.headers, v.created, v.expires)
if err != nil {
return err
}
actualMAC, err := base64.StdEncoding.DecodeString(v.signature)
if err != nil {
return err
}
ok, err = m.Equal([]byte(signature), actualMAC, key)
if err != nil {
return err
} else if !ok {
return fmt.Errorf("invalid http signature")
}
return nil
}
func (v *verifier) asymmVerify(s signer, pKey crypto.PublicKey) error {
toHash, err := v.sigStringFn(v.header, v.headers, v.created, v.expires)
if err != nil {
return err
}
signature, err := base64.StdEncoding.DecodeString(v.signature)
if err != nil {
return err
}
err = s.Verify(pKey, []byte(toHash), signature)
if err != nil {
return err
}
return nil
}
func getSignatureScheme(h http.Header) (scheme SignatureScheme, val string, err error) {
s := h.Get(string(Signature))
sigHasAll := strings.Contains(s, keyIdParameter) ||
strings.Contains(s, headersParameter) ||
strings.Contains(s, signatureParameter)
a := h.Get(string(Authorization))
authHasAll := strings.Contains(a, keyIdParameter) ||
strings.Contains(a, headersParameter) ||
strings.Contains(a, signatureParameter)
if sigHasAll && authHasAll {
err = fmt.Errorf("both %q and %q have signature parameters", Signature, Authorization)
return
} else if !sigHasAll && !authHasAll {
err = fmt.Errorf("neither %q nor %q have signature parameters", Signature, Authorization)
return
} else if sigHasAll {
val = s
scheme = Signature
return
} else { // authHasAll
val = a
scheme = Authorization
return
}
}
func getSignatureComponents(scheme SignatureScheme, s string) (kId, sig string, headers []string, created int64, expires int64, err error) {
if as := scheme.authScheme(); len(as) > 0 {
s = strings.TrimPrefix(s, as+prefixSeparater)
}
params := strings.Split(s, parameterSeparater)
for _, p := range params {
kv := strings.SplitN(p, parameterKVSeparater, 2)
if len(kv) != 2 {
err = fmt.Errorf("malformed http signature parameter: %v", kv)
return
}
k := kv[0]
v := strings.Trim(kv[1], parameterValueDelimiter)
switch k {
case keyIdParameter:
kId = v
case createdKey:
created, err = strconv.ParseInt(v, 10, 64)
if err != nil {
return
}
case expiresKey:
expires, err = strconv.ParseInt(v, 10, 64)
if err != nil {
return
}
case algorithmParameter:
// Deprecated, ignore
case headersParameter:
headers = strings.Split(v, headerParameterValueDelim)
case signatureParameter:
sig = v
default:
// Ignore unrecognized parameters
}
}
if len(kId) == 0 {
err = fmt.Errorf("missing %q parameter in http signature", keyIdParameter)
} else if len(sig) == 0 {
err = fmt.Errorf("missing %q parameter in http signature", signatureParameter)
} else if len(headers) == 0 { // Optional
headers = defaultHeaders
}
return
}

90
vendor/github.com/alecthomas/kong-yaml/.golangci.yml generated vendored Normal file
View File

@@ -0,0 +1,90 @@
run:
tests: true
skip-dirs:
- _examples
output:
print-issued-lines: false
linters:
enable-all: true
disable:
- maligned
- megacheck
- lll
- gocyclo
- dupl
- gochecknoglobals
- funlen
- godox
- wsl
- gomnd
- gocognit
- goerr113
- nolintlint
- testpackage
- godot
- nestif
- paralleltest
- nlreturn
- cyclop
- exhaustivestruct
- gci
- gofumpt
- errorlint
- exhaustive
- ifshort
- wrapcheck
- stylecheck
- thelper
- nonamedreturns
- revive
- dupword
- exhaustruct
- varnamelen
- forcetypeassert
- ireturn
- maintidx
- govet
- nosnakecase
- testableexamples
- musttag
linters-settings:
govet:
check-shadowing: true
gocyclo:
min-complexity: 10
dupl:
threshold: 100
goconst:
min-len: 8
min-occurrences: 3
forbidigo:
#forbid:
# - (Must)?NewLexer$
exclude_godoc_examples: false
issues:
max-per-linter: 0
max-same: 0
exclude-use-default: false
exclude:
# Captured by errcheck.
- '^(G104|G204):'
# Very commonly not checked.
- 'Error return value of .(.*\.Help|.*\.MarkFlagRequired|(os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked'
- 'exported method (.*\.MarshalJSON|.*\.UnmarshalJSON|.*\.EntityURN|.*\.GoString|.*\.Pos) should have comment or be unexported'
- 'composite literal uses unkeyed fields'
- 'declaration of "err" shadows declaration'
- 'should not use dot imports'
- 'Potential file inclusion via variable'
- 'should have comment or be unexported'
- 'comment on exported var .* should be of the form'
- 'at least one file in a package should have a package comment'
- 'string literal contains the Unicode'
- 'methods on the same type should have the same receiver name'
- '_TokenType_name should be _TokenTypeName'
- '`_TokenType_map` should be `_TokenTypeMap`'
- 'rewrite if-else to switch statement'

26
vendor/github.com/alecthomas/kong-yaml/README.md generated vendored Normal file
View File

@@ -0,0 +1,26 @@
# Kong YAML utilities [![](https://godoc.org/github.com/alecthomas/kong-yaml?status.svg)](http://godoc.org/github.com/alecthomas/kong-yaml) [![CircleCI](https://img.shields.io/circleci/project/github/alecthomas/kong-yaml.svg)](https://circleci.com/gh/alecthomas/kong-yaml)
## Configuration loader
Use it like so:
```go
parser, err := kong.New(&cli, kong.Configuration(kongyaml.Loader, "/etc/myapp/config.yaml", "~/.myapp.yaml"))
```
## YAMLFileMapper
YAMLFileMapper implements kong.MapperValue to decode a YAML file into
a struct field.
Use it like so:
```go
var cli struct {
Profile Profile `type:"yamlfile"`
}
func main() {
kong.Parse(&cli, kong.NamedMapper("yamlfile", kongyaml.YAMLFileMapper))
}
```

35
vendor/github.com/alecthomas/kong-yaml/mapper.go generated vendored Normal file
View File

@@ -0,0 +1,35 @@
package kongyaml
import (
"os"
"reflect"
"github.com/alecthomas/kong"
"gopkg.in/yaml.v3"
)
// YAMLFileMapper implements kong.MapperValue to decode a YAML file into
// a struct field.
//
// var cli struct {
// Profile Profile `type:"yamlfile"`
// }
//
// func main() {
// kong.Parse(&cli, kong.NamedMapper("yamlfile", YAMLFileMapper))
// }
var YAMLFileMapper = kong.MapperFunc(decodeYAMLFile) //nolint: gochecknoglobals
func decodeYAMLFile(ctx *kong.DecodeContext, target reflect.Value) error {
var fname string
if err := ctx.Scan.PopValueInto("filename", &fname); err != nil {
return err
}
f, err := os.Open(fname) //nolint:gosec
if err != nil {
return err
}
defer f.Close() //nolint
return yaml.NewDecoder(f).Decode(target.Addr().Interface())
}

44
vendor/github.com/alecthomas/kong-yaml/yaml.go generated vendored Normal file
View File

@@ -0,0 +1,44 @@
package kongyaml
import (
"errors"
"fmt"
"io"
"strings"
"github.com/alecthomas/kong"
"gopkg.in/yaml.v3"
)
// Loader is a Kong configuration loader for YAML.
func Loader(r io.Reader) (kong.Resolver, error) {
decoder := yaml.NewDecoder(r)
config := map[string]interface{}{}
err := decoder.Decode(config)
if err != nil && !errors.Is(err, io.EOF) {
return nil, fmt.Errorf("YAML config decode error: %w", err)
}
return kong.ResolverFunc(func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (interface{}, error) {
// Build a string path up to this flag.
path := []string{}
for n := parent.Node(); n != nil && n.Type != kong.ApplicationNode; n = n.Parent {
path = append([]string{n.Name}, path...)
}
path = append(path, flag.Name)
path = strings.Split(strings.Join(path, "-"), "-")
return find(config, path), nil
}), nil
}
func find(config map[string]interface{}, path []string) interface{} {
if len(path) == 0 {
return config
}
for i := 0; i < len(path); i++ {
prefix := strings.Join(path[:i+1], "-")
if child, ok := config[prefix].(map[string]interface{}); ok {
return find(child, path[i+1:])
}
}
return config[strings.Join(path, "-")]
}

0
vendor/github.com/alecthomas/kong/.gitignore generated vendored Normal file
View File

85
vendor/github.com/alecthomas/kong/.golangci.yml generated vendored Normal file
View File

@@ -0,0 +1,85 @@
run:
tests: true
output:
print-issued-lines: false
linters:
enable-all: true
disable:
- lll
- gochecknoglobals
- wsl
- funlen
- gocognit
- goprintffuncname
- paralleltest
- nlreturn
- testpackage
- wrapcheck
- forbidigo
- gci
- godot
- gofumpt
- cyclop
- errorlint
- nestif
- tagliatelle
- thelper
- godox
- goconst
- varnamelen
- ireturn
- exhaustruct
- nonamedreturns
- nilnil
- depguard # nothing to guard against yet
- tagalign # hurts readability of kong tags
- tenv # deprecated since v1.64, but not removed yet
- mnd
- perfsprint
- err113
- copyloopvar
- intrange
- nakedret
- recvcheck # value receivers are intentionally used for copies
linters-settings:
govet:
# These govet checks are disabled by default, but they're useful.
enable:
- niliness
- sortslice
- unusedwrite
dupl:
threshold: 100
gocyclo:
min-complexity: 20
exhaustive:
default-signifies-exhaustive: true
issues:
max-per-linter: 0
max-same: 0
exclude-use-default: false
exclude:
- '^(G104|G204):'
# Very commonly not checked.
- 'Error return value of .(.*\.Help|.*\.MarkFlagRequired|(os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked'
- 'exported method (.*\.MarshalJSON|.*\.UnmarshalJSON) should have comment or be unexported'
- 'composite literal uses unkeyed fields'
- 'bad syntax for struct tag key'
- 'bad syntax for struct tag pair'
- 'result .* \(error\) is always nil'
- 'Error return value of `fmt.Fprintln` is not checked'
exclude-rules:
# Don't warn on unused parameters.
# Parameter names are useful for documentation.
# Replacing them with '_' hides useful information.
- linters: [revive]
text: 'unused-parameter: parameter \S+ seems to be unused, consider removing or renaming it as _'
# Duplicate words are okay in tests.
- linters: [dupword]
path: _test\.go

19
vendor/github.com/alecthomas/kong/COPYING generated vendored Normal file
View File

@@ -0,0 +1,19 @@
Copyright (C) 2018 Alec Thomas
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.

775
vendor/github.com/alecthomas/kong/README.md generated vendored Normal file
View File

@@ -0,0 +1,775 @@
<!-- markdownlint-disable MD013 MD033 -->
<p align="center"><img width="90%" src="kong.png" /></p>
# Kong is a command-line parser for Go
[![](https://godoc.org/github.com/alecthomas/kong?status.svg)](http://godoc.org/github.com/alecthomas/kong) [![CircleCI](https://img.shields.io/circleci/project/github/alecthomas/kong.svg)](https://circleci.com/gh/alecthomas/kong) [![Go Report Card](https://goreportcard.com/badge/github.com/alecthomas/kong)](https://goreportcard.com/report/github.com/alecthomas/kong) [![Slack chat](https://img.shields.io/static/v1?logo=slack&style=flat&label=slack&color=green&message=gophers)](https://gophers.slack.com/messages/CN9DS8YF3)
- [Version 1.0.0 Release](#version-100-release)
- [Introduction](#introduction)
- [Help](#help)
- [Help as a user of a Kong application](#help-as-a-user-of-a-kong-application)
- [Defining help in Kong](#defining-help-in-kong)
- [Command handling](#command-handling)
- [Switch on the command string](#switch-on-the-command-string)
- [Attach a `Run(...) error` method to each command](#attach-a-run-error-method-to-each-command)
- [Hooks: BeforeReset(), BeforeResolve(), BeforeApply(), AfterApply()](#hooks-beforereset-beforeresolve-beforeapply-afterapply)
- [The Bind() option](#the-bind-option)
- [Flags](#flags)
- [Commands and sub-commands](#commands-and-sub-commands)
- [Branching positional arguments](#branching-positional-arguments)
- [Positional arguments](#positional-arguments)
- [Slices](#slices)
- [Maps](#maps)
- [Pointers](#pointers)
- [Nested data structure](#nested-data-structure)
- [Custom named decoders](#custom-named-decoders)
- [Supported field types](#supported-field-types)
- [Custom decoders (mappers)](#custom-decoders-mappers)
- [Supported tags](#supported-tags)
- [Plugins](#plugins)
- [Dynamic Commands](#dynamic-commands)
- [Variable interpolation](#variable-interpolation)
- [Validation](#validation)
- [Modifying Kong's behaviour](#modifying-kongs-behaviour)
- [`Name(help)` and `Description(help)` - set the application name description](#namehelp-and-descriptionhelp---set-the-application-name-description)
- [`Configuration(loader, paths...)` - load defaults from configuration files](#configurationloader-paths---load-defaults-from-configuration-files)
- [`Resolver(...)` - support for default values from external sources](#resolver---support-for-default-values-from-external-sources)
- [`*Mapper(...)` - customising how the command-line is mapped to Go values](#mapper---customising-how-the-command-line-is-mapped-to-go-values)
- [`ConfigureHelp(HelpOptions)` and `Help(HelpFunc)` - customising help](#configurehelphelpoptions-and-helphelpfunc---customising-help)
- [Injecting values into `Run()` methods](#injecting-values-into-run-methods)
- [Other options](#other-options)
## Version 1.0.0 Release
Kong has been stable for a long time, so it seemed appropriate to cut a 1.0 release.
There is one breaking change, [#436](https://github.com/alecthomas/kong/pull/436), which should effect relatively few users.
## Introduction
Kong aims to support arbitrarily complex command-line structures with as little developer effort as possible.
To achieve that, command-lines are expressed as Go types, with the structure and tags directing how the command line is mapped onto the struct.
For example, the following command-line:
shell rm [-f] [-r] <paths> ...
shell ls [<paths> ...]
Can be represented by the following command-line structure:
```go
package main
import "github.com/alecthomas/kong"
var CLI struct {
Rm struct {
Force bool `help:"Force removal."`
Recursive bool `help:"Recursively remove files."`
Paths []string `arg:"" name:"path" help:"Paths to remove." type:"path"`
} `cmd:"" help:"Remove files."`
Ls struct {
Paths []string `arg:"" optional:"" name:"path" help:"Paths to list." type:"path"`
} `cmd:"" help:"List paths."`
}
func main() {
ctx := kong.Parse(&CLI)
switch ctx.Command() {
case "rm <path>":
case "ls":
default:
panic(ctx.Command())
}
}
```
## Help
### Help as a user of a Kong application
Every Kong application includes a `--help` flag that will display auto-generated help.
eg.
$ shell --help
usage: shell <command>
A shell-like example app.
Flags:
--help Show context-sensitive help.
--debug Debug mode.
Commands:
rm <path> ...
Remove files.
ls [<path> ...]
List paths.
If a command is provided, the help will show full detail on the command including all available flags.
eg.
$ shell --help rm
usage: shell rm <paths> ...
Remove files.
Arguments:
<paths> ... Paths to remove.
Flags:
--debug Debug mode.
-f, --force Force removal.
-r, --recursive Recursively remove files.
### Defining help in Kong
Help is automatically generated from the command-line structure itself,
including `help:""` and other tags. [Variables](#variable-interpolation) will
also be interpolated into the help string.
Finally, any command, or argument type implementing the interface
`Help() string` will have this function called to retrieve more detail to
augment the help tag. This allows for much more descriptive text than can
fit in Go tags. [See \_examples/shell/help](./_examples/shell/help)
#### Showing the _command_'s detailed help
A command's additional help text is _not_ shown from top-level help, but can be displayed within contextual help:
**Top level help**
```bash
$ go run ./_examples/shell/help --help
Usage: help <command>
An app demonstrating HelpProviders
Flags:
-h, --help Show context-sensitive help.
--flag Regular flag help
Commands:
echo Regular command help
```
**Contextual**
```bash
$ go run ./_examples/shell/help echo --help
Usage: help echo <msg>
Regular command help
🚀 additional command help
Arguments:
<msg> Regular argument help
Flags:
-h, --help Show context-sensitive help.
--flag Regular flag help
```
#### Showing an _argument_'s detailed help
Custom help will only be shown for _positional arguments with named fields_ ([see the README section on positional arguments for more details on what that means](#branching-positional-arguments))
**Contextual argument help**
```bash
$ go run ./_examples/shell/help msg --help
Usage: help echo <msg>
Regular argument help
📣 additional argument help
Flags:
-h, --help Show context-sensitive help.
--flag Regular flag help
```
## Command handling
There are two ways to handle commands in Kong.
### Switch on the command string
When you call `kong.Parse()` it will return a unique string representation of the command. Each command branch in the hierarchy will be a bare word and each branching argument or required positional argument will be the name surrounded by angle brackets. Here's an example:
There's an example of this pattern [here](https://github.com/alecthomas/kong/blob/master/_examples/shell/commandstring/main.go).
eg.
```go
package main
import "github.com/alecthomas/kong"
var CLI struct {
Rm struct {
Force bool `help:"Force removal."`
Recursive bool `help:"Recursively remove files."`
Paths []string `arg:"" name:"path" help:"Paths to remove." type:"path"`
} `cmd:"" help:"Remove files."`
Ls struct {
Paths []string `arg:"" optional:"" name:"path" help:"Paths to list." type:"path"`
} `cmd:"" help:"List paths."`
}
func main() {
ctx := kong.Parse(&CLI)
switch ctx.Command() {
case "rm <path>":
case "ls":
default:
panic(ctx.Command())
}
}
```
This has the advantage that it is convenient, but the downside that if you modify your CLI structure, the strings may change. This can be fragile.
### Attach a `Run(...) error` method to each command
A more robust approach is to break each command out into their own structs:
1. Break leaf commands out into separate structs.
2. Attach a `Run(...) error` method to all leaf commands.
3. Call `kong.Kong.Parse()` to obtain a `kong.Context`.
4. Call `kong.Context.Run(bindings...)` to call the selected parsed command.
Once a command node is selected by Kong it will search from that node back to the root. Each
encountered command node with a `Run(...) error` will be called in reverse order. This allows
sub-trees to be re-used fairly conveniently.
In addition to values bound with the `kong.Bind(...)` option, any values
passed through to `kong.Context.Run(...)` are also bindable to the target's
`Run()` arguments.
Finally, hooks can also contribute bindings via `kong.Context.Bind()` and `kong.Context.BindTo()`.
There's a full example emulating part of the Docker CLI [here](https://github.com/alecthomas/kong/tree/master/_examples/docker).
eg.
```go
type Context struct {
Debug bool
}
type RmCmd struct {
Force bool `help:"Force removal."`
Recursive bool `help:"Recursively remove files."`
Paths []string `arg:"" name:"path" help:"Paths to remove." type:"path"`
}
func (r *RmCmd) Run(ctx *Context) error {
fmt.Println("rm", r.Paths)
return nil
}
type LsCmd struct {
Paths []string `arg:"" optional:"" name:"path" help:"Paths to list." type:"path"`
}
func (l *LsCmd) Run(ctx *Context) error {
fmt.Println("ls", l.Paths)
return nil
}
var cli struct {
Debug bool `help:"Enable debug mode."`
Rm RmCmd `cmd:"" help:"Remove files."`
Ls LsCmd `cmd:"" help:"List paths."`
}
func main() {
ctx := kong.Parse(&cli)
// Call the Run() method of the selected parsed command.
err := ctx.Run(&Context{Debug: cli.Debug})
ctx.FatalIfErrorf(err)
}
```
## Hooks: BeforeReset(), BeforeResolve(), BeforeApply(), AfterApply()
If a node in the CLI, or any of its embedded fields, implements a `BeforeReset(...) error`, `BeforeResolve
(...) error`, `BeforeApply(...) error` and/or `AfterApply(...) error` method, those will be called as Kong
resets, resolves, validates, and assigns values to the node.
| Hook | Description |
| --------------- | ----------------------------------------------------------------------------------------------------------- |
| `BeforeReset` | Invoked before values are reset to their defaults (as defined by the grammar) or to zero values |
| `BeforeResolve` | Invoked before resolvers are applied to a node |
| `BeforeApply` | Invoked before the traced command line arguments are applied to the grammar |
| `AfterApply` | Invoked after command line arguments are applied to the grammar **and validated**` |
The `--help` flag is implemented with a `BeforeReset` hook.
eg.
```go
// A flag with a hook that, if triggered, will set the debug loggers output to stdout.
type debugFlag bool
func (d debugFlag) BeforeApply(logger *log.Logger) error {
logger.SetOutput(os.Stdout)
return nil
}
var cli struct {
Debug debugFlag `help:"Enable debug logging."`
}
func main() {
// Debug logger going to discard.
logger := log.New(io.Discard, "", log.LstdFlags)
ctx := kong.Parse(&cli, kong.Bind(logger))
// ...
}
```
It's also possible to register these hooks with the functional options
`kong.WithBeforeReset`, `kong.WithBeforeResolve`, `kong.WithBeforeApply`, and
`kong.WithAfterApply`.
## The Bind() option
Arguments to hooks are provided via the `Run(...)` method or `Bind(...)` option. `*Kong`, `*Context`, `*Path` and parent commands are also bound and finally, hooks can also contribute bindings via `kong.Context.Bind()` and `kong.Context.BindTo()`.
eg:
```go
type CLI struct {
Debug bool `help:"Enable debug mode."`
Rm RmCmd `cmd:"" help:"Remove files."`
Ls LsCmd `cmd:"" help:"List paths."`
}
type AuthorName string
// ...
func (l *LsCmd) Run(cli *CLI) error {
// use cli.Debug here !!
return nil
}
func (r *RmCmD) Run(author AuthorName) error{
// use binded author here
return nil
}
func main() {
var cli CLI
ctx := kong.Parse(&cli, Bind(AuthorName("penguin")))
err := ctx.Run()
```
## Flags
Any [mapped](#mapper---customising-how-the-command-line-is-mapped-to-go-values) field in the command structure _not_ tagged with `cmd` or `arg` will be a flag. Flags are optional by default.
eg. The command-line `app [--flag="foo"]` can be represented by the following.
```go
type CLI struct {
Flag string
}
```
## Commands and sub-commands
Sub-commands are specified by tagging a struct field with `cmd`. Kong supports arbitrarily nested commands.
eg. The following struct represents the CLI structure `command [--flag="str"] sub-command`.
```go
type CLI struct {
Command struct {
Flag string
SubCommand struct {
} `cmd`
} `cmd`
}
```
If a sub-command is tagged with `default:"1"` it will be selected if there are no further arguments. If a sub-command is tagged with `default:"withargs"` it will be selected even if there are further arguments or flags and those arguments or flags are valid for the sub-command. This allows the user to omit the sub-command name on the CLI if its arguments/flags are not ambiguous with the sibling commands or flags.
## Branching positional arguments
In addition to sub-commands, structs can also be configured as branching positional arguments.
This is achieved by tagging an [unmapped](#mapper---customising-how-the-command-line-is-mapped-to-go-values) nested struct field with `arg`, then including a positional argument field inside that struct _with the same name_. For example, the following command structure:
app rename <name> to <name>
Can be represented with the following:
```go
var CLI struct {
Rename struct {
Name struct {
Name string `arg` // <-- NOTE: identical name to enclosing struct field.
To struct {
Name struct {
Name string `arg`
} `arg`
} `cmd`
} `arg`
} `cmd`
}
```
This looks a little verbose in this contrived example, but typically this will not be the case.
## Positional arguments
If a field is tagged with `arg:""` it will be treated as the final positional
value to be parsed on the command line. By default positional arguments are
required, but specifying `optional:""` will alter this.
If a positional argument is a slice, all remaining arguments will be appended
to that slice.
## Slices
Slice values are treated specially. First the input is split on the `sep:"<rune>"` tag (defaults to `,`), then each element is parsed by the slice element type and appended to the slice. If the same value is encountered multiple times, elements continue to be appended.
To represent the following command-line:
cmd ls <file> <file> ...
You would use the following:
```go
var CLI struct {
Ls struct {
Files []string `arg:"" type:"existingfile"`
} `cmd`
}
```
## Maps
Maps are similar to slices except that only one key/value pair can be assigned per value, and the `sep` tag denotes the assignment character and defaults to `=`.
To represent the following command-line:
cmd config set <key>=<value> <key>=<value> ...
You would use the following:
```go
var CLI struct {
Config struct {
Set struct {
Config map[string]float64 `arg:"" type:"file:"`
} `cmd`
} `cmd`
}
```
For flags, multiple key+value pairs should be separated by `mapsep:"rune"` tag (defaults to `;`) eg. `--set="key1=value1;key2=value2"`.
## Pointers
Pointers work like the underlying type, except that you can differentiate between the presence of the zero value and no value being supplied.
For example:
```go
var CLI struct {
Foo *int
}
```
Would produce a nil value for `Foo` if no `--foo` argument is supplied, but would have a pointer to the value 0 if the argument `--foo=0` was supplied.
## Nested data structure
Kong support a nested data structure as well with `embed:""`. You can combine `embed:""` with `prefix:""`:
```go
var CLI struct {
Logging struct {
Level string `enum:"debug,info,warn,error" default:"info"`
Type string `enum:"json,console" default:"console"`
} `embed:"" prefix:"logging."`
}
```
This configures Kong to accept flags `--logging.level` and `--logging.type`.
## Custom named decoders
Kong includes a number of builtin custom type mappers. These can be used by
specifying the tag `type:"<type>"`. They are registered with the option
function `NamedMapper(name, mapper)`.
| Name | Description |
| -------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `path` | A path. ~ expansion is applied. `-` is accepted for stdout, and will be passed unaltered. |
| `existingfile` | An existing file. ~ expansion is applied. `-` is accepted for stdin, and will be passed unaltered. |
| `existingdir` | An existing directory. ~ expansion is applied. |
| `counter` | Increment a numeric field. Useful for `-vvv`. Can accept `-s`, `--long` or `--long=N`. |
| `filecontent` | Read the file at path into the field. ~ expansion is applied. `-` is accepted for stdin, and will be passed unaltered. |
Slices and maps treat type tags specially. For slices, the `type:""` tag
specifies the element type. For maps, the tag has the format
`tag:"[<key>]:[<value>]"` where either may be omitted.
## Supported field types
## Custom decoders (mappers)
Any field implementing `encoding.TextUnmarshaler` or `json.Unmarshaler` will use those interfaces
for decoding values. Kong also includes builtin support for many common Go types:
| Type | Description |
| --------------- | ----------------------------------------------------------------------------------------------------------- |
| `time.Duration` | Populated using `time.ParseDuration()`. |
| `time.Time` | Populated using `time.Parse()`. Format defaults to RFC3339 but can be overridden with the `format:"X"` tag. |
| `*os.File` | Path to a file that will be opened, or `-` for `os.Stdin`. File must be closed by the user. |
| `*url.URL` | Populated with `url.Parse()`. |
For more fine-grained control, if a field implements the
[MapperValue](https://godoc.org/github.com/alecthomas/kong#MapperValue)
interface it will be used to decode arguments into the field.
## Supported tags
Tags can be in two forms:
1. Standard Go syntax, eg. `kong:"required,name='foo'"`.
2. Bare tags, eg. `required:"" name:"foo"`
Both can coexist with standard Tag parsing.
| Tag | Description |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `cmd:""` | If present, struct is a command. |
| `arg:""` | If present, field is an argument. Required by default. |
| `env:"X,Y,..."` | Specify envars to use for default value. The envs are resolved in the declared order. The first value found is used. |
| `name:"X"` | Long name, for overriding field name. |
| `help:"X"` | Help text. |
| `type:"X"` | Specify [named types](#custom-named-decoders) to use. |
| `placeholder:"X"` | Placeholder input, if flag. e.g. `` `placeholder:"<the-placeholder>"` `` will show `--flag-name=<the-placeholder>` when displaying help. |
| `default:"X"` | Default value. |
| `default:"1"` | On a command, make it the default. |
| `default:"withargs"` | On a command, make it the default and allow args/flags from that command |
| `short:"X"` | Short name, if flag. |
| `aliases:"X,Y"` | One or more aliases (for cmd or flag). |
| `required:""` | If present, flag/arg is required. |
| `optional:""` | If present, flag/arg is optional. |
| `hidden:""` | If present, command or flag is hidden. |
| `negatable:""` | If present on a `bool` field, supports prefixing a flag with `--no-` to invert the default value |
| `negatable:"X"` | If present on a `bool` field, supports `--X` to invert the default value |
| `format:"X"` | Format for parsing input, if supported. |
| `sep:"X"` | Separator for sequences (defaults to ","). May be `none` to disable splitting. |
| `mapsep:"X"` | Separator for maps (defaults to ";"). May be `none` to disable splitting. |
| `enum:"X,Y,..."` | Set of valid values allowed for this flag. An enum field must be `required` or have a valid `default`. |
| `group:"X"` | Logical group for a flag or command. |
| `xor:"X,Y,..."` | Exclusive OR groups for flags. Only one flag in the group can be used which is restricted within the same command. When combined with `required`, at least one of the `xor` group will be required. |
| `and:"X,Y,..."` | AND groups for flags. All flags in the group must be used in the same command. When combined with `required`, all flags in the group will be required. |
| `prefix:"X"` | Prefix for all sub-flags. |
| `envprefix:"X"` | Envar prefix for all sub-flags. |
| `xorprefix:"X"` | Prefix for all sub-flags in XOR/AND groups. |
| `set:"K=V"` | Set a variable for expansion by child elements. Multiples can occur. |
| `embed:""` | If present, this field's children will be embedded in the parent. Useful for composition. |
| `passthrough:"<mode>"`[^1] | If present on a positional argument, it stops flag parsing when encountered, as if `--` was processed before. Useful for external command wrappers, like `exec`. On a command it requires that the command contains only one argument of type `[]string` which is then filled with everything following the command, unparsed. |
| `-` | Ignore the field. Useful for adding non-CLI fields to a configuration struct. e.g `` `kong:"-"` `` |
[^1]: `<mode>` can be `partial` or `all` (the default). `all` will pass through all arguments including flags, including
flags. `partial` will validate flags until the first positional argument is encountered, then pass through all remaining
positional arguments.
## Plugins
Kong CLI's can be extended by embedding the `kong.Plugin` type and populating it with pointers to Kong annotated structs. For example:
```go
var pluginOne struct {
PluginOneFlag string
}
var pluginTwo struct {
PluginTwoFlag string
}
var cli struct {
BaseFlag string
kong.Plugins
}
cli.Plugins = kong.Plugins{&pluginOne, &pluginTwo}
```
Additionally if an interface type is embedded, it can also be populated with a Kong annotated struct.
## Dynamic Commands
While plugins give complete control over extending command-line interfaces, Kong
also supports dynamically adding commands via `kong.DynamicCommand()`.
## Variable interpolation
Kong supports limited variable interpolation into help strings, placeholder strings,
enum lists and default values.
Variables are in the form:
${<name>}
${<name>=<default>}
Variables are set with the `Vars{"key": "value", ...}` option. Undefined
variable references in the grammar without a default will result in an error at
construction time.
Variables can also be set via the `set:"K=V"` tag. In this case, those variables will be available for that
node and all children. This is useful for composition by allowing the same struct to be reused.
When interpolating into flag or argument help strings, some extra variables
are defined from the value itself:
${default}
${enum}
For flags with associated environment variables, the variable `${env}` can be
interpolated into the help string. In the absence of this variable in the
help string, Kong will append `($$${env})` to the help string.
eg.
```go
type cli struct {
Config string `type:"path" default:"${config_file}"`
}
func main() {
kong.Parse(&cli,
kong.Vars{
"config_file": "~/.app.conf",
})
}
```
## Validation
Kong does validation on the structure of a command-line, but also supports
extensible validation. Any node in the tree may implement either of the following interfaces:
```go
type Validatable interface {
Validate() error
}
```
```go
type Validatable interface {
Validate(kctx *kong.Context) error
}
```
If one of these nodes is in the active command-line it will be called during
normal validation.
## Modifying Kong's behaviour
Each Kong parser can be configured via functional options passed to `New(cli any, options...Option)`.
The full set of options can be found [here](https://godoc.org/github.com/alecthomas/kong#Option).
### `Name(help)` and `Description(help)` - set the application name description
Set the application name and/or description.
The name of the application will default to the binary name, but can be overridden with `Name(name)`.
As with all help in Kong, text will be wrapped to the terminal.
### `Configuration(loader, paths...)` - load defaults from configuration files
This option provides Kong with support for loading defaults from a set of configuration files. Each file is opened, if possible, and the loader called to create a resolver for that file.
eg.
```go
kong.Parse(&cli, kong.Configuration(kong.JSON, "/etc/myapp.json", "~/.myapp.json"))
```
[See the tests](https://github.com/alecthomas/kong/blob/master/resolver_test.go#L206) for an example of how the JSON file is structured.
#### List of Configuration Loaders
- [YAML](https://github.com/alecthomas/kong-yaml)
- [HCL](https://github.com/alecthomas/kong-hcl)
- [TOML](https://github.com/alecthomas/kong-toml)
- [JSON](https://github.com/alecthomas/kong)
### `Resolver(...)` - support for default values from external sources
Resolvers are Kong's extension point for providing default values from external sources. As an example, support for environment variables via the `env` tag is provided by a resolver. There's also a builtin resolver for JSON configuration files.
Example resolvers can be found in [resolver.go](https://github.com/alecthomas/kong/blob/master/resolver.go).
### `*Mapper(...)` - customising how the command-line is mapped to Go values
Command-line arguments are mapped to Go values via the Mapper interface:
```go
// A Mapper represents how a field is mapped from command-line values to Go.
//
// Mappers can be associated with concrete fields via pointer, reflect.Type, reflect.Kind, or via a "type" tag.
//
// Additionally, if a type implements the MapperValue interface, it will be used.
type Mapper interface {
// Decode ctx.Value with ctx.Scanner into target.
Decode(ctx *DecodeContext, target reflect.Value) error
}
```
All builtin Go types (as well as a bunch of useful stdlib types like `time.Time`) have mappers registered by default. Mappers for custom types can be added using `kong.??Mapper(...)` options. Mappers are applied to fields in four ways:
1. `NamedMapper(string, Mapper)` and using the tag key `type:"<name>"`.
2. `KindMapper(reflect.Kind, Mapper)`.
3. `TypeMapper(reflect.Type, Mapper)`.
4. `ValueMapper(any, Mapper)`, passing in a pointer to a field of the grammar.
### `ConfigureHelp(HelpOptions)` and `Help(HelpFunc)` - customising help
The default help output is usually sufficient, but if not there are two solutions.
1. Use `ConfigureHelp(HelpOptions)` to configure how help is formatted (see [HelpOptions](https://godoc.org/github.com/alecthomas/kong#HelpOptions) for details).
2. Custom help can be wired into Kong via the `Help(HelpFunc)` option. The `HelpFunc` is passed a `Context`, which contains the parsed context for the current command-line. See the implementation of `DefaultHelpPrinter` for an example.
3. Use `ValueFormatter(HelpValueFormatter)` if you want to just customize the help text that is accompanied by flags and arguments.
4. Use `Groups([]Group)` if you want to customize group titles or add a header.
### Injecting values into `Run()` methods
There are several ways to inject values into `Run()` methods:
1. Use `Bind()` to bind values directly.
2. Use `BindTo()` to bind values to an interface type.
3. Use `BindToProvider()` to bind values to a function that provides the value.
4. Implement `Provide<Type>() error` methods on the command structure.
### Other options
The full set of options can be found [here](https://godoc.org/github.com/alecthomas/kong#Option).

411
vendor/github.com/alecthomas/kong/build.go generated vendored Normal file
View File

@@ -0,0 +1,411 @@
package kong
import (
"fmt"
"reflect"
"strings"
)
// Plugins are dynamically embedded command-line structures.
//
// Each element in the Plugins list *must* be a pointer to a structure.
type Plugins []any
func build(k *Kong, ast any) (app *Application, err error) {
v := reflect.ValueOf(ast)
iv := reflect.Indirect(v)
if v.Kind() != reflect.Ptr || iv.Kind() != reflect.Struct {
return nil, fmt.Errorf("expected a pointer to a struct but got %T", ast)
}
app = &Application{}
extraFlags := k.extraFlags()
seenFlags := map[string]bool{}
for _, flag := range extraFlags {
seenFlags[flag.Name] = true
}
node, err := buildNode(k, iv, ApplicationNode, newEmptyTag(), seenFlags)
if err != nil {
return nil, err
}
if len(node.Positional) > 0 && len(node.Children) > 0 {
return nil, fmt.Errorf("can't mix positional arguments and branching arguments on %T", ast)
}
app.Node = node
app.Node.Flags = append(extraFlags, app.Node.Flags...)
app.Tag = newEmptyTag()
app.Tag.Vars = k.vars
return app, nil
}
func dashedString(s string) string {
return strings.Join(camelCase(s), "-")
}
type flattenedField struct {
field reflect.StructField
value reflect.Value
tag *Tag
}
func flattenedFields(v reflect.Value, ptag *Tag) (out []flattenedField, err error) {
v = reflect.Indirect(v)
if v.Kind() != reflect.Struct {
return out, nil
}
ignored := map[string]bool{}
for i := 0; i < v.NumField(); i++ {
ft := v.Type().Field(i)
fv := v.Field(i)
tag, err := parseTag(v, ft)
if err != nil {
return nil, err
}
if tag.Ignored || ignored[ft.Name] {
ignored[ft.Name] = true
continue
}
// Assign group if it's not already set.
if tag.Group == "" {
tag.Group = ptag.Group
}
// Accumulate prefixes.
tag.Prefix = ptag.Prefix + tag.Prefix
tag.EnvPrefix = ptag.EnvPrefix + tag.EnvPrefix
tag.XorPrefix = ptag.XorPrefix + tag.XorPrefix
// Combine parent vars.
tag.Vars = ptag.Vars.CloneWith(tag.Vars)
// Command and embedded structs can be pointers, so we hydrate them now.
if (tag.Cmd || tag.Embed) && ft.Type.Kind() == reflect.Ptr {
fv = reflect.New(ft.Type.Elem()).Elem()
v.FieldByIndex(ft.Index).Set(fv.Addr())
}
if !ft.Anonymous && !tag.Embed {
if fv.CanSet() {
field := flattenedField{field: ft, value: fv, tag: tag}
out = append(out, field)
}
continue
}
// Embedded type.
if fv.Kind() == reflect.Interface {
fv = fv.Elem()
} else if fv.Type() == reflect.TypeOf(Plugins{}) {
for i := 0; i < fv.Len(); i++ {
fields, ferr := flattenedFields(fv.Index(i).Elem(), tag)
if ferr != nil {
return nil, ferr
}
out = append(out, fields...)
}
continue
}
sub, err := flattenedFields(fv, tag)
if err != nil {
return nil, err
}
out = append(out, sub...)
}
out = removeIgnored(out, ignored)
return out, nil
}
func removeIgnored(fields []flattenedField, ignored map[string]bool) []flattenedField {
j := 0
for i := 0; i < len(fields); i++ {
if ignored[fields[i].field.Name] {
continue
}
if i != j {
fields[j] = fields[i]
}
j++
}
if j != len(fields) {
fields = fields[:j]
}
return fields
}
// Build a Node in the Kong data model.
//
// "v" is the value to create the node from, "typ" is the output Node type.
func buildNode(k *Kong, v reflect.Value, typ NodeType, tag *Tag, seenFlags map[string]bool) (*Node, error) { //nolint:gocyclo
node := &Node{
Type: typ,
Target: v,
Tag: tag,
}
fields, err := flattenedFields(v, tag)
if err != nil {
return nil, err
}
MAIN:
for _, field := range fields {
for _, r := range k.ignoreFields {
if r.MatchString(v.Type().Name() + "." + field.field.Name) {
continue MAIN
}
}
ft := field.field
fv := field.value
tag := field.tag
name := tag.Name
if name == "" {
name = tag.Prefix + k.flagNamer(ft.Name)
} else {
name = tag.Prefix + name
}
if len(tag.Envs) != 0 {
for i := range tag.Envs {
tag.Envs[i] = tag.EnvPrefix + tag.Envs[i]
}
}
if len(tag.Xor) != 0 {
for i := range tag.Xor {
tag.Xor[i] = tag.XorPrefix + tag.Xor[i]
}
}
if len(tag.And) != 0 {
for i := range tag.And {
tag.And[i] = tag.XorPrefix + tag.And[i]
}
}
// Nested structs are either commands or args, unless they implement the Mapper interface.
if field.value.Kind() == reflect.Struct && (tag.Cmd || tag.Arg) && k.registry.ForValue(fv) == nil {
typ := CommandNode
if tag.Arg {
typ = ArgumentNode
}
err = buildChild(k, node, typ, v, ft, fv, tag, name, seenFlags)
} else {
err = buildField(k, node, v, ft, fv, tag, name, seenFlags)
}
if err != nil {
return nil, err
}
}
// Validate if there are no duplicate names
if err := checkDuplicateNames(node, v); err != nil {
return nil, err
}
// "Unsee" flags.
for _, flag := range node.Flags {
delete(seenFlags, "--"+flag.Name)
if flag.Short != 0 {
delete(seenFlags, "-"+string(flag.Short))
}
if negFlag := negatableFlagName(flag.Name, flag.Tag.Negatable); negFlag != "" {
delete(seenFlags, negFlag)
}
for _, aflag := range flag.Aliases {
delete(seenFlags, "--"+aflag)
}
}
if err := validatePositionalArguments(node); err != nil {
return nil, err
}
return node, nil
}
func validatePositionalArguments(node *Node) error {
var last *Value
for i, curr := range node.Positional {
if last != nil {
// Scan through argument positionals to ensure optional is never before a required.
if !last.Required && curr.Required {
return fmt.Errorf("%s: required %q cannot come after optional %q", node.FullPath(), curr.Name, last.Name)
}
// Cumulative argument needs to be last.
if last.IsCumulative() {
return fmt.Errorf("%s: argument %q cannot come after cumulative %q", node.FullPath(), curr.Name, last.Name)
}
}
last = curr
curr.Position = i
}
return nil
}
func buildChild(k *Kong, node *Node, typ NodeType, v reflect.Value, ft reflect.StructField, fv reflect.Value, tag *Tag, name string, seenFlags map[string]bool) error {
child, err := buildNode(k, fv, typ, newEmptyTag(), seenFlags)
if err != nil {
return err
}
child.Name = name
child.Tag = tag
child.Parent = node
child.Help = tag.Help
child.Hidden = tag.Hidden
child.Group = buildGroupForKey(k, tag.Group)
child.Aliases = tag.Aliases
if provider, ok := fv.Addr().Interface().(HelpProvider); ok {
child.Detail = provider.Help()
}
// A branching argument. This is a bit hairy, as we let buildNode() do the parsing, then check that
// a positional argument is provided to the child, and move it to the branching argument field.
if tag.Arg {
if len(child.Positional) == 0 {
return failField(v, ft, "positional branch must have at least one child positional argument named %q", name)
}
if child.Positional[0].Name != name {
return failField(v, ft, "first field in positional branch must have the same name as the parent field (%s).", child.Name)
}
child.Argument = child.Positional[0]
child.Positional = child.Positional[1:]
if child.Help == "" {
child.Help = child.Argument.Help
}
} else {
if tag.HasDefault {
if node.DefaultCmd != nil {
return failField(v, ft, "can't have more than one default command under %s", node.Summary())
}
if tag.Default != "withargs" && (len(child.Children) > 0 || len(child.Positional) > 0) {
return failField(v, ft, "default command %s must not have subcommands or arguments", child.Summary())
}
node.DefaultCmd = child
}
if tag.Passthrough {
if len(child.Children) > 0 || len(child.Flags) > 0 {
return failField(v, ft, "passthrough command %s must not have subcommands or flags", child.Summary())
}
if len(child.Positional) != 1 {
return failField(v, ft, "passthrough command %s must contain exactly one positional argument", child.Summary())
}
if !checkPassthroughArg(child.Positional[0].Target) {
return failField(v, ft, "passthrough command %s must contain exactly one positional argument of []string type", child.Summary())
}
child.Passthrough = true
}
}
node.Children = append(node.Children, child)
if len(child.Positional) > 0 && len(child.Children) > 0 {
return failField(v, ft, "can't mix positional arguments and branching arguments")
}
return nil
}
func buildField(k *Kong, node *Node, v reflect.Value, ft reflect.StructField, fv reflect.Value, tag *Tag, name string, seenFlags map[string]bool) error {
mapper := k.registry.ForNamedValue(tag.Type, fv)
if mapper == nil {
return failField(v, ft, "unsupported field type %s, perhaps missing a cmd:\"\" tag?", ft.Type)
}
value := &Value{
Name: name,
Help: tag.Help,
OrigHelp: tag.Help,
HasDefault: tag.HasDefault,
Default: tag.Default,
DefaultValue: reflect.New(fv.Type()).Elem(),
Mapper: mapper,
Tag: tag,
Target: fv,
Enum: tag.Enum,
Passthrough: tag.Passthrough,
PassthroughMode: tag.PassthroughMode,
// Flags are optional by default, and args are required by default.
Required: (!tag.Arg && tag.Required) || (tag.Arg && !tag.Optional),
Format: tag.Format,
}
if tag.Arg {
node.Positional = append(node.Positional, value)
} else {
if seenFlags["--"+value.Name] {
return failField(v, ft, "duplicate flag --%s", value.Name)
}
seenFlags["--"+value.Name] = true
for _, alias := range tag.Aliases {
aliasFlag := "--" + alias
if seenFlags[aliasFlag] {
return failField(v, ft, "duplicate flag %s", aliasFlag)
}
seenFlags[aliasFlag] = true
}
if tag.Short != 0 {
if seenFlags["-"+string(tag.Short)] {
return failField(v, ft, "duplicate short flag -%c", tag.Short)
}
seenFlags["-"+string(tag.Short)] = true
}
if tag.Negatable != "" {
negFlag := negatableFlagName(value.Name, tag.Negatable)
if seenFlags[negFlag] {
return failField(v, ft, "duplicate negation flag %s", negFlag)
}
seenFlags[negFlag] = true
}
flag := &Flag{
Value: value,
Aliases: tag.Aliases,
Short: tag.Short,
PlaceHolder: tag.PlaceHolder,
Envs: tag.Envs,
Group: buildGroupForKey(k, tag.Group),
Xor: tag.Xor,
And: tag.And,
Hidden: tag.Hidden,
}
value.Flag = flag
node.Flags = append(node.Flags, flag)
}
return nil
}
func buildGroupForKey(k *Kong, key string) *Group {
if key == "" {
return nil
}
for _, group := range k.groups {
if group.Key == key {
return &group
}
}
// No group provided with kong.ExplicitGroups. We create one ad-hoc for this key.
return &Group{
Key: key,
Title: key,
}
}
func checkDuplicateNames(node *Node, v reflect.Value) error {
seenNames := make(map[string]struct{})
for _, node := range node.Children {
if _, ok := seenNames[node.Name]; ok {
name := v.Type().Name()
if name == "" {
name = "<anonymous struct>"
}
return fmt.Errorf("duplicate command name %q in command %q", node.Name, name)
}
seenNames[node.Name] = struct{}{}
}
return nil
}

231
vendor/github.com/alecthomas/kong/callbacks.go generated vendored Normal file
View File

@@ -0,0 +1,231 @@
package kong
import (
"fmt"
"reflect"
"strings"
)
// binding is a single binding registered with Kong.
type binding struct {
// fn is a function that returns a value of the target type.
fn reflect.Value
// val is a value of the target type.
// Must be set if done and singleton are true.
val reflect.Value
// singleton indicates whether the binding is a singleton.
// If true, the binding will be resolved once and cached.
singleton bool
// done indicates whether a singleton binding has been resolved.
// If singleton is false, this field is ignored.
done bool
}
// newValueBinding builds a binding with an already resolved value.
func newValueBinding(v reflect.Value) *binding {
return &binding{val: v, done: true, singleton: true}
}
// newFunctionBinding builds a binding with a function
// that will return a value of the target type.
//
// The function signature must be func(...) (T, error) or func(...) T
// where parameters are recursively resolved.
func newFunctionBinding(f reflect.Value, singleton bool) *binding {
return &binding{fn: f, singleton: singleton}
}
// Get returns the pre-resolved value for the binding,
// or false if the binding is not resolved.
func (b *binding) Get() (v reflect.Value, ok bool) {
return b.val, b.done
}
// Set sets the value of the binding to the given value,
// marking it as resolved.
//
// If the binding is not a singleton, this method does nothing.
func (b *binding) Set(v reflect.Value) {
if b.singleton {
b.val = v
b.done = true
}
}
// A map of type to function that returns a value of that type.
//
// The function should have the signature func(...) (T, error). Arguments are recursively resolved.
type bindings map[reflect.Type]*binding
func (b bindings) String() string {
out := []string{}
for k := range b {
out = append(out, k.String())
}
return "bindings{" + strings.Join(out, ", ") + "}"
}
func (b bindings) add(values ...any) bindings {
for _, v := range values {
val := reflect.ValueOf(v)
b[val.Type()] = newValueBinding(val)
}
return b
}
func (b bindings) addTo(impl, iface any) {
val := reflect.ValueOf(impl)
b[reflect.TypeOf(iface).Elem()] = newValueBinding(val)
}
func (b bindings) addProvider(provider any, singleton bool) error {
pv := reflect.ValueOf(provider)
t := pv.Type()
if t.Kind() != reflect.Func {
return fmt.Errorf("%T must be a function", provider)
}
if t.NumOut() == 0 {
return fmt.Errorf("%T must be a function with the signature func(...)(T, error) or func(...) T", provider)
}
if t.NumOut() == 2 {
if t.Out(1) != reflect.TypeOf((*error)(nil)).Elem() {
return fmt.Errorf("missing error; %T must be a function with the signature func(...)(T, error) or func(...) T", provider)
}
}
rt := pv.Type().Out(0)
b[rt] = newFunctionBinding(pv, singleton)
return nil
}
// Clone and add values.
func (b bindings) clone() bindings {
out := make(bindings, len(b))
for k, v := range b {
out[k] = v
}
return out
}
func (b bindings) merge(other bindings) bindings {
for k, v := range other {
b[k] = v
}
return b
}
func getMethod(value reflect.Value, name string) reflect.Value {
method := value.MethodByName(name)
if !method.IsValid() {
if value.CanAddr() {
method = value.Addr().MethodByName(name)
}
}
return method
}
// getMethods gets all methods with the given name from the given value
// and any embedded fields.
//
// Returns a slice of bound methods that can be called directly.
func getMethods(value reflect.Value, name string) (methods []reflect.Value) {
if value.Kind() == reflect.Ptr {
value = value.Elem()
}
if !value.IsValid() {
return
}
if method := getMethod(value, name); method.IsValid() {
methods = append(methods, method)
}
if value.Kind() != reflect.Struct {
return
}
// If the current value is a struct, also consider embedded fields.
// Two kinds of embedded fields are considered if they're exported:
//
// - standard Go embedded fields
// - fields tagged with `embed:""`
t := value.Type()
for i := 0; i < value.NumField(); i++ {
fieldValue := value.Field(i)
field := t.Field(i)
if !field.IsExported() {
continue
}
// Consider a field embedded if it's actually embedded
// or if it's tagged with `embed:""`.
_, isEmbedded := field.Tag.Lookup("embed")
isEmbedded = isEmbedded || field.Anonymous
if isEmbedded {
methods = append(methods, getMethods(fieldValue, name)...)
}
}
return
}
func callFunction(f reflect.Value, bindings bindings) error {
if f.Kind() != reflect.Func {
return fmt.Errorf("expected function, got %s", f.Type())
}
t := f.Type()
if t.NumOut() != 1 || !t.Out(0).Implements(callbackReturnSignature) {
return fmt.Errorf("return value of %s must implement \"error\"", t)
}
out, err := callAnyFunction(f, bindings)
if err != nil {
return err
}
ferr := out[0]
if ferrv := reflect.ValueOf(ferr); !ferrv.IsValid() || ((ferrv.Kind() == reflect.Interface || ferrv.Kind() == reflect.Pointer) && ferrv.IsNil()) {
return nil
}
return ferr.(error) //nolint:forcetypeassert
}
func callAnyFunction(f reflect.Value, bindings bindings) (out []any, err error) {
if f.Kind() != reflect.Func {
return nil, fmt.Errorf("expected function, got %s", f.Type())
}
in := []reflect.Value{}
t := f.Type()
for i := 0; i < t.NumIn(); i++ {
pt := t.In(i)
binding, ok := bindings[pt]
if !ok {
return nil, fmt.Errorf("couldn't find binding of type %s for parameter %d of %s(), use kong.Bind(%s)", pt, i, t, pt)
}
// Don't need to call the function if the value is already resolved.
if val, ok := binding.Get(); ok {
in = append(in, val)
continue
}
// Recursively resolve binding functions.
argv, err := callAnyFunction(binding.fn, bindings)
if err != nil {
return nil, fmt.Errorf("%s: %w", pt, err)
}
if ferrv := reflect.ValueOf(argv[len(argv)-1]); ferrv.IsValid() && ferrv.Type().Implements(callbackReturnSignature) && !ferrv.IsNil() {
return nil, ferrv.Interface().(error) //nolint:forcetypeassert
}
val := reflect.ValueOf(argv[0])
binding.Set(val)
in = append(in, val)
}
outv := f.Call(in)
out = make([]any, len(outv))
for i, v := range outv {
out[i] = v.Interface()
}
return out, nil
}

90
vendor/github.com/alecthomas/kong/camelcase.go generated vendored Normal file
View File

@@ -0,0 +1,90 @@
package kong
// NOTE: This code is from https://github.com/fatih/camelcase. MIT license.
import (
"unicode"
"unicode/utf8"
)
// Split splits the camelcase word and returns a list of words. It also
// supports digits. Both lower camel case and upper camel case are supported.
// For more info please check: http://en.wikipedia.org/wiki/CamelCase
//
// Examples
//
// "" => [""]
// "lowercase" => ["lowercase"]
// "Class" => ["Class"]
// "MyClass" => ["My", "Class"]
// "MyC" => ["My", "C"]
// "HTML" => ["HTML"]
// "PDFLoader" => ["PDF", "Loader"]
// "AString" => ["A", "String"]
// "SimpleXMLParser" => ["Simple", "XML", "Parser"]
// "vimRPCPlugin" => ["vim", "RPC", "Plugin"]
// "GL11Version" => ["GL", "11", "Version"]
// "99Bottles" => ["99", "Bottles"]
// "May5" => ["May", "5"]
// "BFG9000" => ["BFG", "9000"]
// "BöseÜberraschung" => ["Böse", "Überraschung"]
// "Two spaces" => ["Two", " ", "spaces"]
// "BadUTF8\xe2\xe2\xa1" => ["BadUTF8\xe2\xe2\xa1"]
//
// Splitting rules
//
// 1. If string is not valid UTF-8, return it without splitting as
// single item array.
// 2. Assign all unicode characters into one of 4 sets: lower case
// letters, upper case letters, numbers, and all other characters.
// 3. Iterate through characters of string, introducing splits
// between adjacent characters that belong to different sets.
// 4. Iterate through array of split strings, and if a given string
// is upper case:
// if subsequent string is lower case:
// move last character of upper case string to beginning of
// lower case string
func camelCase(src string) (entries []string) {
// don't split invalid utf8
if !utf8.ValidString(src) {
return []string{src}
}
entries = []string{}
var runes [][]rune
lastClass := 0
// split into fields based on class of unicode character
for _, r := range src {
var class int
switch {
case unicode.IsLower(r):
class = 1
case unicode.IsUpper(r):
class = 2
case unicode.IsDigit(r):
class = 3
default:
class = 4
}
if class == lastClass {
runes[len(runes)-1] = append(runes[len(runes)-1], r)
} else {
runes = append(runes, []rune{r})
}
lastClass = class
}
// handle upper case -> lower case sequences, e.g.
// "PDFL", "oader" -> "PDF", "Loader"
for i := 0; i < len(runes)-1; i++ {
if unicode.IsUpper(runes[i][0]) && unicode.IsLower(runes[i+1][0]) {
runes[i+1] = append([]rune{runes[i][len(runes[i])-1]}, runes[i+1]...)
runes[i] = runes[i][:len(runes[i])-1]
}
}
// construct []string from results
for _, s := range runes {
if len(s) > 0 {
entries = append(entries, string(s))
}
}
return entries
}

1188
vendor/github.com/alecthomas/kong/context.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

21
vendor/github.com/alecthomas/kong/defaults.go generated vendored Normal file
View File

@@ -0,0 +1,21 @@
package kong
// ApplyDefaults if they are not already set.
func ApplyDefaults(target any, options ...Option) error {
app, err := New(target, options...)
if err != nil {
return err
}
ctx, err := Trace(app, nil)
if err != nil {
return err
}
err = ctx.Resolve()
if err != nil {
return err
}
if err = ctx.ApplyDefaults(); err != nil {
return err
}
return ctx.Validate()
}

32
vendor/github.com/alecthomas/kong/doc.go generated vendored Normal file
View File

@@ -0,0 +1,32 @@
// Package kong aims to support arbitrarily complex command-line structures with as little developer effort as possible.
//
// Here's an example:
//
// shell rm [-f] [-r] <paths> ...
// shell ls [<paths> ...]
//
// This can be represented by the following command-line structure:
//
// package main
//
// import "github.com/alecthomas/kong"
//
// var CLI struct {
// Rm struct {
// Force bool `short:"f" help:"Force removal."`
// Recursive bool `short:"r" help:"Recursively remove files."`
//
// Paths []string `arg help:"Paths to remove." type:"path"`
// } `cmd help:"Remove files."`
//
// Ls struct {
// Paths []string `arg optional help:"Paths to list." type:"path"`
// } `cmd help:"List paths."`
// }
//
// func main() {
// kong.Parse(&CLI)
// }
//
// See https://github.com/alecthomas/kong for details.
package kong

21
vendor/github.com/alecthomas/kong/error.go generated vendored Normal file
View File

@@ -0,0 +1,21 @@
package kong
// ParseError is the error type returned by Kong.Parse().
//
// It contains the parse Context that triggered the error.
type ParseError struct {
error
Context *Context
exitCode int
}
// Unwrap returns the original cause of the error.
func (p *ParseError) Unwrap() error { return p.error }
// ExitCode returns the status that Kong should exit with if it fails with a ParseError.
func (p *ParseError) ExitCode() int {
if p.exitCode == 0 {
return exitNotOk
}
return p.exitCode
}

32
vendor/github.com/alecthomas/kong/exit.go generated vendored Normal file
View File

@@ -0,0 +1,32 @@
package kong
import "errors"
const (
exitOk = 0
exitNotOk = 1
// Semantic exit codes from https://github.com/square/exit?tab=readme-ov-file#about
exitUsageError = 80
)
// ExitCoder is an interface that may be implemented by an error value to
// provide an integer exit code. The method ExitCode should return an integer
// that is intended to be used as the exit code for the application.
type ExitCoder interface {
ExitCode() int
}
// exitCodeFromError returns the exit code for the given error.
// If err implements the exitCoder interface, the ExitCode method is called.
// Otherwise, exitCodeFromError returns 0 if err is nil, and 1 if it is not.
func exitCodeFromError(err error) int {
var e ExitCoder
if errors.As(err, &e) {
return e.ExitCode()
} else if err == nil {
return exitOk
}
return exitNotOk
}

16
vendor/github.com/alecthomas/kong/global.go generated vendored Normal file
View File

@@ -0,0 +1,16 @@
package kong
import (
"os"
)
// Parse constructs a new parser and parses the default command-line.
func Parse(cli any, options ...Option) *Context {
parser, err := New(cli, options...)
if err != nil {
panic(err)
}
ctx, err := parser.Parse(os.Args[1:])
parser.FatalIfErrorf(err)
return ctx
}

9
vendor/github.com/alecthomas/kong/guesswidth.go generated vendored Normal file
View File

@@ -0,0 +1,9 @@
//go:build tinygo || appengine || (!linux && !freebsd && !darwin && !dragonfly && !netbsd && !openbsd)
package kong
import "io"
func guessWidth(w io.Writer) int {
return 80
}

41
vendor/github.com/alecthomas/kong/guesswidth_unix.go generated vendored Normal file
View File

@@ -0,0 +1,41 @@
//go:build !tinygo && ((!appengine && linux) || freebsd || darwin || dragonfly || netbsd || openbsd)
package kong
import (
"io"
"os"
"strconv"
"syscall"
"unsafe"
)
func guessWidth(w io.Writer) int {
// check if COLUMNS env is set to comply with
// http://pubs.opengroup.org/onlinepubs/009604499/basedefs/xbd_chap08.html
colsStr := os.Getenv("COLUMNS")
if colsStr != "" {
if cols, err := strconv.Atoi(colsStr); err == nil {
return cols
}
}
if t, ok := w.(*os.File); ok {
fd := t.Fd()
var dimensions [4]uint16
if _, _, err := syscall.Syscall6(
syscall.SYS_IOCTL,
uintptr(fd), //nolint: unconvert
uintptr(syscall.TIOCGWINSZ),
uintptr(unsafe.Pointer(&dimensions)), //nolint: gas
0, 0, 0,
); err == 0 {
if dimensions[1] == 0 {
return 80
}
return int(dimensions[1])
}
}
return 80
}

577
vendor/github.com/alecthomas/kong/help.go generated vendored Normal file
View File

@@ -0,0 +1,577 @@
package kong
import (
"bytes"
"fmt"
"go/doc"
"io"
"strings"
)
const (
defaultIndent = 2
defaultColumnPadding = 4
)
// Help flag.
type helpFlag bool
func (h helpFlag) IgnoreDefault() {}
func (h helpFlag) BeforeReset(ctx *Context) error {
options := ctx.Kong.helpOptions
options.Summary = false
err := ctx.printHelp(options)
if err != nil {
return err
}
ctx.Kong.Exit(0)
return nil
}
// HelpOptions for HelpPrinters.
type HelpOptions struct {
// Don't print top-level usage summary.
NoAppSummary bool
// Write a one-line summary of the context.
Summary bool
// Write help in a more compact, but still fully-specified, form.
Compact bool
// Tree writes command chains in a tree structure instead of listing them separately.
Tree bool
// Place the flags after the commands listing.
FlagsLast bool
// Indenter modulates the given prefix for the next layer in the tree view.
// The following exported templates can be used: kong.SpaceIndenter, kong.LineIndenter, kong.TreeIndenter
// The kong.SpaceIndenter will be used by default.
Indenter HelpIndenter
// Don't show the help associated with subcommands
NoExpandSubcommands bool
// Clamp the help wrap width to a value smaller than the terminal width.
// If this is set to a non-positive number, the terminal width is used; otherwise,
// the min of this value or the terminal width is used.
WrapUpperBound int
// ValueFormatter is used to format the help text of flags and positional arguments.
ValueFormatter HelpValueFormatter
}
// Apply options to Kong as a configuration option.
func (h HelpOptions) Apply(k *Kong) error {
k.helpOptions = h
return nil
}
// HelpProvider can be implemented by commands/args to provide detailed help.
type HelpProvider interface {
// This string is formatted by go/doc and thus has the same formatting rules.
Help() string
}
// PlaceHolderProvider can be implemented by mappers to provide custom placeholder text.
type PlaceHolderProvider interface {
PlaceHolder(flag *Flag) string
}
// HelpIndenter is used to indent new layers in the help tree.
type HelpIndenter func(prefix string) string
// HelpPrinter is used to print context-sensitive help.
type HelpPrinter func(options HelpOptions, ctx *Context) error
// HelpValueFormatter is used to format the help text of flags and positional arguments.
type HelpValueFormatter func(value *Value) string
// DefaultHelpValueFormatter is the default HelpValueFormatter.
func DefaultHelpValueFormatter(value *Value) string {
if len(value.Tag.Envs) == 0 || HasInterpolatedVar(value.OrigHelp, "env") {
return value.Help
}
suffix := "(" + formatEnvs(value.Tag.Envs) + ")"
switch {
case strings.HasSuffix(value.Help, "."):
return value.Help[:len(value.Help)-1] + " " + suffix + "."
case value.Help == "":
return suffix
default:
return value.Help + " " + suffix
}
}
// DefaultShortHelpPrinter is the default HelpPrinter for short help on error.
func DefaultShortHelpPrinter(options HelpOptions, ctx *Context) error {
w := newHelpWriter(ctx, options)
cmd := ctx.Selected()
app := ctx.Model
if cmd == nil {
w.Printf("Usage: %s%s", app.Name, app.Summary())
w.Printf(`Run "%s --help" for more information.`, app.Name)
} else {
w.Printf("Usage: %s %s", app.Name, cmd.Summary())
w.Printf(`Run "%s --help" for more information.`, cmd.FullPath())
}
return w.Write(ctx.Stdout)
}
// DefaultHelpPrinter is the default HelpPrinter.
func DefaultHelpPrinter(options HelpOptions, ctx *Context) error {
if ctx.Empty() {
options.Summary = false
}
w := newHelpWriter(ctx, options)
selected := ctx.Selected()
if selected == nil {
printApp(w, ctx.Model)
} else {
printCommand(w, ctx.Model, selected)
}
return w.Write(ctx.Stdout)
}
func printApp(w *helpWriter, app *Application) {
if !w.NoAppSummary {
w.Printf("Usage: %s%s", app.Name, app.Summary())
}
printNodeDetail(w, app.Node, true)
cmds := app.Leaves(true)
if len(cmds) > 0 && app.HelpFlag != nil {
w.Print("")
if w.Summary {
w.Printf(`Run "%s --help" for more information.`, app.Name)
} else {
w.Printf(`Run "%s <command> --help" for more information on a command.`, app.Name)
}
}
}
func printCommand(w *helpWriter, app *Application, cmd *Command) {
if !w.NoAppSummary {
w.Printf("Usage: %s %s", app.Name, cmd.Summary())
}
printNodeDetail(w, cmd, true)
if w.Summary && app.HelpFlag != nil {
w.Print("")
w.Printf(`Run "%s --help" for more information.`, cmd.FullPath())
}
}
func printNodeDetail(w *helpWriter, node *Node, hide bool) {
if node.Help != "" {
w.Print("")
w.Wrap(node.Help)
}
if w.Summary {
return
}
if node.Detail != "" {
w.Print("")
w.Wrap(node.Detail)
}
if len(node.Positional) > 0 {
w.Print("")
w.Print("Arguments:")
writePositionals(w.Indent(), node.Positional)
}
printFlags := func() {
if flags := node.AllFlags(true); len(flags) > 0 {
groupedFlags := collectFlagGroups(flags)
for _, group := range groupedFlags {
w.Print("")
if group.Metadata.Title != "" {
w.Wrap(group.Metadata.Title)
}
if group.Metadata.Description != "" {
w.Indent().Wrap(group.Metadata.Description)
w.Print("")
}
writeFlags(w.Indent(), group.Flags)
}
}
}
if !w.FlagsLast {
printFlags()
}
var cmds []*Node
if w.NoExpandSubcommands {
cmds = node.Children
} else {
cmds = node.Leaves(hide)
}
if len(cmds) > 0 {
iw := w.Indent()
if w.Tree {
w.Print("")
w.Print("Commands:")
writeCommandTree(iw, node)
} else {
groupedCmds := collectCommandGroups(cmds)
for _, group := range groupedCmds {
w.Print("")
if group.Metadata.Title != "" {
w.Wrap(group.Metadata.Title)
}
if group.Metadata.Description != "" {
w.Indent().Wrap(group.Metadata.Description)
w.Print("")
}
if w.Compact {
writeCompactCommandList(group.Commands, iw)
} else {
writeCommandList(group.Commands, iw)
}
}
}
}
if w.FlagsLast {
printFlags()
}
}
func writeCommandList(cmds []*Node, iw *helpWriter) {
for i, cmd := range cmds {
if cmd.Hidden {
continue
}
printCommandSummary(iw, cmd)
if i != len(cmds)-1 {
iw.Print("")
}
}
}
func writeCompactCommandList(cmds []*Node, iw *helpWriter) {
rows := [][2]string{}
for _, cmd := range cmds {
if cmd.Hidden {
continue
}
rows = append(rows, [2]string{cmd.Path(), cmd.Help})
}
writeTwoColumns(iw, rows)
}
func writeCommandTree(w *helpWriter, node *Node) {
rows := make([][2]string, 0, len(node.Children)*2)
for i, cmd := range node.Children {
if cmd.Hidden {
continue
}
rows = append(rows, w.CommandTree(cmd, "")...)
if i != len(node.Children)-1 {
rows = append(rows, [2]string{"", ""})
}
}
writeTwoColumns(w, rows)
}
type helpFlagGroup struct {
Metadata *Group
Flags [][]*Flag
}
func collectFlagGroups(flags [][]*Flag) []helpFlagGroup {
// Group keys in order of appearance.
groups := []*Group{}
// Flags grouped by their group key.
flagsByGroup := map[string][][]*Flag{}
for _, levelFlags := range flags {
levelFlagsByGroup := map[string][]*Flag{}
for _, flag := range levelFlags {
key := ""
if flag.Group != nil {
key = flag.Group.Key
groupAlreadySeen := false
for _, group := range groups {
if key == group.Key {
groupAlreadySeen = true
break
}
}
if !groupAlreadySeen {
groups = append(groups, flag.Group)
}
}
levelFlagsByGroup[key] = append(levelFlagsByGroup[key], flag)
}
for key, flags := range levelFlagsByGroup {
flagsByGroup[key] = append(flagsByGroup[key], flags)
}
}
out := []helpFlagGroup{}
// Ungrouped flags are always displayed first.
if ungroupedFlags, ok := flagsByGroup[""]; ok {
out = append(out, helpFlagGroup{
Metadata: &Group{Title: "Flags:"},
Flags: ungroupedFlags,
})
}
for _, group := range groups {
out = append(out, helpFlagGroup{Metadata: group, Flags: flagsByGroup[group.Key]})
}
return out
}
type helpCommandGroup struct {
Metadata *Group
Commands []*Node
}
func collectCommandGroups(nodes []*Node) []helpCommandGroup {
// Groups in order of appearance.
groups := []*Group{}
// Nodes grouped by their group key.
nodesByGroup := map[string][]*Node{}
for _, node := range nodes {
key := ""
if group := node.ClosestGroup(); group != nil {
key = group.Key
if _, ok := nodesByGroup[key]; !ok {
groups = append(groups, group)
}
}
nodesByGroup[key] = append(nodesByGroup[key], node)
}
out := []helpCommandGroup{}
// Ungrouped nodes are always displayed first.
if ungroupedNodes, ok := nodesByGroup[""]; ok {
out = append(out, helpCommandGroup{
Metadata: &Group{Title: "Commands:"},
Commands: ungroupedNodes,
})
}
for _, group := range groups {
out = append(out, helpCommandGroup{Metadata: group, Commands: nodesByGroup[group.Key]})
}
return out
}
func printCommandSummary(w *helpWriter, cmd *Command) {
w.Print(cmd.Summary())
if cmd.Help != "" {
w.Indent().Wrap(cmd.Help)
}
}
type helpWriter struct {
indent string
width int
lines *[]string
HelpOptions
}
func newHelpWriter(ctx *Context, options HelpOptions) *helpWriter {
lines := []string{}
wrapWidth := guessWidth(ctx.Stdout)
if options.WrapUpperBound > 0 && wrapWidth > options.WrapUpperBound {
wrapWidth = options.WrapUpperBound
}
w := &helpWriter{
indent: "",
width: wrapWidth,
lines: &lines,
HelpOptions: options,
}
return w
}
func (h *helpWriter) Printf(format string, args ...any) {
h.Print(fmt.Sprintf(format, args...))
}
func (h *helpWriter) Print(text string) {
*h.lines = append(*h.lines, strings.TrimRight(h.indent+text, " "))
}
// Indent returns a new helpWriter indented by two characters.
func (h *helpWriter) Indent() *helpWriter {
return &helpWriter{indent: h.indent + " ", lines: h.lines, width: h.width - 2, HelpOptions: h.HelpOptions}
}
func (h *helpWriter) String() string {
return strings.Join(*h.lines, "\n")
}
func (h *helpWriter) Write(w io.Writer) error {
for _, line := range *h.lines {
_, err := io.WriteString(w, line+"\n")
if err != nil {
return err
}
}
return nil
}
func (h *helpWriter) Wrap(text string) {
w := bytes.NewBuffer(nil)
doc.ToText(w, strings.TrimSpace(text), "", " ", h.width) //nolint:staticcheck // cross-package links not possible
for _, line := range strings.Split(strings.TrimSpace(w.String()), "\n") {
h.Print(line)
}
}
func writePositionals(w *helpWriter, args []*Positional) {
rows := [][2]string{}
for _, arg := range args {
rows = append(rows, [2]string{arg.Summary(), w.HelpOptions.ValueFormatter(arg)})
}
writeTwoColumns(w, rows)
}
func writeFlags(w *helpWriter, groups [][]*Flag) {
rows := [][2]string{}
haveShort := false
for _, group := range groups {
for _, flag := range group {
if flag.Short != 0 {
haveShort = true
break
}
}
}
for i, group := range groups {
if i > 0 {
rows = append(rows, [2]string{"", ""})
}
for _, flag := range group {
if !flag.Hidden {
rows = append(rows, [2]string{formatFlag(haveShort, flag), w.HelpOptions.ValueFormatter(flag.Value)})
}
}
}
writeTwoColumns(w, rows)
}
func writeTwoColumns(w *helpWriter, rows [][2]string) {
maxLeft := 375 * w.width / 1000
if maxLeft < 30 {
maxLeft = 30
}
// Find size of first column.
leftSize := 0
for _, row := range rows {
if c := len(row[0]); c > leftSize && c < maxLeft {
leftSize = c
}
}
offsetStr := strings.Repeat(" ", leftSize+defaultColumnPadding)
for _, row := range rows {
buf := bytes.NewBuffer(nil)
doc.ToText(buf, row[1], "", strings.Repeat(" ", defaultIndent), w.width-leftSize-defaultColumnPadding) //nolint:staticcheck // cross-package links not possible
lines := strings.Split(strings.TrimRight(buf.String(), "\n"), "\n")
line := fmt.Sprintf("%-*s", leftSize, row[0])
if len(row[0]) < maxLeft {
line += fmt.Sprintf("%*s%s", defaultColumnPadding, "", lines[0])
lines = lines[1:]
}
w.Print(line)
for _, line := range lines {
w.Printf("%s%s", offsetStr, line)
}
}
}
// haveShort will be true if there are short flags present at all in the help. Useful for column alignment.
func formatFlag(haveShort bool, flag *Flag) string {
flagString := ""
name := flag.Name
isBool := flag.IsBool()
isCounter := flag.IsCounter()
short := ""
if flag.Short != 0 {
short = "-" + string(flag.Short) + ", "
} else if haveShort {
short = " "
}
if isBool && flag.Tag.Negatable == negatableDefault {
name = "[no-]" + name
} else if isBool && flag.Tag.Negatable != "" {
name += "/" + flag.Tag.Negatable
}
flagString += fmt.Sprintf("%s--%s", short, name)
if !isBool && !isCounter {
flagString += fmt.Sprintf("=%s", flag.FormatPlaceHolder())
}
return flagString
}
// CommandTree creates a tree with the given node name as root and its children's arguments and sub commands as leaves.
func (h *HelpOptions) CommandTree(node *Node, prefix string) (rows [][2]string) {
var nodeName string
switch node.Type {
default:
nodeName += prefix + node.Name
if len(node.Aliases) != 0 {
nodeName += fmt.Sprintf(" (%s)", strings.Join(node.Aliases, ","))
}
case ArgumentNode:
nodeName += prefix + "<" + node.Name + ">"
}
rows = append(rows, [2]string{nodeName, node.Help})
if h.Indenter == nil {
prefix = SpaceIndenter(prefix)
} else {
prefix = h.Indenter(prefix)
}
for _, arg := range node.Positional {
rows = append(rows, [2]string{prefix + arg.Summary(), arg.Help})
}
for _, subCmd := range node.Children {
if subCmd.Hidden {
continue
}
rows = append(rows, h.CommandTree(subCmd, prefix)...)
}
return
}
// SpaceIndenter adds a space indent to the given prefix.
func SpaceIndenter(prefix string) string {
return prefix + strings.Repeat(" ", defaultIndent)
}
// LineIndenter adds line points to every new indent.
func LineIndenter(prefix string) string {
if prefix == "" {
return "- "
}
return strings.Repeat(" ", defaultIndent) + prefix
}
// TreeIndenter adds line points to every new indent and vertical lines to every layer.
func TreeIndenter(prefix string) string {
if prefix == "" {
return "|- "
}
return "|" + strings.Repeat(" ", defaultIndent) + prefix
}
func formatEnvs(envs []string) string {
formatted := make([]string, len(envs))
for i := range envs {
formatted[i] = "$" + envs[i]
}
return strings.Join(formatted, ", ")
}

32
vendor/github.com/alecthomas/kong/hooks.go generated vendored Normal file
View File

@@ -0,0 +1,32 @@
package kong
// BeforeReset is a documentation-only interface describing hooks that run before defaults values are applied.
type BeforeReset interface {
// This is not the correct signature - see README for details.
BeforeReset(args ...any) error
}
// BeforeResolve is a documentation-only interface describing hooks that run before resolvers are applied.
type BeforeResolve interface {
// This is not the correct signature - see README for details.
BeforeResolve(args ...any) error
}
// BeforeApply is a documentation-only interface describing hooks that run before values are set.
type BeforeApply interface {
// This is not the correct signature - see README for details.
BeforeApply(args ...any) error
}
// AfterApply is a documentation-only interface describing hooks that run after values are set.
type AfterApply interface {
// This is not the correct signature - see README for details.
AfterApply(args ...any) error
}
// AfterRun is a documentation-only interface describing hooks that run after Run() returns.
type AfterRun interface {
// This is not the correct signature - see README for details.
// AfterRun is called after Run() returns.
AfterRun(args ...any) error
}

52
vendor/github.com/alecthomas/kong/interpolate.go generated vendored Normal file
View File

@@ -0,0 +1,52 @@
package kong
import (
"fmt"
"regexp"
)
var interpolationRegex = regexp.MustCompile(`(\$\$)|((?:\${([[:alpha:]_][[:word:]]*))(?:=([^}]+))?})|(\$)|([^$]+)`)
// HasInterpolatedVar returns true if the variable "v" is interpolated in "s".
func HasInterpolatedVar(s string, v string) bool {
matches := interpolationRegex.FindAllStringSubmatch(s, -1)
for _, match := range matches {
if name := match[3]; name == v {
return true
}
}
return false
}
// Interpolate variables from vars into s for substrings in the form ${var} or ${var=default}.
func interpolate(s string, vars Vars, updatedVars map[string]string) (string, error) {
out := ""
matches := interpolationRegex.FindAllStringSubmatch(s, -1)
if len(matches) == 0 {
return s, nil
}
for key, val := range updatedVars {
if vars[key] != val {
vars = vars.CloneWith(updatedVars)
break
}
}
for _, match := range matches {
if dollar := match[1]; dollar != "" {
out += "$"
} else if name := match[3]; name != "" {
value, ok := vars[name]
if !ok {
// No default value.
if match[4] == "" {
return "", fmt.Errorf("undefined variable ${%s}", name)
}
value = match[4]
}
out += value
} else {
out += match[0]
}
}
return out, nil
}

503
vendor/github.com/alecthomas/kong/kong.go generated vendored Normal file
View File

@@ -0,0 +1,503 @@
package kong
import (
"errors"
"fmt"
"io"
"os"
"path/filepath"
"reflect"
"regexp"
"strings"
)
var (
callbackReturnSignature = reflect.TypeOf((*error)(nil)).Elem()
)
func failField(parent reflect.Value, field reflect.StructField, format string, args ...any) error {
name := parent.Type().Name()
if name == "" {
name = "<anonymous struct>"
}
return fmt.Errorf("%s.%s: %s", name, field.Name, fmt.Sprintf(format, args...))
}
// Must creates a new Parser or panics if there is an error.
func Must(ast any, options ...Option) *Kong {
k, err := New(ast, options...)
if err != nil {
panic(err)
}
return k
}
type usageOnError int
const (
shortUsage usageOnError = iota + 1
fullUsage
)
// Kong is the main parser type.
type Kong struct {
// Grammar model.
Model *Application
// Termination function (defaults to os.Exit)
Exit func(int)
Stdout io.Writer
Stderr io.Writer
bindings bindings
loader ConfigurationLoader
resolvers []Resolver
registry *Registry
ignoreFields []*regexp.Regexp
noDefaultHelp bool
allowHyphenated bool
usageOnError usageOnError
help HelpPrinter
shortHelp HelpPrinter
helpFormatter HelpValueFormatter
helpOptions HelpOptions
helpFlag *Flag
groups []Group
vars Vars
flagNamer func(string) string
// Set temporarily by Options. These are applied after build().
postBuildOptions []Option
embedded []embedded
dynamicCommands []*dynamicCommand
hooks map[string][]reflect.Value
}
// New creates a new Kong parser on grammar.
//
// See the README (https://github.com/alecthomas/kong) for usage instructions.
func New(grammar any, options ...Option) (*Kong, error) {
k := &Kong{
Exit: os.Exit,
Stdout: os.Stdout,
Stderr: os.Stderr,
registry: NewRegistry().RegisterDefaults(),
vars: Vars{},
bindings: bindings{},
hooks: make(map[string][]reflect.Value),
helpFormatter: DefaultHelpValueFormatter,
ignoreFields: make([]*regexp.Regexp, 0),
flagNamer: func(s string) string {
return strings.ToLower(dashedString(s))
},
}
options = append(options, Bind(k))
for _, option := range options {
if err := option.Apply(k); err != nil {
return nil, err
}
}
if k.help == nil {
k.help = DefaultHelpPrinter
}
if k.shortHelp == nil {
k.shortHelp = DefaultShortHelpPrinter
}
model, err := build(k, grammar)
if err != nil {
return k, err
}
model.Name = filepath.Base(os.Args[0])
k.Model = model
k.Model.HelpFlag = k.helpFlag
// Embed any embedded structs.
for _, embed := range k.embedded {
tag, err := parseTagString(strings.Join(embed.tags, " "))
if err != nil {
return nil, err
}
tag.Embed = true
v := reflect.Indirect(reflect.ValueOf(embed.strct))
node, err := buildNode(k, v, CommandNode, tag, map[string]bool{})
if err != nil {
return nil, err
}
for _, child := range node.Children {
child.Parent = k.Model.Node
k.Model.Children = append(k.Model.Children, child)
}
k.Model.Flags = append(k.Model.Flags, node.Flags...)
}
// Synthesise command nodes.
for _, dcmd := range k.dynamicCommands {
tag, terr := parseTagString(strings.Join(dcmd.tags, " "))
if terr != nil {
return nil, terr
}
tag.Name = dcmd.name
tag.Help = dcmd.help
tag.Group = dcmd.group
tag.Cmd = true
v := reflect.Indirect(reflect.ValueOf(dcmd.cmd))
err = buildChild(k, k.Model.Node, CommandNode, reflect.Value{}, reflect.StructField{
Name: dcmd.name,
Type: v.Type(),
}, v, tag, dcmd.name, map[string]bool{})
if err != nil {
return nil, err
}
}
for _, option := range k.postBuildOptions {
if err = option.Apply(k); err != nil {
return nil, err
}
}
k.postBuildOptions = nil
if err = k.interpolate(k.Model.Node); err != nil {
return nil, err
}
k.bindings.add(k.vars)
if err = checkOverlappingXorAnd(k); err != nil {
return nil, err
}
return k, nil
}
func checkOverlappingXorAnd(k *Kong) error {
xorGroups := map[string][]string{}
andGroups := map[string][]string{}
for _, flag := range k.Model.Node.Flags {
for _, xor := range flag.Xor {
xorGroups[xor] = append(xorGroups[xor], flag.Name)
}
for _, and := range flag.And {
andGroups[and] = append(andGroups[and], flag.Name)
}
}
for xor, xorSet := range xorGroups {
for and, andSet := range andGroups {
overlappingEntries := []string{}
for _, xorTag := range xorSet {
for _, andTag := range andSet {
if xorTag == andTag {
overlappingEntries = append(overlappingEntries, xorTag)
}
}
}
if len(overlappingEntries) > 1 {
return fmt.Errorf("invalid xor and combination, %s and %s overlap with more than one: %s", xor, and, overlappingEntries)
}
}
}
return nil
}
type varStack []Vars
func (v *varStack) head() Vars { return (*v)[len(*v)-1] }
func (v *varStack) pop() { *v = (*v)[:len(*v)-1] }
func (v *varStack) push(vars Vars) Vars {
if len(*v) != 0 {
vars = (*v)[len(*v)-1].CloneWith(vars)
}
*v = append(*v, vars)
return vars
}
// Interpolate variables into model.
func (k *Kong) interpolate(node *Node) (err error) {
stack := varStack{}
return Visit(node, func(node Visitable, next Next) error {
switch node := node.(type) {
case *Node:
vars := stack.push(node.Vars())
node.Help, err = interpolate(node.Help, vars, nil)
if err != nil {
return fmt.Errorf("help for %s: %s", node.Path(), err)
}
err = next(nil)
stack.pop()
return err
case *Value:
return next(k.interpolateValue(node, stack.head()))
}
return next(nil)
})
}
func (k *Kong) interpolateValue(value *Value, vars Vars) (err error) {
if len(value.Tag.Vars) > 0 {
vars = vars.CloneWith(value.Tag.Vars)
}
if varsContributor, ok := value.Mapper.(VarsContributor); ok {
vars = vars.CloneWith(varsContributor.Vars(value))
}
initialVars := vars.CloneWith(nil)
for n, v := range initialVars {
if vars[n], err = interpolate(v, initialVars, nil); err != nil {
return fmt.Errorf("variable %s for %s: %s", n, value.Summary(), err)
}
}
if value.Default, err = interpolate(value.Default, vars, nil); err != nil {
return fmt.Errorf("default value for %s: %s", value.Summary(), err)
}
if value.Enum, err = interpolate(value.Enum, vars, nil); err != nil {
return fmt.Errorf("enum value for %s: %s", value.Summary(), err)
}
updatedVars := map[string]string{
"default": value.Default,
"enum": value.Enum,
}
if value.Flag != nil {
for i, env := range value.Flag.Envs {
if value.Flag.Envs[i], err = interpolate(env, vars, updatedVars); err != nil {
return fmt.Errorf("env value for %s: %s", value.Summary(), err)
}
}
value.Tag.Envs = value.Flag.Envs
updatedVars["env"] = ""
if len(value.Flag.Envs) != 0 {
updatedVars["env"] = value.Flag.Envs[0]
}
value.Flag.PlaceHolder, err = interpolate(value.Flag.PlaceHolder, vars, updatedVars)
if err != nil {
return fmt.Errorf("placeholder value for %s: %s", value.Summary(), err)
}
}
value.Help, err = interpolate(value.Help, vars, updatedVars)
if err != nil {
return fmt.Errorf("help for %s: %s", value.Summary(), err)
}
return nil
}
// Provide additional builtin flags, if any.
func (k *Kong) extraFlags() []*Flag {
if k.noDefaultHelp {
return nil
}
var helpTarget helpFlag
value := reflect.ValueOf(&helpTarget).Elem()
helpFlag := &Flag{
Short: 'h',
Value: &Value{
Name: "help",
Help: "Show context-sensitive help.",
OrigHelp: "Show context-sensitive help.",
Target: value,
Tag: &Tag{},
Mapper: k.registry.ForValue(value),
DefaultValue: reflect.ValueOf(false),
},
}
helpFlag.Flag = helpFlag
k.helpFlag = helpFlag
return []*Flag{helpFlag}
}
// Parse arguments into target.
//
// The return Context can be used to further inspect the parsed command-line, to format help, to find the
// selected command, to run command Run() methods, and so on. See Context and README for more information.
//
// Will return a ParseError if a *semantically* invalid command-line is encountered (as opposed to a syntactically
// invalid one, which will report a normal error).
func (k *Kong) Parse(args []string) (ctx *Context, err error) {
ctx, err = Trace(k, args)
if err != nil { // Trace is not expected to return an err
return nil, &ParseError{error: err, Context: ctx, exitCode: exitUsageError}
}
if ctx.Error != nil {
return nil, &ParseError{error: ctx.Error, Context: ctx, exitCode: exitUsageError}
}
if err = k.applyHook(ctx, "BeforeReset"); err != nil {
return nil, &ParseError{error: err, Context: ctx}
}
if err = ctx.Reset(); err != nil {
return nil, &ParseError{error: err, Context: ctx}
}
if err = k.applyHook(ctx, "BeforeResolve"); err != nil {
return nil, &ParseError{error: err, Context: ctx}
}
if err = ctx.Resolve(); err != nil {
return nil, &ParseError{error: err, Context: ctx}
}
if err = k.applyHook(ctx, "BeforeApply"); err != nil {
return nil, &ParseError{error: err, Context: ctx}
}
if _, err = ctx.Apply(); err != nil { // Apply is not expected to return an err
return nil, &ParseError{error: err, Context: ctx}
}
if err = ctx.Validate(); err != nil {
return nil, &ParseError{error: err, Context: ctx, exitCode: exitUsageError}
}
if err = k.applyHook(ctx, "AfterApply"); err != nil {
return nil, &ParseError{error: err, Context: ctx}
}
return ctx, nil
}
func (k *Kong) applyHook(ctx *Context, name string) error {
for _, trace := range ctx.Path {
var value reflect.Value
switch {
case trace.App != nil:
value = trace.App.Target
case trace.Argument != nil:
value = trace.Argument.Target
case trace.Command != nil:
value = trace.Command.Target
case trace.Positional != nil:
value = trace.Positional.Target
case trace.Flag != nil:
value = trace.Flag.Value.Target
default:
panic("unsupported Path")
}
for _, method := range k.getMethods(value, name) {
binds := k.bindings.clone()
binds.add(ctx, trace)
binds.add(trace.Node().Vars().CloneWith(k.vars))
binds.merge(ctx.bindings)
if err := callFunction(method, binds); err != nil {
return err
}
}
}
// Path[0] will always be the app root.
return k.applyHookToDefaultFlags(ctx, ctx.Path[0].Node(), name)
}
func (k *Kong) getMethods(value reflect.Value, name string) []reflect.Value {
return append(
// Identify callbacks by reflecting on value
getMethods(value, name),
// Identify callbacks that were registered with a kong.Option
k.hooks[name]...,
)
}
// Call hook on any unset flags with default values.
func (k *Kong) applyHookToDefaultFlags(ctx *Context, node *Node, name string) error {
if node == nil {
return nil
}
return Visit(node, func(n Visitable, next Next) error {
node, ok := n.(*Node)
if !ok {
return next(nil)
}
binds := k.bindings.clone().add(ctx).add(node.Vars().CloneWith(k.vars))
for _, flag := range node.Flags {
if !flag.HasDefault || ctx.values[flag.Value].IsValid() || !flag.Target.IsValid() {
continue
}
for _, method := range getMethods(flag.Target, name) {
path := &Path{Flag: flag}
if err := callFunction(method, binds.clone().add(path)); err != nil {
return next(err)
}
}
}
return next(nil)
})
}
func formatMultilineMessage(w io.Writer, leaders []string, format string, args ...any) {
lines := strings.Split(strings.TrimRight(fmt.Sprintf(format, args...), "\n"), "\n")
leader := ""
for _, l := range leaders {
if l == "" {
continue
}
leader += l + ": "
}
fmt.Fprintf(w, "%s%s\n", leader, lines[0])
for _, line := range lines[1:] {
fmt.Fprintf(w, "%*s%s\n", len(leader), " ", line)
}
}
// Printf writes a message to Kong.Stdout with the application name prefixed.
func (k *Kong) Printf(format string, args ...any) *Kong {
formatMultilineMessage(k.Stdout, []string{k.Model.Name}, format, args...)
return k
}
// Errorf writes a message to Kong.Stderr with the application name prefixed.
func (k *Kong) Errorf(format string, args ...any) *Kong {
formatMultilineMessage(k.Stderr, []string{k.Model.Name, "error"}, format, args...)
return k
}
// Fatalf writes a message to Kong.Stderr with the application name prefixed then exits with status 1.
func (k *Kong) Fatalf(format string, args ...any) {
k.Errorf(format, args...)
k.Exit(1)
}
// FatalIfErrorf terminates with an error message if err != nil.
// If the error implements the ExitCoder interface, the ExitCode() method is called and
// the application exits with that status. Otherwise, the application exits with status 1.
func (k *Kong) FatalIfErrorf(err error, args ...any) {
if err == nil {
return
}
msg := err.Error()
if len(args) > 0 {
msg = fmt.Sprintf(args[0].(string), args[1:]...) + ": " + err.Error() //nolint
}
// Maybe display usage information.
var parseErr *ParseError
if errors.As(err, &parseErr) {
switch k.usageOnError {
case fullUsage:
_ = parseErr.Context.printHelp(k.helpOptions)
fmt.Fprintln(k.Stdout)
case shortUsage:
_ = k.shortHelp(k.helpOptions, parseErr.Context)
fmt.Fprintln(k.Stdout)
}
}
k.Errorf("%s", msg)
k.Exit(exitCodeFromError(err))
}
// LoadConfig from path using the loader configured via Configuration(loader).
//
// "path" will have ~ and any variables expanded.
func (k *Kong) LoadConfig(path string) (Resolver, error) {
var err error
path = ExpandPath(path)
path, err = interpolate(path, k.vars, nil)
if err != nil {
return nil, err
}
r, err := os.Open(path) //nolint: gas
if err != nil {
return nil, err
}
defer r.Close()
return k.loader(r)
}

BIN
vendor/github.com/alecthomas/kong/kong.png generated vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

BIN
vendor/github.com/alecthomas/kong/kong.sketch generated vendored Normal file

Binary file not shown.

11
vendor/github.com/alecthomas/kong/lefthook.yml generated vendored Normal file
View File

@@ -0,0 +1,11 @@
output:
- success
- failure
pre-push:
parallel: true
jobs:
- name: test
run: go test -v ./...
- name: lint
run: golangci-lint run

39
vendor/github.com/alecthomas/kong/levenshtein.go generated vendored Normal file
View File

@@ -0,0 +1,39 @@
package kong
import "unicode/utf8"
// Copied from https://github.com/daviddengcn/go-algs/blob/fe23fabd9d0670e4675326040ba7c285c7117b4c/ed/ed.go#L31
// License: https://github.com/daviddengcn/go-algs/blob/fe23fabd9d0670e4675326040ba7c285c7117b4c/LICENSE
func levenshtein(a, b string) int {
f := make([]int, utf8.RuneCountInString(b)+1)
for j := range f {
f[j] = j
}
for _, ca := range a {
j := 1
fj1 := f[0] // fj1 is the value of f[j - 1] in last iteration
f[0]++
for _, cb := range b {
mn := min(f[j]+1, f[j-1]+1) // delete & insert
if cb != ca {
mn = min(mn, fj1+1) // change
} else {
mn = min(mn, fj1) // matched
}
fj1, f[j] = f[j], mn // save f[j] to fj1(j is about to increase), update f[j] to mn
j++
}
}
return f[len(f)-1]
}
func min(a, b int) int { //nolint:predeclared
if a <= b {
return a
}
return b
}

938
vendor/github.com/alecthomas/kong/mapper.go generated vendored Normal file
View File

@@ -0,0 +1,938 @@
package kong
import (
"encoding"
"encoding/json"
"errors"
"fmt"
"io"
"math/bits"
"net/url"
"os"
"reflect"
"strconv"
"strings"
"time"
)
var (
mapperValueType = reflect.TypeOf((*MapperValue)(nil)).Elem()
boolMapperValueType = reflect.TypeOf((*BoolMapperValue)(nil)).Elem()
jsonUnmarshalerType = reflect.TypeOf((*json.Unmarshaler)(nil)).Elem()
textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
binaryUnmarshalerType = reflect.TypeOf((*encoding.BinaryUnmarshaler)(nil)).Elem()
)
// DecodeContext is passed to a Mapper's Decode().
//
// It contains the Value being decoded into and the Scanner to parse from.
type DecodeContext struct {
// Value being decoded into.
Value *Value
// Scan contains the input to scan into Target.
Scan *Scanner
}
// WithScanner creates a clone of this context with a new Scanner.
func (r *DecodeContext) WithScanner(scan *Scanner) *DecodeContext {
return &DecodeContext{
Value: r.Value,
Scan: scan,
}
}
// MapperValue may be implemented by fields in order to provide custom mapping.
// Mappers may additionally implement PlaceHolderProvider to provide custom placeholder text.
type MapperValue interface {
Decode(ctx *DecodeContext) error
}
// BoolMapperValue may be implemented by fields in order to provide custom mappings for boolean values.
type BoolMapperValue interface {
MapperValue
IsBool() bool
}
type mapperValueAdapter struct {
isBool bool
}
func (m *mapperValueAdapter) Decode(ctx *DecodeContext, target reflect.Value) error {
if target.Type().Implements(mapperValueType) {
return target.Interface().(MapperValue).Decode(ctx) //nolint
}
return target.Addr().Interface().(MapperValue).Decode(ctx) //nolint
}
func (m *mapperValueAdapter) IsBool() bool {
return m.isBool
}
type textUnmarshalerAdapter struct{}
func (m *textUnmarshalerAdapter) Decode(ctx *DecodeContext, target reflect.Value) error {
var value string
err := ctx.Scan.PopValueInto("value", &value)
if err != nil {
return err
}
if target.Type().Implements(textUnmarshalerType) {
return target.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(value)) //nolint
}
return target.Addr().Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(value)) //nolint
}
type binaryUnmarshalerAdapter struct{}
func (m *binaryUnmarshalerAdapter) Decode(ctx *DecodeContext, target reflect.Value) error {
var value string
err := ctx.Scan.PopValueInto("value", &value)
if err != nil {
return err
}
if target.Type().Implements(binaryUnmarshalerType) {
return target.Interface().(encoding.BinaryUnmarshaler).UnmarshalBinary([]byte(value)) //nolint
}
return target.Addr().Interface().(encoding.BinaryUnmarshaler).UnmarshalBinary([]byte(value)) //nolint
}
type jsonUnmarshalerAdapter struct{}
func (j *jsonUnmarshalerAdapter) Decode(ctx *DecodeContext, target reflect.Value) error {
var value string
err := ctx.Scan.PopValueInto("value", &value)
if err != nil {
return err
}
if target.Type().Implements(jsonUnmarshalerType) {
return target.Interface().(json.Unmarshaler).UnmarshalJSON([]byte(value)) //nolint
}
return target.Addr().Interface().(json.Unmarshaler).UnmarshalJSON([]byte(value)) //nolint
}
// A Mapper represents how a field is mapped from command-line values to Go.
//
// Mappers can be associated with concrete fields via pointer, reflect.Type, reflect.Kind, or via a "type" tag.
//
// Additionally, if a type implements the MapperValue interface, it will be used.
type Mapper interface {
// Decode ctx.Value with ctx.Scanner into target.
Decode(ctx *DecodeContext, target reflect.Value) error
}
// VarsContributor can be implemented by a Mapper to contribute Vars during interpolation.
type VarsContributor interface {
Vars(ctx *Value) Vars
}
// A BoolMapper is a Mapper to a value that is a boolean.
//
// This is used solely for formatting help.
type BoolMapper interface {
Mapper
IsBool() bool
}
// BoolMapperExt allows a Mapper to dynamically determine if a value is a boolean.
type BoolMapperExt interface {
Mapper
IsBoolFromValue(v reflect.Value) bool
}
// A MapperFunc is a single function that complies with the Mapper interface.
type MapperFunc func(ctx *DecodeContext, target reflect.Value) error
func (m MapperFunc) Decode(ctx *DecodeContext, target reflect.Value) error { //nolint: revive
return m(ctx, target)
}
// A Registry contains a set of mappers and supporting lookup methods.
type Registry struct {
names map[string]Mapper
types map[reflect.Type]Mapper
kinds map[reflect.Kind]Mapper
values map[reflect.Value]Mapper
}
// NewRegistry creates a new (empty) Registry.
func NewRegistry() *Registry {
return &Registry{
names: map[string]Mapper{},
types: map[reflect.Type]Mapper{},
kinds: map[reflect.Kind]Mapper{},
values: map[reflect.Value]Mapper{},
}
}
// ForNamedValue finds a mapper for a value with a user-specified name.
//
// Will return nil if a mapper can not be determined.
func (r *Registry) ForNamedValue(name string, value reflect.Value) Mapper {
if mapper, ok := r.names[name]; ok {
return mapper
}
return r.ForValue(value)
}
// ForValue looks up the Mapper for a reflect.Value.
func (r *Registry) ForValue(value reflect.Value) Mapper {
if mapper, ok := r.values[value]; ok {
return mapper
}
return r.ForType(value.Type())
}
// ForNamedType finds a mapper for a type with a user-specified name.
//
// Will return nil if a mapper can not be determined.
func (r *Registry) ForNamedType(name string, typ reflect.Type) Mapper {
if mapper, ok := r.names[name]; ok {
return mapper
}
return r.ForType(typ)
}
// ForType finds a mapper from a type, by type, then kind.
//
// Will return nil if a mapper can not be determined.
func (r *Registry) ForType(typ reflect.Type) Mapper {
// Check if the type implements MapperValue.
for _, impl := range []reflect.Type{typ, reflect.PtrTo(typ)} {
if impl.Implements(mapperValueType) {
// FIXME: This should pass in the bool mapper.
return &mapperValueAdapter{impl.Implements(boolMapperValueType)}
}
}
// Next, try explicitly registered types.
var mapper Mapper
var ok bool
if mapper, ok = r.types[typ]; ok {
return mapper
}
// Next try stdlib unmarshaler interfaces.
for _, impl := range []reflect.Type{typ, reflect.PtrTo(typ)} {
switch {
case impl.Implements(textUnmarshalerType):
return &textUnmarshalerAdapter{}
case impl.Implements(binaryUnmarshalerType):
return &binaryUnmarshalerAdapter{}
case impl.Implements(jsonUnmarshalerType):
return &jsonUnmarshalerAdapter{}
}
}
// Finally try registered kinds.
if mapper, ok = r.kinds[typ.Kind()]; ok {
return mapper
}
return nil
}
// RegisterKind registers a Mapper for a reflect.Kind.
func (r *Registry) RegisterKind(kind reflect.Kind, mapper Mapper) *Registry {
r.kinds[kind] = mapper
return r
}
// RegisterName registers a mapper to be used if the value mapper has a "type" tag matching name.
//
// eg.
//
// Mapper string `kong:"type='colour'`
// registry.RegisterName("colour", ...)
func (r *Registry) RegisterName(name string, mapper Mapper) *Registry {
r.names[name] = mapper
return r
}
// RegisterType registers a Mapper for a reflect.Type.
func (r *Registry) RegisterType(typ reflect.Type, mapper Mapper) *Registry {
r.types[typ] = mapper
return r
}
// RegisterValue registers a Mapper by pointer to the field value.
func (r *Registry) RegisterValue(ptr any, mapper Mapper) *Registry {
key := reflect.ValueOf(ptr)
if key.Kind() != reflect.Ptr {
panic("expected a pointer")
}
key = key.Elem()
r.values[key] = mapper
return r
}
// RegisterDefaults registers Mappers for all builtin supported Go types and some common stdlib types.
func (r *Registry) RegisterDefaults() *Registry {
return r.RegisterKind(reflect.Int, intDecoder(bits.UintSize)).
RegisterKind(reflect.Int8, intDecoder(8)).
RegisterKind(reflect.Int16, intDecoder(16)).
RegisterKind(reflect.Int32, intDecoder(32)).
RegisterKind(reflect.Int64, intDecoder(64)).
RegisterKind(reflect.Uint, uintDecoder(bits.UintSize)).
RegisterKind(reflect.Uint8, uintDecoder(8)).
RegisterKind(reflect.Uint16, uintDecoder(16)).
RegisterKind(reflect.Uint32, uintDecoder(32)).
RegisterKind(reflect.Uint64, uintDecoder(64)).
RegisterKind(reflect.Float32, floatDecoder(32)).
RegisterKind(reflect.Float64, floatDecoder(64)).
RegisterKind(reflect.String, MapperFunc(func(ctx *DecodeContext, target reflect.Value) error {
return ctx.Scan.PopValueInto("string", target.Addr().Interface())
})).
RegisterKind(reflect.Bool, boolMapper{}).
RegisterKind(reflect.Slice, sliceDecoder(r)).
RegisterKind(reflect.Map, mapDecoder(r)).
RegisterType(reflect.TypeOf(time.Time{}), timeDecoder()).
RegisterType(reflect.TypeOf(time.Duration(0)), durationDecoder()).
RegisterType(reflect.TypeOf(&url.URL{}), urlMapper()).
RegisterType(reflect.TypeOf(&os.File{}), fileMapper(r)).
RegisterName("path", pathMapper(r)).
RegisterName("existingfile", existingFileMapper(r)).
RegisterName("existingdir", existingDirMapper(r)).
RegisterName("counter", counterMapper()).
RegisterName("filecontent", fileContentMapper(r)).
RegisterKind(reflect.Ptr, ptrMapper{r})
}
type boolMapper struct{}
func (boolMapper) Decode(ctx *DecodeContext, target reflect.Value) error {
if ctx.Scan.Peek().Type == FlagValueToken {
token := ctx.Scan.Pop()
switch v := token.Value.(type) {
case string:
v = strings.ToLower(v)
switch v {
case "true", "1", "yes":
target.SetBool(true)
case "false", "0", "no":
target.SetBool(false)
default:
return fmt.Errorf("bool value must be true, 1, yes, false, 0 or no but got %q", v)
}
case bool:
target.SetBool(v)
default:
return fmt.Errorf("expected bool but got %q (%T)", token.Value, token.Value)
}
} else {
target.SetBool(true)
}
return nil
}
func (boolMapper) IsBool() bool { return true }
func durationDecoder() MapperFunc {
return func(ctx *DecodeContext, target reflect.Value) error {
t, err := ctx.Scan.PopValue("duration")
if err != nil {
return err
}
var d time.Duration
switch v := t.Value.(type) {
case string:
d, err = time.ParseDuration(v)
if err != nil {
return fmt.Errorf("expected duration but got %q: %v", v, err)
}
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
d = reflect.ValueOf(v).Convert(reflect.TypeOf(time.Duration(0))).Interface().(time.Duration) //nolint: forcetypeassert
default:
return fmt.Errorf("expected duration but got %q", v)
}
target.Set(reflect.ValueOf(d))
return nil
}
}
func timeDecoder() MapperFunc {
return func(ctx *DecodeContext, target reflect.Value) error {
format := time.RFC3339
if ctx.Value.Format != "" {
format = ctx.Value.Format
}
var value string
if err := ctx.Scan.PopValueInto("time", &value); err != nil {
return err
}
t, err := time.Parse(format, value)
if err != nil {
return err
}
target.Set(reflect.ValueOf(t))
return nil
}
}
func intDecoder(bits int) MapperFunc { //nolint: dupl
return func(ctx *DecodeContext, target reflect.Value) error {
t, err := ctx.Scan.PopValue("int")
if err != nil {
return err
}
var sv string
switch v := t.Value.(type) {
case string:
sv = v
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
sv = fmt.Sprintf("%v", v)
case float32, float64:
sv = fmt.Sprintf("%0.f", v)
default:
return fmt.Errorf("expected an int but got %q (%T)", t, t.Value)
}
n, err := strconv.ParseInt(sv, 0, bits)
if err != nil {
return fmt.Errorf("expected a valid %d bit int but got %q", bits, sv)
}
target.SetInt(n)
return nil
}
}
func uintDecoder(bits int) MapperFunc { //nolint: dupl
return func(ctx *DecodeContext, target reflect.Value) error {
t, err := ctx.Scan.PopValue("uint")
if err != nil {
return err
}
var sv string
switch v := t.Value.(type) {
case string:
sv = v
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
sv = fmt.Sprintf("%v", v)
case float32, float64:
sv = fmt.Sprintf("%0.f", v)
default:
return fmt.Errorf("expected an int but got %q (%T)", t, t.Value)
}
n, err := strconv.ParseUint(sv, 0, bits)
if err != nil {
return fmt.Errorf("expected a valid %d bit uint but got %q", bits, sv)
}
target.SetUint(n)
return nil
}
}
func floatDecoder(bits int) MapperFunc {
return func(ctx *DecodeContext, target reflect.Value) error {
t, err := ctx.Scan.PopValue("float")
if err != nil {
return err
}
switch v := t.Value.(type) {
case string:
n, err := strconv.ParseFloat(v, bits)
if err != nil {
return fmt.Errorf("expected a float but got %q (%T)", t, t.Value)
}
target.SetFloat(n)
case float32:
target.SetFloat(float64(v))
case float64:
target.SetFloat(v)
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
target.Set(reflect.ValueOf(v))
default:
return fmt.Errorf("expected an int but got %q (%T)", t, t.Value)
}
return nil
}
}
func mapDecoder(r *Registry) MapperFunc {
return func(ctx *DecodeContext, target reflect.Value) error {
if target.IsNil() {
target.Set(reflect.MakeMap(target.Type()))
}
el := target.Type()
mapsep := ctx.Value.Tag.MapSep
var childScanner *Scanner
if ctx.Value.Flag != nil {
t := ctx.Scan.Pop()
// If decoding a flag, we need an value.
if t.IsEOL() {
return fmt.Errorf("missing value, expecting \"<key>=<value>%c...\"", mapsep)
}
switch v := t.Value.(type) {
case string:
childScanner = ScanAsType(t.Type, SplitEscaped(v, mapsep)...)
case []map[string]any:
for _, m := range v {
err := jsonTranscode(m, target.Addr().Interface())
if err != nil {
return err
}
}
return nil
case map[string]any:
return jsonTranscode(v, target.Addr().Interface())
default:
return fmt.Errorf("invalid map value %q (of type %T)", t, t.Value)
}
} else {
tokens := ctx.Scan.PopWhile(func(t Token) bool { return t.IsValue() })
childScanner = ScanFromTokens(tokens...)
}
for !childScanner.Peek().IsEOL() {
var token string
err := childScanner.PopValueInto("map", &token)
if err != nil {
return err
}
parts := strings.SplitN(token, "=", 2)
if len(parts) != 2 {
return fmt.Errorf("expected \"<key>=<value>\" but got %q", token)
}
key, value := parts[0], parts[1]
keyTypeName, valueTypeName := "", ""
if typ := ctx.Value.Tag.Type; typ != "" {
parts := strings.Split(typ, ":")
if len(parts) != 2 {
return errors.New("type:\"\" on map field must be in the form \"[<keytype>]:[<valuetype>]\"")
}
keyTypeName, valueTypeName = parts[0], parts[1]
}
keyScanner := ScanAsType(FlagValueToken, key)
keyDecoder := r.ForNamedType(keyTypeName, el.Key())
keyValue := reflect.New(el.Key()).Elem()
if err := keyDecoder.Decode(ctx.WithScanner(keyScanner), keyValue); err != nil {
return fmt.Errorf("invalid map key %q", key)
}
valueScanner := ScanAsType(FlagValueToken, value)
valueDecoder := r.ForNamedType(valueTypeName, el.Elem())
valueValue := reflect.New(el.Elem()).Elem()
if err := valueDecoder.Decode(ctx.WithScanner(valueScanner), valueValue); err != nil {
return fmt.Errorf("invalid map value %q", value)
}
target.SetMapIndex(keyValue, valueValue)
}
return nil
}
}
func sliceDecoder(r *Registry) MapperFunc {
return func(ctx *DecodeContext, target reflect.Value) error {
el := target.Type().Elem()
sep := ctx.Value.Tag.Sep
var childScanner *Scanner
if ctx.Value.Flag != nil {
t := ctx.Scan.Pop()
// If decoding a flag, we need a value.
tail := ""
if sep != -1 {
tail += string(sep) + "..."
}
if t.IsEOL() {
return fmt.Errorf("missing value, expecting \"<arg>%s\"", tail)
}
switch v := t.Value.(type) {
case string:
childScanner = ScanAsType(t.Type, SplitEscaped(v, sep)...)
case []any:
return jsonTranscode(v, target.Addr().Interface())
default:
v = []any{v}
return jsonTranscode(v, target.Addr().Interface())
}
} else {
tokens := ctx.Scan.PopWhile(func(t Token) bool { return t.IsValue() })
childScanner = ScanFromTokens(tokens...)
}
childDecoder := r.ForNamedType(ctx.Value.Tag.Type, el)
if childDecoder == nil {
return fmt.Errorf("no mapper for element type of %s", target.Type())
}
for !childScanner.Peek().IsEOL() {
childValue := reflect.New(el).Elem()
err := childDecoder.Decode(ctx.WithScanner(childScanner), childValue)
if err != nil {
return err
}
target.Set(reflect.Append(target, childValue))
}
return nil
}
}
func pathMapper(r *Registry) MapperFunc {
return func(ctx *DecodeContext, target reflect.Value) error {
if target.Kind() == reflect.Slice {
return sliceDecoder(r)(ctx, target)
}
if target.Kind() == reflect.Ptr && target.Elem().Kind() == reflect.String {
if target.IsNil() {
return nil
}
target = target.Elem()
}
if target.Kind() != reflect.String {
return fmt.Errorf("\"path\" type must be applied to a string not %s", target.Type())
}
var path string
err := ctx.Scan.PopValueInto("file", &path)
if err != nil {
return err
}
if path != "-" {
path = ExpandPath(path)
}
target.SetString(path)
return nil
}
}
func fileMapper(r *Registry) MapperFunc {
return func(ctx *DecodeContext, target reflect.Value) error {
if target.Kind() == reflect.Slice {
return sliceDecoder(r)(ctx, target)
}
var path string
err := ctx.Scan.PopValueInto("file", &path)
if err != nil {
return err
}
var file *os.File
if path == "-" {
file = os.Stdin
} else {
path = ExpandPath(path)
file, err = os.Open(path) //nolint: gosec
if err != nil {
return err
}
}
target.Set(reflect.ValueOf(file))
return nil
}
}
func existingFileMapper(r *Registry) MapperFunc {
return func(ctx *DecodeContext, target reflect.Value) error {
if target.Kind() == reflect.Slice {
return sliceDecoder(r)(ctx, target)
}
if target.Kind() != reflect.String {
return fmt.Errorf("\"existingfile\" type must be applied to a string not %s", target.Type())
}
var path string
err := ctx.Scan.PopValueInto("file", &path)
if err != nil {
return err
}
if !ctx.Value.Active || (ctx.Value.Set && ctx.Value.Target.Type() == target.Type()) {
// early return to avoid checking extra files that may not exist;
// this hack only works because the value provided on the cli is
// checked before the default value is checked (if default is set).
return nil
}
if path != "-" {
path = ExpandPath(path)
stat, err := os.Stat(path)
if err != nil {
return err
}
if stat.IsDir() {
return fmt.Errorf("%q exists but is a directory", path)
}
}
target.SetString(path)
return nil
}
}
func existingDirMapper(r *Registry) MapperFunc {
return func(ctx *DecodeContext, target reflect.Value) error {
if target.Kind() == reflect.Slice {
return sliceDecoder(r)(ctx, target)
}
if target.Kind() != reflect.String {
return fmt.Errorf("\"existingdir\" must be applied to a string not %s", target.Type())
}
var path string
err := ctx.Scan.PopValueInto("file", &path)
if err != nil {
return err
}
if !ctx.Value.Active || (ctx.Value.Set && ctx.Value.Target.Type() == target.Type()) {
// early return to avoid checking extra dirs that may not exist;
// this hack only works because the value provided on the cli is
// checked before the default value is checked (if default is set).
return nil
}
path = ExpandPath(path)
stat, err := os.Stat(path)
if err != nil {
return err
}
if !stat.IsDir() {
return fmt.Errorf("%q exists but is not a directory", path)
}
target.SetString(path)
return nil
}
}
func fileContentMapper(r *Registry) MapperFunc {
return func(ctx *DecodeContext, target reflect.Value) error {
if target.Kind() != reflect.Slice && target.Elem().Kind() != reflect.Uint8 {
return fmt.Errorf("\"filecontent\" must be applied to []byte not %s", target.Type())
}
var path string
err := ctx.Scan.PopValueInto("file", &path)
if err != nil {
return err
}
if !ctx.Value.Active || ctx.Value.Set {
// early return to avoid checking extra dirs that may not exist;
// this hack only works because the value provided on the cli is
// checked before the default value is checked (if default is set).
return nil
}
var data []byte
if path != "-" {
path = ExpandPath(path)
data, err = os.ReadFile(path) //nolint:gosec
} else {
data, err = io.ReadAll(os.Stdin)
}
if err != nil {
if info, statErr := os.Stat(path); statErr == nil && info.IsDir() {
return fmt.Errorf("%q exists but is a directory: %w", path, err)
}
return err
}
target.SetBytes(data)
return nil
}
}
type ptrMapper struct {
r *Registry
}
var _ BoolMapperExt = (*ptrMapper)(nil)
// IsBoolFromValue implements BoolMapperExt
func (p ptrMapper) IsBoolFromValue(target reflect.Value) bool {
elem := reflect.New(target.Type().Elem()).Elem()
nestedMapper := p.r.ForValue(elem)
if nestedMapper == nil {
return false
}
if bm, ok := nestedMapper.(BoolMapper); ok && bm.IsBool() {
return true
}
if bm, ok := nestedMapper.(BoolMapperExt); ok && bm.IsBoolFromValue(target) {
return true
}
return target.Kind() == reflect.Ptr && target.Type().Elem().Kind() == reflect.Bool
}
func (p ptrMapper) Decode(ctx *DecodeContext, target reflect.Value) error {
elem := reflect.New(target.Type().Elem()).Elem()
nestedMapper := p.r.ForValue(elem)
if nestedMapper == nil {
return fmt.Errorf("cannot find mapper for %v", target.Type().Elem().String())
}
err := nestedMapper.Decode(ctx, elem)
if err != nil {
return err
}
target.Set(elem.Addr())
return nil
}
func counterMapper() MapperFunc {
return func(ctx *DecodeContext, target reflect.Value) error {
if ctx.Scan.Peek().Type == FlagValueToken {
t, err := ctx.Scan.PopValue("counter")
if err != nil {
return err
}
switch v := t.Value.(type) {
case string:
n, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return fmt.Errorf("expected a counter but got %q (%T)", t, t.Value)
}
target.SetInt(n)
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
target.Set(reflect.ValueOf(v))
default:
return fmt.Errorf("expected a counter but got %q (%T)", t, t.Value)
}
return nil
}
switch target.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
target.SetInt(target.Int() + 1)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
target.SetUint(target.Uint() + 1)
case reflect.Float32, reflect.Float64:
target.SetFloat(target.Float() + 1)
default:
return fmt.Errorf("type:\"counter\" must be used with a numeric field")
}
return nil
}
}
func urlMapper() MapperFunc {
return func(ctx *DecodeContext, target reflect.Value) error {
var urlStr string
err := ctx.Scan.PopValueInto("url", &urlStr)
if err != nil {
return err
}
url, err := url.Parse(urlStr)
if err != nil {
return err
}
target.Set(reflect.ValueOf(url))
return nil
}
}
// SplitEscaped splits a string on a separator.
//
// It differs from strings.Split() in that the separator can exist in a field by escaping it with a \. eg.
//
// SplitEscaped(`hello\,there,bob`, ',') == []string{"hello,there", "bob"}
func SplitEscaped(s string, sep rune) (out []string) {
if sep == -1 {
return []string{s}
}
escaped := false
token := ""
for i, ch := range s {
switch {
case escaped:
if ch != sep {
token += `\`
}
token += string(ch)
escaped = false
case ch == '\\' && i < len(s)-1:
escaped = true
case ch == sep && !escaped:
out = append(out, token)
token = ""
escaped = false
default:
token += string(ch)
}
}
if token != "" {
out = append(out, token)
}
return
}
// JoinEscaped joins a slice of strings on sep, but also escapes any instances of sep in the fields with \. eg.
//
// JoinEscaped([]string{"hello,there", "bob"}, ',') == `hello\,there,bob`
func JoinEscaped(s []string, sep rune) string {
escaped := []string{}
for _, e := range s {
escaped = append(escaped, strings.ReplaceAll(e, string(sep), `\`+string(sep)))
}
return strings.Join(escaped, string(sep))
}
// NamedFileContentFlag is a flag value that loads a file's contents and filename into its value.
type NamedFileContentFlag struct {
Filename string
Contents []byte
}
func (f *NamedFileContentFlag) Decode(ctx *DecodeContext) error { //nolint: revive
var filename string
err := ctx.Scan.PopValueInto("filename", &filename)
if err != nil {
return err
}
// This allows unsetting of file content flags.
if filename == "" {
*f = NamedFileContentFlag{}
return nil
}
filename = ExpandPath(filename)
data, err := os.ReadFile(filename) //nolint: gosec
if err != nil {
return fmt.Errorf("failed to open %q: %v", filename, err)
}
f.Contents = data
f.Filename = filename
return nil
}
// FileContentFlag is a flag value that loads a file's contents into its value.
type FileContentFlag []byte
func (f *FileContentFlag) Decode(ctx *DecodeContext) error { //nolint: revive
var filename string
err := ctx.Scan.PopValueInto("filename", &filename)
if err != nil {
return err
}
// This allows unsetting of file content flags.
if filename == "" {
*f = nil
return nil
}
filename = ExpandPath(filename)
data, err := os.ReadFile(filename) //nolint: gosec
if err != nil {
return fmt.Errorf("failed to open %q: %v", filename, err)
}
*f = data
return nil
}
func jsonTranscode(in, out any) error {
data, err := json.Marshal(in)
if err != nil {
return err
}
if err = json.Unmarshal(data, out); err != nil {
return fmt.Errorf("%#v -> %T: %v", in, out, err)
}
return nil
}

514
vendor/github.com/alecthomas/kong/model.go generated vendored Normal file
View File

@@ -0,0 +1,514 @@
package kong
import (
"fmt"
"math"
"os"
"reflect"
"strconv"
"strings"
)
// A Visitable component in the model.
type Visitable interface {
node()
}
// Application is the root of the Kong model.
type Application struct {
*Node
// Help flag, if the NoDefaultHelp() option is not specified.
HelpFlag *Flag
}
// Argument represents a branching positional argument.
type Argument = Node
// Command represents a command in the CLI.
type Command = Node
// NodeType is an enum representing the type of a Node.
type NodeType int
// Node type enumerations.
const (
ApplicationNode NodeType = iota
CommandNode
ArgumentNode
)
// Node is a branch in the CLI. ie. a command or positional argument.
type Node struct {
Type NodeType
Parent *Node
Name string
Help string // Short help displayed in summaries.
Detail string // Detailed help displayed when describing command/arg alone.
Group *Group
Hidden bool
Flags []*Flag
Positional []*Positional
Children []*Node
DefaultCmd *Node
Target reflect.Value // Pointer to the value in the grammar that this Node is associated with.
Tag *Tag
Aliases []string
Passthrough bool // Set to true to stop flag parsing when encountered.
Active bool // Denotes the node is part of an active branch in the CLI.
Argument *Value // Populated when Type is ArgumentNode.
}
func (*Node) node() {}
// Leaf returns true if this Node is a leaf node.
func (n *Node) Leaf() bool {
return len(n.Children) == 0
}
// Find a command/argument/flag by pointer to its field.
//
// Returns nil if not found. Panics if ptr is not a pointer.
func (n *Node) Find(ptr any) *Node {
key := reflect.ValueOf(ptr)
if key.Kind() != reflect.Ptr {
panic("expected a pointer")
}
return n.findNode(key)
}
func (n *Node) findNode(key reflect.Value) *Node {
if n.Target == key {
return n
}
for _, child := range n.Children {
if found := child.findNode(key); found != nil {
return found
}
}
return nil
}
// AllFlags returns flags from all ancestor branches encountered.
//
// If "hide" is true hidden flags will be omitted.
func (n *Node) AllFlags(hide bool) (out [][]*Flag) {
if n.Parent != nil {
out = append(out, n.Parent.AllFlags(hide)...)
}
group := []*Flag{}
for _, flag := range n.Flags {
if !hide || !flag.Hidden {
flag.Active = true
group = append(group, flag)
}
}
if len(group) > 0 {
out = append(out, group)
}
return
}
// Leaves returns the leaf commands/arguments under Node.
//
// If "hidden" is true hidden leaves will be omitted.
func (n *Node) Leaves(hide bool) (out []*Node) {
_ = Visit(n, func(nd Visitable, next Next) error {
if nd == n {
return next(nil)
}
if node, ok := nd.(*Node); ok {
if hide && node.Hidden {
return nil
}
if len(node.Children) == 0 && node.Type != ApplicationNode {
out = append(out, node)
}
}
return next(nil)
})
return
}
// Depth of the command from the application root.
func (n *Node) Depth() int {
depth := 0
p := n.Parent
for p != nil && p.Type != ApplicationNode {
depth++
p = p.Parent
}
return depth
}
// Summary help string for the node (not including application name).
func (n *Node) Summary() string {
summary := n.Path()
if flags := n.FlagSummary(true); flags != "" {
summary += " " + flags
}
args := []string{}
optional := 0
for _, arg := range n.Positional {
argSummary := arg.Summary()
if arg.Tag.Optional {
optional++
argSummary = strings.TrimRight(argSummary, "]")
}
args = append(args, argSummary)
}
if len(args) != 0 {
summary += " " + strings.Join(args, " ") + strings.Repeat("]", optional)
} else if len(n.Children) > 0 {
summary += " <command>"
}
allFlags := n.Flags
if n.Parent != nil {
allFlags = append(allFlags, n.Parent.Flags...)
}
for _, flag := range allFlags {
if _, ok := flag.Target.Interface().(helpFlag); ok {
continue
}
if !flag.Required {
summary += " [flags]"
break
}
}
return summary
}
// FlagSummary for the node.
func (n *Node) FlagSummary(hide bool) string {
required := []string{}
count := 0
for _, group := range n.AllFlags(hide) {
for _, flag := range group {
count++
if flag.Required {
required = append(required, flag.Summary())
}
}
}
return strings.Join(required, " ")
}
// FullPath is like Path() but includes the Application root node.
func (n *Node) FullPath() string {
root := n
for root.Parent != nil {
root = root.Parent
}
return strings.TrimSpace(root.Name + " " + n.Path())
}
// Vars returns the combined Vars defined by all ancestors of this Node.
func (n *Node) Vars() Vars {
if n == nil {
return Vars{}
}
return n.Parent.Vars().CloneWith(n.Tag.Vars)
}
// Path through ancestors to this Node.
func (n *Node) Path() (out string) {
if n.Parent != nil {
out += " " + n.Parent.Path()
}
switch n.Type {
case CommandNode:
out += " " + n.Name
if len(n.Aliases) > 0 {
out += fmt.Sprintf(" (%s)", strings.Join(n.Aliases, ","))
}
case ArgumentNode:
out += " " + "<" + n.Name + ">"
default:
}
return strings.TrimSpace(out)
}
// ClosestGroup finds the first non-nil group in this node and its ancestors.
func (n *Node) ClosestGroup() *Group {
switch {
case n.Group != nil:
return n.Group
case n.Parent != nil:
return n.Parent.ClosestGroup()
default:
return nil
}
}
// A Value is either a flag or a variable positional argument.
type Value struct {
Flag *Flag // Nil if positional argument.
Name string
Help string
OrigHelp string // Original help string, without interpolated variables.
HasDefault bool
Default string
DefaultValue reflect.Value
Enum string
Mapper Mapper
Tag *Tag
Target reflect.Value
Required bool
Set bool // Set to true when this value is set through some mechanism.
Format string // Formatting directive, if applicable.
Position int // Position (for positional arguments).
Passthrough bool // Deprecated: Use PassthroughMode instead. Set to true to stop flag parsing when encountered.
PassthroughMode PassthroughMode //
Active bool // Denotes the value is part of an active branch in the CLI.
}
// EnumMap returns a map of the enums in this value.
func (v *Value) EnumMap() map[string]bool {
parts := strings.Split(v.Enum, ",")
out := make(map[string]bool, len(parts))
for _, part := range parts {
out[strings.TrimSpace(part)] = true
}
return out
}
// EnumSlice returns a slice of the enums in this value.
func (v *Value) EnumSlice() []string {
parts := strings.Split(v.Enum, ",")
out := make([]string, len(parts))
for i, part := range parts {
out[i] = strings.TrimSpace(part)
}
return out
}
// ShortSummary returns a human-readable summary of the value, not including any placeholders/defaults.
func (v *Value) ShortSummary() string {
if v.Flag != nil {
return fmt.Sprintf("--%s", v.Name)
}
argText := "<" + v.Name + ">"
if v.IsCumulative() {
argText += " ..."
}
if !v.Required {
argText = "[" + argText + "]"
}
return argText
}
// Summary returns a human-readable summary of the value.
func (v *Value) Summary() string {
if v.Flag != nil {
if v.IsBool() {
return fmt.Sprintf("--%s", v.Name)
}
return fmt.Sprintf("--%s=%s", v.Name, v.Flag.FormatPlaceHolder())
}
argText := "<" + v.Name + ">"
if v.IsCumulative() {
argText += " ..."
}
if !v.Required {
argText = "[" + argText + "]"
}
return argText
}
// IsCumulative returns true if the type can be accumulated into.
func (v *Value) IsCumulative() bool {
return v.IsSlice() || v.IsMap()
}
// IsSlice returns true if the value is a slice.
func (v *Value) IsSlice() bool {
return v.Target.Type().Name() == "" && v.Target.Kind() == reflect.Slice
}
// IsMap returns true if the value is a map.
func (v *Value) IsMap() bool {
return v.Target.Kind() == reflect.Map
}
// IsBool returns true if the underlying value is a boolean.
func (v *Value) IsBool() bool {
if m, ok := v.Mapper.(BoolMapperExt); ok && m.IsBoolFromValue(v.Target) {
return true
}
if m, ok := v.Mapper.(BoolMapper); ok && m.IsBool() {
return true
}
return v.Target.Kind() == reflect.Bool
}
// IsCounter returns true if the value is a counter.
func (v *Value) IsCounter() bool {
return v.Tag.Type == "counter"
}
// Parse tokens into value, parse, and validate, but do not write to the field.
func (v *Value) Parse(scan *Scanner, target reflect.Value) (err error) {
if target.Kind() == reflect.Ptr && target.IsNil() {
target.Set(reflect.New(target.Type().Elem()))
}
err = v.Mapper.Decode(&DecodeContext{Value: v, Scan: scan}, target)
if err != nil {
return fmt.Errorf("%s: %w", v.ShortSummary(), err)
}
v.Set = true
return nil
}
// Apply value to field.
func (v *Value) Apply(value reflect.Value) {
v.Target.Set(value)
v.Set = true
}
// ApplyDefault value to field if it is not already set.
func (v *Value) ApplyDefault() error {
if reflectValueIsZero(v.Target) {
return v.Reset()
}
v.Set = true
return nil
}
// Reset this value to its default, either the zero value or the parsed result of its envar,
// or its "default" tag.
//
// Does not include resolvers.
func (v *Value) Reset() error {
v.Target.Set(reflect.Zero(v.Target.Type()))
if len(v.Tag.Envs) != 0 {
for _, env := range v.Tag.Envs {
envar, ok := os.LookupEnv(env)
// Parse the first non-empty ENV in the list
if ok {
err := v.Parse(ScanFromTokens(Token{Type: FlagValueToken, Value: envar}), v.Target)
if err != nil {
return fmt.Errorf("%s (from envar %s=%q)", err, env, envar)
}
return nil
}
}
}
if v.HasDefault {
return v.Parse(ScanFromTokens(Token{Type: FlagValueToken, Value: v.Default}), v.Target)
}
return nil
}
func (*Value) node() {}
// A Positional represents a non-branching command-line positional argument.
type Positional = Value
// A Flag represents a command-line flag.
type Flag struct {
*Value
Group *Group // Logical grouping when displaying. May also be used by configuration loaders to group options logically.
Xor []string
And []string
PlaceHolder string
Envs []string
Aliases []string
Short rune
Hidden bool
Negated bool
}
func (f *Flag) String() string {
out := "--" + f.Name
if f.Short != 0 {
out = fmt.Sprintf("-%c, %s", f.Short, out)
}
if !f.IsBool() && !f.IsCounter() {
out += "=" + f.FormatPlaceHolder()
}
return out
}
// FormatPlaceHolder formats the placeholder string for a Flag.
func (f *Flag) FormatPlaceHolder() string {
placeholderHelper, ok := f.Value.Mapper.(PlaceHolderProvider)
if ok {
return placeholderHelper.PlaceHolder(f)
}
tail := ""
if f.Value.IsSlice() && f.Value.Tag.Sep != -1 && f.Tag.Type == "" {
tail += string(f.Value.Tag.Sep) + "..."
}
if f.PlaceHolder != "" {
return f.PlaceHolder + tail
}
if f.HasDefault {
if f.Value.Target.Kind() == reflect.String {
return strconv.Quote(f.Default) + tail
}
return f.Default + tail
}
if f.Value.IsMap() {
if f.Value.Tag.MapSep != -1 && f.Tag.Type == "" {
tail = string(f.Value.Tag.MapSep) + "..."
}
return "KEY=VALUE" + tail
}
if f.Tag != nil && f.Tag.TypeName != "" {
return strings.ToUpper(dashedString(f.Tag.TypeName)) + tail
}
return strings.ToUpper(f.Name) + tail
}
// Group holds metadata about a command or flag group used when printing help.
type Group struct {
// Key is the `group` field tag value used to identify this group.
Key string
// Title is displayed above the grouped items.
Title string
// Description is optional and displayed under the Title when non empty.
// It can be used to introduce the group's purpose to the user.
Description string
}
// This is directly from the Go 1.13 source code.
func reflectValueIsZero(v reflect.Value) bool {
switch v.Kind() {
case reflect.Bool:
return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return math.Float64bits(v.Float()) == 0
case reflect.Complex64, reflect.Complex128:
c := v.Complex()
return math.Float64bits(real(c)) == 0 && math.Float64bits(imag(c)) == 0
case reflect.Array:
for i := 0; i < v.Len(); i++ {
if !reflectValueIsZero(v.Index(i)) {
return false
}
}
return true
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer:
return v.IsNil()
case reflect.String:
return v.Len() == 0
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
if !reflectValueIsZero(v.Field(i)) {
return false
}
}
return true
default:
// This should never happens, but will act as a safeguard for
// later, as a default value doesn't makes sense here.
panic(&reflect.ValueError{
Method: "reflect.Value.IsZero",
Kind: v.Kind(),
})
}
}

19
vendor/github.com/alecthomas/kong/negatable.go generated vendored Normal file
View File

@@ -0,0 +1,19 @@
package kong
// negatableDefault is a placeholder value for the Negatable tag to indicate
// the negated flag is --no-<flag-name>. This is needed as at the time of
// parsing a tag, the field's flag name is not yet known.
const negatableDefault = "_"
// negatableFlagName returns the name of the flag for a negatable field, or
// an empty string if the field is not negatable.
func negatableFlagName(name, negation string) string {
switch negation {
case "":
return ""
case negatableDefault:
return "--no-" + name
default:
return "--" + negation
}
}

559
vendor/github.com/alecthomas/kong/options.go generated vendored Normal file
View File

@@ -0,0 +1,559 @@
package kong
import (
"errors"
"fmt"
"io"
"os"
"os/user"
"path/filepath"
"reflect"
"regexp"
"strings"
)
// An Option applies optional changes to the Kong application.
type Option interface {
Apply(k *Kong) error
}
// OptionFunc is function that adheres to the Option interface.
type OptionFunc func(k *Kong) error
func (o OptionFunc) Apply(k *Kong) error { return o(k) } //nolint: revive
// Vars sets the variables to use for interpolation into help strings and default values.
//
// See README for details.
type Vars map[string]string
// Apply lets Vars act as an Option.
func (v Vars) Apply(k *Kong) error {
for key, value := range v {
k.vars[key] = value
}
return nil
}
// CloneWith clones the current Vars and merges "vars" onto the clone.
func (v Vars) CloneWith(vars Vars) Vars {
out := make(Vars, len(v)+len(vars))
for key, value := range v {
out[key] = value
}
for key, value := range vars {
out[key] = value
}
return out
}
// Exit overrides the function used to terminate. This is useful for testing or interactive use.
func Exit(exit func(int)) Option {
return OptionFunc(func(k *Kong) error {
k.Exit = exit
return nil
})
}
// WithHyphenPrefixedParameters enables or disables hyphen-prefixed parameters.
//
// These are disabled by default.
func WithHyphenPrefixedParameters(enable bool) Option {
return OptionFunc(func(k *Kong) error {
k.allowHyphenated = enable
return nil
})
}
type embedded struct {
strct any
tags []string
}
// Embed a struct into the root of the CLI.
//
// "strct" must be a pointer to a structure.
func Embed(strct any, tags ...string) Option {
t := reflect.TypeOf(strct)
if t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Struct {
panic("kong: Embed() must be called with a pointer to a struct")
}
return OptionFunc(func(k *Kong) error {
k.embedded = append(k.embedded, embedded{strct, tags})
return nil
})
}
type dynamicCommand struct {
name string
help string
group string
tags []string
cmd any
}
// DynamicCommand registers a dynamically constructed command with the root of the CLI.
//
// This is useful for command-line structures that are extensible via user-provided plugins.
//
// "tags" is a list of extra tag strings to parse, in the form <key>:"<value>".
func DynamicCommand(name, help, group string, cmd any, tags ...string) Option {
return OptionFunc(func(k *Kong) error {
k.dynamicCommands = append(k.dynamicCommands, &dynamicCommand{
name: name,
help: help,
group: group,
cmd: cmd,
tags: tags,
})
return nil
})
}
// NoDefaultHelp disables the default help flags.
func NoDefaultHelp() Option {
return OptionFunc(func(k *Kong) error {
k.noDefaultHelp = true
return nil
})
}
// PostBuild provides read/write access to kong.Kong after initial construction of the model is complete but before
// parsing occurs.
//
// This is useful for, e.g., adding short options to flags, updating help, etc.
func PostBuild(fn func(*Kong) error) Option {
return OptionFunc(func(k *Kong) error {
k.postBuildOptions = append(k.postBuildOptions, OptionFunc(fn))
return nil
})
}
// WithBeforeReset registers a hook to run before fields values are reset to their defaults
// (as specified in the grammar) or to zero values.
func WithBeforeReset(fn any) Option {
return withHook("BeforeReset", fn)
}
// WithBeforeResolve registers a hook to run before resolvers are applied.
func WithBeforeResolve(fn any) Option {
return withHook("BeforeResolve", fn)
}
// WithBeforeApply registers a hook to run before command line arguments are applied to the grammar.
func WithBeforeApply(fn any) Option {
return withHook("BeforeApply", fn)
}
// WithAfterApply registers a hook to run after values are applied to the grammar and validated.
func WithAfterApply(fn any) Option {
return withHook("AfterApply", fn)
}
// withHook registers a named hook.
func withHook(name string, fn any) Option {
value := reflect.ValueOf(fn)
if value.Kind() != reflect.Func {
panic(fmt.Errorf("expected function, got %s", value.Type()))
}
return OptionFunc(func(k *Kong) error {
k.hooks[name] = append(k.hooks[name], value)
return nil
})
}
// Name overrides the application name.
func Name(name string) Option {
return PostBuild(func(k *Kong) error {
k.Model.Name = name
return nil
})
}
// Description sets the application description.
func Description(description string) Option {
return PostBuild(func(k *Kong) error {
k.Model.Help = description
return nil
})
}
// TypeMapper registers a mapper to a type.
func TypeMapper(typ reflect.Type, mapper Mapper) Option {
return OptionFunc(func(k *Kong) error {
k.registry.RegisterType(typ, mapper)
return nil
})
}
// KindMapper registers a mapper to a kind.
func KindMapper(kind reflect.Kind, mapper Mapper) Option {
return OptionFunc(func(k *Kong) error {
k.registry.RegisterKind(kind, mapper)
return nil
})
}
// ValueMapper registers a mapper to a field value.
func ValueMapper(ptr any, mapper Mapper) Option {
return OptionFunc(func(k *Kong) error {
k.registry.RegisterValue(ptr, mapper)
return nil
})
}
// NamedMapper registers a mapper to a name.
func NamedMapper(name string, mapper Mapper) Option {
return OptionFunc(func(k *Kong) error {
k.registry.RegisterName(name, mapper)
return nil
})
}
// Writers overrides the default writers. Useful for testing or interactive use.
func Writers(stdout, stderr io.Writer) Option {
return OptionFunc(func(k *Kong) error {
k.Stdout = stdout
k.Stderr = stderr
return nil
})
}
// Bind binds values for hooks and Run() function arguments.
//
// Any arguments passed will be available to the receiving hook functions, but may be omitted. Additionally, *Kong and
// the current *Context will also be made available.
//
// There are two hook points:
//
// BeforeApply(...) error
// AfterApply(...) error
//
// Called before validation/assignment, and immediately after validation/assignment, respectively.
func Bind(args ...any) Option {
return OptionFunc(func(k *Kong) error {
k.bindings.add(args...)
return nil
})
}
// BindTo allows binding of implementations to interfaces.
//
// BindTo(impl, (*iface)(nil))
func BindTo(impl, iface any) Option {
return OptionFunc(func(k *Kong) error {
k.bindings.addTo(impl, iface)
return nil
})
}
// BindToProvider binds an injected value to a provider function.
//
// The provider function must have one of the following signatures:
//
// func(...) (T, error)
// func(...) T
//
// Where arguments to the function are injected by Kong.
//
// This is useful when the Run() function of different commands require different values that may
// not all be initialisable from the main() function.
func BindToProvider(provider any) Option {
return OptionFunc(func(k *Kong) error {
return k.bindings.addProvider(provider, false /* singleton */)
})
}
// BindSingletonProvider binds an injected value to a provider function.
// The provider function must have the signature:
//
// func(...) (T, error)
// func(...) T
//
// Unlike [BindToProvider], the provider function will only be called
// at most once, and the result will be cached and reused
// across multiple recipients of the injected value.
func BindSingletonProvider(provider any) Option {
return OptionFunc(func(k *Kong) error {
return k.bindings.addProvider(provider, true /* singleton */)
})
}
// Help printer to use.
func Help(help HelpPrinter) Option {
return OptionFunc(func(k *Kong) error {
k.help = help
return nil
})
}
// ShortHelp configures the short usage message.
//
// It should be used together with kong.ShortUsageOnError() to display a
// custom short usage message on errors.
func ShortHelp(shortHelp HelpPrinter) Option {
return OptionFunc(func(k *Kong) error {
k.shortHelp = shortHelp
return nil
})
}
// HelpFormatter configures how the help text is formatted.
//
// Deprecated: Use ValueFormatter() instead.
func HelpFormatter(helpFormatter HelpValueFormatter) Option {
return OptionFunc(func(k *Kong) error {
k.helpFormatter = helpFormatter
return nil
})
}
// ValueFormatter configures how the help text is formatted.
func ValueFormatter(helpFormatter HelpValueFormatter) Option {
return OptionFunc(func(k *Kong) error {
k.helpFormatter = helpFormatter
return nil
})
}
// ConfigureHelp sets the HelpOptions to use for printing help.
func ConfigureHelp(options HelpOptions) Option {
return OptionFunc(func(k *Kong) error {
k.helpOptions = options
return nil
})
}
// AutoGroup automatically assigns groups to flags.
func AutoGroup(format func(parent Visitable, flag *Flag) *Group) Option {
return PostBuild(func(kong *Kong) error {
parents := []Visitable{kong.Model}
return Visit(kong.Model, func(node Visitable, next Next) error {
if flag, ok := node.(*Flag); ok && flag.Group == nil {
flag.Group = format(parents[len(parents)-1], flag)
}
parents = append(parents, node)
defer func() { parents = parents[:len(parents)-1] }()
return next(nil)
})
})
}
// Groups associates `group` field tags with group metadata.
//
// This option is used to simplify Kong tags while providing
// rich group information such as title and optional description.
//
// Each key in the "groups" map corresponds to the value of a
// `group` Kong tag, while the first line of the value will be
// the title, and subsequent lines if any will be the description of
// the group.
//
// See also ExplicitGroups for a more structured alternative.
type Groups map[string]string
func (g Groups) Apply(k *Kong) error { //nolint: revive
for key, info := range g {
lines := strings.Split(info, "\n")
title := strings.TrimSpace(lines[0])
description := ""
if len(lines) > 1 {
description = strings.TrimSpace(strings.Join(lines[1:], "\n"))
}
k.groups = append(k.groups, Group{
Key: key,
Title: title,
Description: description,
})
}
return nil
}
// ExplicitGroups associates `group` field tags with their metadata.
//
// It can be used to provide a title or header to a command or flag group.
func ExplicitGroups(groups []Group) Option {
return OptionFunc(func(k *Kong) error {
k.groups = groups
return nil
})
}
// UsageOnError configures Kong to display context-sensitive usage if FatalIfErrorf is called with an error.
func UsageOnError() Option {
return OptionFunc(func(k *Kong) error {
k.usageOnError = fullUsage
return nil
})
}
// ShortUsageOnError configures Kong to display context-sensitive short
// usage if FatalIfErrorf is called with an error. The default short
// usage message can be overridden with kong.ShortHelp(...).
func ShortUsageOnError() Option {
return OptionFunc(func(k *Kong) error {
k.usageOnError = shortUsage
return nil
})
}
// ClearResolvers clears all existing resolvers.
func ClearResolvers() Option {
return OptionFunc(func(k *Kong) error {
k.resolvers = nil
return nil
})
}
// Resolvers registers flag resolvers.
func Resolvers(resolvers ...Resolver) Option {
return OptionFunc(func(k *Kong) error {
k.resolvers = append(k.resolvers, resolvers...)
return nil
})
}
// IgnoreFields will cause kong.New() to skip field names that match any
// of the provided regex patterns. This is useful if you are not able to add a
// kong="-" struct tag to a struct/element before the call to New.
//
// Example: When referencing protoc generated structs, you will likely want to
// ignore/skip XXX_* fields.
func IgnoreFields(regexes ...string) Option {
return OptionFunc(func(k *Kong) error {
for _, r := range regexes {
if r == "" {
return errors.New("regex input cannot be empty")
}
re, err := regexp.Compile(r)
if err != nil {
return fmt.Errorf("unable to compile regex: %v", err)
}
k.ignoreFields = append(k.ignoreFields, re)
}
return nil
})
}
// ConfigurationLoader is a function that builds a resolver from a file.
type ConfigurationLoader func(r io.Reader) (Resolver, error)
// Configuration provides Kong with support for loading defaults from a set of configuration files.
//
// Paths will be opened in order, and "loader" will be used to provide a Resolver which is registered with Kong.
//
// Note: The JSON function is a ConfigurationLoader.
//
// ~ and variable expansion will occur on the provided paths.
func Configuration(loader ConfigurationLoader, paths ...string) Option {
return OptionFunc(func(k *Kong) error {
k.loader = loader
for _, path := range paths {
f, err := os.Open(ExpandPath(path))
if err != nil {
if os.IsNotExist(err) || os.IsPermission(err) {
continue
}
return err
}
f.Close()
resolver, err := k.LoadConfig(path)
if err != nil {
return fmt.Errorf("%s: %v", path, err)
}
if resolver != nil {
k.resolvers = append(k.resolvers, resolver)
}
}
return nil
})
}
// ExpandPath is a helper function to expand a relative or home-relative path to an absolute path.
//
// eg. ~/.someconf -> /home/alec/.someconf
func ExpandPath(path string) string {
if filepath.IsAbs(path) {
return path
}
if strings.HasPrefix(path, "~/") {
user, err := user.Current()
if err != nil {
return path
}
return filepath.Join(user.HomeDir, path[2:])
}
abspath, err := filepath.Abs(path)
if err != nil {
return path
}
return abspath
}
func siftStrings(ss []string, filter func(s string) bool) []string {
i := 0
ss = append([]string(nil), ss...)
for _, s := range ss {
if filter(s) {
ss[i] = s
i++
}
}
return ss[0:i]
}
// DefaultEnvars option inits environment names for flags.
// The name will not generate if tag "env" is "-".
// Predefined environment variables are skipped.
//
// For example:
//
// --some.value -> PREFIX_SOME_VALUE
func DefaultEnvars(prefix string) Option {
processFlag := func(flag *Flag) {
switch env := flag.Envs; {
case flag.Name == "help":
return
case len(env) == 1 && env[0] == "-":
flag.Envs = nil
return
case len(env) > 0:
return
}
replacer := strings.NewReplacer("-", "_", ".", "_")
names := append([]string{prefix}, camelCase(replacer.Replace(flag.Name))...)
names = siftStrings(names, func(s string) bool { return !(s == "_" || strings.TrimSpace(s) == "") })
name := strings.ToUpper(strings.Join(names, "_"))
flag.Envs = append(flag.Envs, name)
flag.Value.Tag.Envs = append(flag.Value.Tag.Envs, name)
}
var processNode func(node *Node)
processNode = func(node *Node) {
for _, flag := range node.Flags {
processFlag(flag)
}
for _, node := range node.Children {
processNode(node)
}
}
return PostBuild(func(k *Kong) error {
processNode(k.Model.Node)
return nil
})
}
// FlagNamer allows you to override the default kebab-case automated flag name generation.
func FlagNamer(namer func(fieldName string) string) Option {
return OptionFunc(func(k *Kong) error {
k.flagNamer = namer
return nil
})
}

18
vendor/github.com/alecthomas/kong/renovate.json5 generated vendored Normal file
View File

@@ -0,0 +1,18 @@
{
$schema: "https://docs.renovatebot.com/renovate-schema.json",
extends: [
"config:recommended",
":semanticCommits",
":semanticCommitTypeAll(chore)",
":semanticCommitScope(deps)",
"group:allNonMajor",
"schedule:earlyMondays", // Run once a week.
],
packageRules: [
{
"matchPackageNames": ["golangci-lint"],
"matchManagers": ["hermit"],
"enabled": false
},
]
}

68
vendor/github.com/alecthomas/kong/resolver.go generated vendored Normal file
View File

@@ -0,0 +1,68 @@
package kong
import (
"encoding/json"
"io"
"strings"
)
// A Resolver resolves a Flag value from an external source.
type Resolver interface {
// Validate configuration against Application.
//
// This can be used to validate that all provided configuration is valid within this application.
Validate(app *Application) error
// Resolve the value for a Flag.
Resolve(context *Context, parent *Path, flag *Flag) (any, error)
}
// ResolverFunc is a convenience type for non-validating Resolvers.
type ResolverFunc func(context *Context, parent *Path, flag *Flag) (any, error)
var _ Resolver = ResolverFunc(nil)
func (r ResolverFunc) Resolve(context *Context, parent *Path, flag *Flag) (any, error) { //nolint: revive
return r(context, parent, flag)
}
func (r ResolverFunc) Validate(app *Application) error { return nil } //nolint: revive
// JSON returns a Resolver that retrieves values from a JSON source.
//
// Flag names are used as JSON keys indirectly, by tring snake_case and camelCase variants.
func JSON(r io.Reader) (Resolver, error) {
values := map[string]any{}
err := json.NewDecoder(r).Decode(&values)
if err != nil {
return nil, err
}
var f ResolverFunc = func(context *Context, parent *Path, flag *Flag) (any, error) {
name := strings.ReplaceAll(flag.Name, "-", "_")
snakeCaseName := snakeCase(flag.Name)
raw, ok := values[name]
if ok {
return raw, nil
} else if raw, ok = values[snakeCaseName]; ok {
return raw, nil
}
raw = values
for _, part := range strings.Split(name, ".") {
if values, ok := raw.(map[string]any); ok {
raw, ok = values[part]
if !ok {
return nil, nil
}
} else {
return nil, nil
}
}
return raw, nil
}
return f, nil
}
func snakeCase(name string) string {
name = strings.Join(strings.Split(strings.Title(name), "-"), "") //nolint:staticcheck // Unicode punctuation not an issue
return strings.ToLower(name[:1]) + name[1:]
}

236
vendor/github.com/alecthomas/kong/scanner.go generated vendored Normal file
View File

@@ -0,0 +1,236 @@
package kong
import (
"fmt"
"strings"
)
// TokenType is the type of a token.
type TokenType int
// Token types.
const (
UntypedToken TokenType = iota
EOLToken
FlagToken // --<flag>
FlagValueToken // =<value>
ShortFlagToken // -<short>[<tail]
ShortFlagTailToken // <tail>
PositionalArgumentToken // <arg>
)
func (t TokenType) String() string {
switch t {
case UntypedToken:
return "untyped"
case EOLToken:
return "<EOL>"
case FlagToken: // --<flag>
return "long flag"
case FlagValueToken: // =<value>
return "flag value"
case ShortFlagToken: // -<short>[<tail]
return "short flag"
case ShortFlagTailToken: // <tail>
return "short flag remainder"
case PositionalArgumentToken: // <arg>
return "positional argument"
}
panic("unsupported type")
}
// Token created by Scanner.
type Token struct {
Value any
Type TokenType
}
func (t Token) String() string {
switch t.Type {
case FlagToken:
return fmt.Sprintf("--%v", t.Value)
case ShortFlagToken:
return fmt.Sprintf("-%v", t.Value)
case EOLToken:
return "EOL"
default:
return fmt.Sprintf("%v", t.Value)
}
}
// IsEOL returns true if this Token is past the end of the line.
func (t Token) IsEOL() bool {
return t.Type == EOLToken
}
// IsAny returns true if the token's type is any of those provided.
func (t TokenType) IsAny(types ...TokenType) bool {
for _, typ := range types {
if t == typ {
return true
}
}
return false
}
// InferredType tries to infer the type of a token.
func (t Token) InferredType() TokenType {
if t.Type != UntypedToken {
return t.Type
}
if v, ok := t.Value.(string); ok {
if strings.HasPrefix(v, "--") { //nolint: gocritic
return FlagToken
} else if v == "-" {
return PositionalArgumentToken
} else if strings.HasPrefix(v, "-") {
return ShortFlagToken
}
}
return t.Type
}
// IsValue returns true if token is usable as a parseable value.
//
// A parseable value is either a value typed token, or an untyped token NOT starting with a hyphen.
func (t Token) IsValue() bool {
tt := t.InferredType()
return tt.IsAny(FlagValueToken, ShortFlagTailToken, PositionalArgumentToken) ||
(tt == UntypedToken && !strings.HasPrefix(t.String(), "-"))
}
// Scanner is a stack-based scanner over command-line tokens.
//
// Initially all tokens are untyped. As the parser consumes tokens it assigns types, splits tokens, and pushes them back
// onto the stream.
//
// For example, the token "--foo=bar" will be split into the following by the parser:
//
// [{FlagToken, "foo"}, {FlagValueToken, "bar"}]
type Scanner struct {
allowHyphenated bool
args []Token
}
// ScanAsType creates a new Scanner from args with the given type.
func ScanAsType(ttype TokenType, args ...string) *Scanner {
s := &Scanner{}
for _, arg := range args {
s.args = append(s.args, Token{Value: arg, Type: ttype})
}
return s
}
// Scan creates a new Scanner from args with untyped tokens.
func Scan(args ...string) *Scanner {
return ScanAsType(UntypedToken, args...)
}
// ScanFromTokens creates a new Scanner from a slice of tokens.
func ScanFromTokens(tokens ...Token) *Scanner {
return &Scanner{args: tokens}
}
// AllowHyphenPrefixedParameters enables or disables hyphen-prefixed flag parameters on this Scanner.
//
// Disabled by default.
func (s *Scanner) AllowHyphenPrefixedParameters(enable bool) *Scanner {
s.allowHyphenated = enable
return s
}
// Len returns the number of input arguments.
func (s *Scanner) Len() int {
return len(s.args)
}
// Pop the front token off the Scanner.
func (s *Scanner) Pop() Token {
if len(s.args) == 0 {
return Token{Type: EOLToken}
}
arg := s.args[0]
s.args = s.args[1:]
return arg
}
type expectedError struct {
context string
token Token
}
func (e *expectedError) Error() string {
return fmt.Sprintf("expected %s value but got %q (%s)", e.context, e.token, e.token.InferredType())
}
// PopValue pops a value token, or returns an error.
//
// "context" is used to assist the user if the value can not be popped, eg. "expected <context> value but got <type>"
func (s *Scanner) PopValue(context string) (Token, error) {
t := s.Pop()
if !s.allowHyphenated && !t.IsValue() {
return t, &expectedError{context, t}
}
return t, nil
}
// PopValueInto pops a value token into target or returns an error.
//
// "context" is used to assist the user if the value can not be popped, eg. "expected <context> value but got <type>"
func (s *Scanner) PopValueInto(context string, target any) error {
t, err := s.PopValue(context)
if err != nil {
return err
}
return jsonTranscode(t.Value, target)
}
// PopWhile predicate returns true.
func (s *Scanner) PopWhile(predicate func(Token) bool) (values []Token) {
for predicate(s.Peek()) {
values = append(values, s.Pop())
}
return
}
// PopUntil predicate returns true.
func (s *Scanner) PopUntil(predicate func(Token) bool) (values []Token) {
for !predicate(s.Peek()) {
values = append(values, s.Pop())
}
return
}
// Peek at the next Token or return an EOLToken.
func (s *Scanner) Peek() Token {
if len(s.args) == 0 {
return Token{Type: EOLToken}
}
return s.args[0]
}
// PeekAll remaining tokens
func (s *Scanner) PeekAll() []Token {
return s.args
}
// Push an untyped Token onto the front of the Scanner.
func (s *Scanner) Push(arg any) *Scanner {
s.PushToken(Token{Value: arg})
return s
}
// PushTyped pushes a typed token onto the front of the Scanner.
func (s *Scanner) PushTyped(arg any, typ TokenType) *Scanner {
s.PushToken(Token{Value: arg, Type: typ})
return s
}
// PushToken pushes a preconstructed Token onto the front of the Scanner.
func (s *Scanner) PushToken(token Token) *Scanner {
s.args = append([]Token{token}, s.args...)
return s
}

433
vendor/github.com/alecthomas/kong/tag.go generated vendored Normal file
View File

@@ -0,0 +1,433 @@
package kong
import (
"errors"
"fmt"
"reflect"
"strconv"
"strings"
"unicode/utf8"
)
// PassthroughMode indicates how parameters are passed through when "passthrough" is set.
type PassthroughMode int
const (
// PassThroughModeNone indicates passthrough mode is disabled.
PassThroughModeNone PassthroughMode = iota
// PassThroughModeAll indicates that all parameters, including flags, are passed through. It is the default.
PassThroughModeAll
// PassThroughModePartial will validate flags until the first positional argument is encountered, then pass through all remaining positional arguments.
PassThroughModePartial
)
// Tag represents the parsed state of Kong tags in a struct field tag.
type Tag struct {
Ignored bool // Field is ignored by Kong. ie. kong:"-"
Cmd bool
Arg bool
Required bool
Optional bool
Name string
Help string
Type string
TypeName string
HasDefault bool
Default string
Format string
PlaceHolder string
Envs []string
Short rune
Hidden bool
Sep rune
MapSep rune
Enum string
Group string
Xor []string
And []string
Vars Vars
Prefix string // Optional prefix on anonymous structs. All sub-flags will have this prefix.
EnvPrefix string
XorPrefix string // Optional prefix on XOR/AND groups.
Embed bool
Aliases []string
Negatable string
Passthrough bool // Deprecated: use PassthroughMode instead.
PassthroughMode PassthroughMode
// Storage for all tag keys for arbitrary lookups.
items map[string][]string
}
func (t *Tag) String() string {
out := []string{}
for key, list := range t.items {
for _, value := range list {
out = append(out, fmt.Sprintf("%s:%q", key, value))
}
}
return strings.Join(out, " ")
}
type tagChars struct {
sep, quote, assign rune
needsUnquote bool
}
var kongChars = tagChars{sep: ',', quote: '\'', assign: '=', needsUnquote: false}
var bareChars = tagChars{sep: ' ', quote: '"', assign: ':', needsUnquote: true}
//nolint:gocyclo
func parseTagItems(tagString string, chr tagChars) (map[string][]string, error) {
d := map[string][]string{}
key := []rune{}
value := []rune{}
quotes := false
inKey := true
add := func() error {
// Bare tags are quoted, therefore we need to unquote them in the same fashion reflect.Lookup() (implicitly)
// unquotes "kong tags".
s := string(value)
if chr.needsUnquote && s != "" {
if unquoted, err := strconv.Unquote(fmt.Sprintf(`"%s"`, s)); err == nil {
s = unquoted
} else {
return fmt.Errorf("unquoting tag value `%s`: %w", s, err)
}
}
d[string(key)] = append(d[string(key)], s)
key = []rune{}
value = []rune{}
inKey = true
return nil
}
runes := []rune(tagString)
for idx := 0; idx < len(runes); idx++ {
r := runes[idx]
next := rune(0)
eof := false
if idx < len(runes)-1 {
next = runes[idx+1]
} else {
eof = true
}
if !quotes && r == chr.sep {
if err := add(); err != nil {
return nil, err
}
continue
}
if r == chr.assign && inKey {
inKey = false
continue
}
if r == '\\' {
if next == chr.quote {
idx++
// We need to keep the backslashes, otherwise subsequent unquoting cannot work
if chr.needsUnquote {
value = append(value, r)
}
r = chr.quote
}
} else if r == chr.quote {
if quotes {
quotes = false
if next == chr.sep || eof {
continue
}
return nil, fmt.Errorf("%v has an unexpected char at pos %v", tagString, idx)
}
quotes = true
continue
}
if inKey {
key = append(key, r)
} else {
value = append(value, r)
}
}
if quotes {
return nil, fmt.Errorf("%v is not quoted properly", tagString)
}
if err := add(); err != nil {
return nil, err
}
return d, nil
}
func getTagInfo(tag reflect.StructTag) (string, tagChars) {
s, ok := tag.Lookup("kong")
if ok {
return s, kongChars
}
return string(tag), bareChars
}
func newEmptyTag() *Tag {
return &Tag{items: map[string][]string{}}
}
func tagSplitFn(r rune) bool {
return r == ',' || r == ' '
}
func parseTagString(s string) (*Tag, error) {
items, err := parseTagItems(s, bareChars)
if err != nil {
return nil, err
}
t := &Tag{
items: items,
}
err = hydrateTag(t, nil)
if err != nil {
return nil, fmt.Errorf("%s: %s", s, err)
}
return t, nil
}
func parseTag(parent reflect.Value, ft reflect.StructField) (*Tag, error) {
if ft.Tag.Get("kong") == "-" {
t := newEmptyTag()
t.Ignored = true
return t, nil
}
items := map[string][]string{}
// First use a [Signature] if present
signatureTag, ok := maybeGetSignature(ft.Type)
if ok {
signatureItems, err := parseTagItems(getTagInfo(signatureTag))
if err != nil {
return nil, err
}
items = signatureItems
}
// Next overlay the field's tags.
fieldItems, err := parseTagItems(getTagInfo(ft.Tag))
if err != nil {
return nil, err
}
for key, value := range fieldItems {
// Prepend field tag values
items[key] = append(value, items[key]...)
}
t := &Tag{
items: items,
}
err = hydrateTag(t, ft.Type)
if err != nil {
return nil, failField(parent, ft, "%s", err)
}
return t, nil
}
func hydrateTag(t *Tag, typ reflect.Type) error { //nolint: gocyclo
var typeName string
var isBool bool
var isBoolPtr bool
if typ != nil {
typeName = typ.Name()
isBool = typ.Kind() == reflect.Bool
isBoolPtr = typ.Kind() == reflect.Ptr && typ.Elem().Kind() == reflect.Bool
}
var err error
t.Cmd = t.Has("cmd")
t.Arg = t.Has("arg")
required := t.Has("required")
optional := t.Has("optional")
if required && optional {
return fmt.Errorf("can't specify both required and optional")
}
t.Required = required
t.Optional = optional
t.HasDefault = t.Has("default")
t.Default = t.Get("default")
// Arguments with defaults are always optional.
if t.Arg && t.HasDefault {
t.Optional = true
} else if t.Arg && !optional { // Arguments are required unless explicitly made optional.
t.Required = true
}
t.Name = t.Get("name")
t.Help = t.Get("help")
t.Type = t.Get("type")
t.TypeName = typeName
for _, env := range t.GetAll("env") {
t.Envs = append(t.Envs, strings.FieldsFunc(env, tagSplitFn)...)
}
t.Short, err = t.GetRune("short")
if err != nil && t.Get("short") != "" {
return fmt.Errorf("invalid short flag name %q: %s", t.Get("short"), err)
}
t.Hidden = t.Has("hidden")
t.Format = t.Get("format")
t.Sep, _ = t.GetSep("sep", ',')
t.MapSep, _ = t.GetSep("mapsep", ';')
t.Group = t.Get("group")
for _, xor := range t.GetAll("xor") {
t.Xor = append(t.Xor, strings.FieldsFunc(xor, tagSplitFn)...)
}
for _, and := range t.GetAll("and") {
t.And = append(t.And, strings.FieldsFunc(and, tagSplitFn)...)
}
t.Prefix = t.Get("prefix")
t.EnvPrefix = t.Get("envprefix")
t.XorPrefix = t.Get("xorprefix")
t.Embed = t.Has("embed")
if t.Has("negatable") {
if !isBool && !isBoolPtr {
return fmt.Errorf("negatable can only be set on booleans")
}
negatable := t.Get("negatable")
if negatable == "" {
negatable = negatableDefault // placeholder for default negation of --no-<flag>
}
t.Negatable = negatable
}
aliases := t.Get("aliases")
if len(aliases) > 0 {
t.Aliases = append(t.Aliases, strings.FieldsFunc(aliases, tagSplitFn)...)
}
t.Vars = Vars{}
for _, set := range t.GetAll("set") {
parts := strings.SplitN(set, "=", 2)
if len(parts) == 0 {
return fmt.Errorf("set should be in the form key=value but got %q", set)
}
t.Vars[parts[0]] = parts[1]
}
t.PlaceHolder = t.Get("placeholder")
t.Enum = t.Get("enum")
scalarType := typ == nil || !(typ.Kind() == reflect.Slice || typ.Kind() == reflect.Map || typ.Kind() == reflect.Ptr)
if t.Enum != "" && !(t.Required || t.HasDefault) && scalarType {
return fmt.Errorf("enum value is only valid if it is either required or has a valid default value")
}
passthrough := t.Has("passthrough")
if passthrough && !t.Arg && !t.Cmd {
return fmt.Errorf("passthrough only makes sense for positional arguments or commands")
}
t.Passthrough = passthrough
if t.Passthrough {
passthroughMode := t.Get("passthrough")
switch passthroughMode {
case "partial":
t.PassthroughMode = PassThroughModePartial
case "all", "":
t.PassthroughMode = PassThroughModeAll
default:
return fmt.Errorf("invalid passthrough mode %q, must be one of 'partial' or 'all'", passthroughMode)
}
}
return nil
}
// Has returns true if the tag contained the given key.
func (t *Tag) Has(k string) bool {
_, ok := t.items[k]
return ok
}
// Get returns the value of the given tag.
//
// Note that this will return the empty string if the tag is missing.
func (t *Tag) Get(k string) string {
values := t.items[k]
if len(values) == 0 {
return ""
}
return values[0]
}
// GetAll returns all encountered values for a tag, in the case of multiple occurrences.
func (t *Tag) GetAll(k string) []string {
return t.items[k]
}
// GetBool returns true if the given tag looks like a boolean truth string.
func (t *Tag) GetBool(k string) (bool, error) {
return strconv.ParseBool(t.Get(k))
}
// GetFloat parses the given tag as a float64.
func (t *Tag) GetFloat(k string) (float64, error) {
return strconv.ParseFloat(t.Get(k), 64)
}
// GetInt parses the given tag as an int64.
func (t *Tag) GetInt(k string) (int64, error) {
return strconv.ParseInt(t.Get(k), 10, 64)
}
// GetRune parses the given tag as a rune.
func (t *Tag) GetRune(k string) (rune, error) {
value := t.Get(k)
r, size := utf8.DecodeRuneInString(value)
if r == utf8.RuneError || size < len(value) {
return 0, errors.New("invalid rune")
}
return r, nil
}
// GetSep parses the given tag as a rune separator, allowing for a default or none.
// The separator is returned, or -1 if "none" is specified. If the tag value is an
// invalid utf8 sequence, the default rune is returned as well as an error. If the
// tag value is more than one rune, the first rune is returned as well as an error.
func (t *Tag) GetSep(k string, dflt rune) (rune, error) {
tv := t.Get(k)
if tv == "none" {
return -1, nil
} else if tv == "" {
return dflt, nil
}
r, size := utf8.DecodeRuneInString(tv)
if r == utf8.RuneError {
return dflt, fmt.Errorf(`%v:"%v" has a rune error`, k, tv)
} else if size != len(tv) {
return r, fmt.Errorf(`%v:"%v" is more than a single rune`, k, tv)
}
return r, nil
}
// Signature allows flags, args and commands to supply a default set of tags,
// that can be overridden by the field itself.
type Signature interface {
// Signature returns default tags for the flag, arg or command.
//
// eg. `name:"migrate" help:"Run migrations" aliases:"mig,mg"`.
Signature() string
}
var signatureOverrideType = reflect.TypeOf((*Signature)(nil)).Elem()
func maybeGetSignature(t reflect.Type) (reflect.StructTag, bool) {
ut := t
if ut.Kind() == reflect.Pointer {
ut = ut.Elem()
}
ptr := reflect.New(ut)
var sig string
for _, v := range []reflect.Value{ptr, ptr.Elem()} {
if v.Type().Implements(signatureOverrideType) {
sig = v.Interface().(Signature).Signature() //nolint:forcetypeassert
break
}
}
sig = strings.TrimSpace(sig)
if sig == "" {
return "", false
}
return reflect.StructTag(sig), true
}

57
vendor/github.com/alecthomas/kong/util.go generated vendored Normal file
View File

@@ -0,0 +1,57 @@
package kong
import (
"fmt"
"os"
"reflect"
)
// ConfigFlag uses the configured (via kong.Configuration(loader)) configuration loader to load configuration
// from a file specified by a flag.
//
// Use this as a flag value to support loading of custom configuration via a flag.
type ConfigFlag string
// BeforeResolve adds a resolver.
func (c ConfigFlag) BeforeResolve(kong *Kong, ctx *Context, trace *Path) error {
if kong.loader == nil {
return fmt.Errorf("kong must be configured with kong.Configuration(...)")
}
path := string(ctx.FlagValue(trace.Flag).(ConfigFlag)) //nolint
resolver, err := kong.LoadConfig(path)
if err != nil {
return err
}
ctx.AddResolver(resolver)
return nil
}
// VersionFlag is a flag type that can be used to display a version number, stored in the "version" variable.
type VersionFlag bool
// BeforeReset writes the version variable and terminates with a 0 exit status.
func (v VersionFlag) BeforeReset(app *Kong, vars Vars) error {
fmt.Fprintln(app.Stdout, vars["version"])
app.Exit(0)
return nil
}
// ChangeDirFlag changes the current working directory to a path specified by a flag
// early in the parsing process, changing how other flags resolve relative paths.
//
// Use this flag to provide a "git -C" like functionality.
//
// It is not compatible with custom named decoders, e.g., existingdir.
type ChangeDirFlag string
// Decode is used to create a side effect of changing the current working directory.
func (c ChangeDirFlag) Decode(ctx *DecodeContext) error {
var path string
err := ctx.Scan.PopValueInto("string", &path)
if err != nil {
return err
}
path = ExpandPath(path)
ctx.Value.Target.Set(reflect.ValueOf(ChangeDirFlag(path)))
return os.Chdir(path)
}

58
vendor/github.com/alecthomas/kong/visit.go generated vendored Normal file
View File

@@ -0,0 +1,58 @@
package kong
import (
"fmt"
)
// Next should be called by Visitor to proceed with the walk.
//
// The walk will terminate if "err" is non-nil.
type Next func(err error) error
// Visitor can be used to walk all nodes in the model.
type Visitor func(node Visitable, next Next) error
// Visit all nodes.
func Visit(node Visitable, visitor Visitor) error {
return visitor(node, func(err error) error {
if err != nil {
return err
}
switch node := node.(type) {
case *Application:
return visitNodeChildren(node.Node, visitor)
case *Node:
return visitNodeChildren(node, visitor)
case *Value:
case *Flag:
return Visit(node.Value, visitor)
default:
panic(fmt.Sprintf("unsupported node type %T", node))
}
return nil
})
}
func visitNodeChildren(node *Node, visitor Visitor) error {
if node.Argument != nil {
if err := Visit(node.Argument, visitor); err != nil {
return err
}
}
for _, flag := range node.Flags {
if err := Visit(flag, visitor); err != nil {
return err
}
}
for _, pos := range node.Positional {
if err := Visit(pos, visitor); err != nil {
return err
}
}
for _, child := range node.Children {
if err := Visit(child, visitor); err != nil {
return err
}
}
return nil
}

27
vendor/github.com/bahlo/generic-list-go/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,27 @@
Copyright (c) 2009 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.

5
vendor/github.com/bahlo/generic-list-go/README.md generated vendored Normal file
View File

@@ -0,0 +1,5 @@
# generic-list-go [![CI](https://github.com/bahlo/generic-list-go/actions/workflows/ci.yml/badge.svg)](https://github.com/bahlo/generic-list-go/actions/workflows/ci.yml)
Go [container/list](https://pkg.go.dev/container/list) but with generics.
The code is based on `container/list` in `go1.18beta2`.

235
vendor/github.com/bahlo/generic-list-go/list.go generated vendored Normal file
View File

@@ -0,0 +1,235 @@
// Copyright 2009 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 list implements a doubly linked list.
//
// To iterate over a list (where l is a *List):
// for e := l.Front(); e != nil; e = e.Next() {
// // do something with e.Value
// }
//
package list
// Element is an element of a linked list.
type Element[T any] struct {
// Next and previous pointers in the doubly-linked list of elements.
// To simplify the implementation, internally a list l is implemented
// as a ring, such that &l.root is both the next element of the last
// list element (l.Back()) and the previous element of the first list
// element (l.Front()).
next, prev *Element[T]
// The list to which this element belongs.
list *List[T]
// The value stored with this element.
Value T
}
// Next returns the next list element or nil.
func (e *Element[T]) Next() *Element[T] {
if p := e.next; e.list != nil && p != &e.list.root {
return p
}
return nil
}
// Prev returns the previous list element or nil.
func (e *Element[T]) Prev() *Element[T] {
if p := e.prev; e.list != nil && p != &e.list.root {
return p
}
return nil
}
// List represents a doubly linked list.
// The zero value for List is an empty list ready to use.
type List[T any] struct {
root Element[T] // sentinel list element, only &root, root.prev, and root.next are used
len int // current list length excluding (this) sentinel element
}
// Init initializes or clears list l.
func (l *List[T]) Init() *List[T] {
l.root.next = &l.root
l.root.prev = &l.root
l.len = 0
return l
}
// New returns an initialized list.
func New[T any]() *List[T] { return new(List[T]).Init() }
// Len returns the number of elements of list l.
// The complexity is O(1).
func (l *List[T]) Len() int { return l.len }
// Front returns the first element of list l or nil if the list is empty.
func (l *List[T]) Front() *Element[T] {
if l.len == 0 {
return nil
}
return l.root.next
}
// Back returns the last element of list l or nil if the list is empty.
func (l *List[T]) Back() *Element[T] {
if l.len == 0 {
return nil
}
return l.root.prev
}
// lazyInit lazily initializes a zero List value.
func (l *List[T]) lazyInit() {
if l.root.next == nil {
l.Init()
}
}
// insert inserts e after at, increments l.len, and returns e.
func (l *List[T]) insert(e, at *Element[T]) *Element[T] {
e.prev = at
e.next = at.next
e.prev.next = e
e.next.prev = e
e.list = l
l.len++
return e
}
// insertValue is a convenience wrapper for insert(&Element{Value: v}, at).
func (l *List[T]) insertValue(v T, at *Element[T]) *Element[T] {
return l.insert(&Element[T]{Value: v}, at)
}
// remove removes e from its list, decrements l.len
func (l *List[T]) remove(e *Element[T]) {
e.prev.next = e.next
e.next.prev = e.prev
e.next = nil // avoid memory leaks
e.prev = nil // avoid memory leaks
e.list = nil
l.len--
}
// move moves e to next to at.
func (l *List[T]) move(e, at *Element[T]) {
if e == at {
return
}
e.prev.next = e.next
e.next.prev = e.prev
e.prev = at
e.next = at.next
e.prev.next = e
e.next.prev = e
}
// Remove removes e from l if e is an element of list l.
// It returns the element value e.Value.
// The element must not be nil.
func (l *List[T]) Remove(e *Element[T]) T {
if e.list == l {
// if e.list == l, l must have been initialized when e was inserted
// in l or l == nil (e is a zero Element) and l.remove will crash
l.remove(e)
}
return e.Value
}
// PushFront inserts a new element e with value v at the front of list l and returns e.
func (l *List[T]) PushFront(v T) *Element[T] {
l.lazyInit()
return l.insertValue(v, &l.root)
}
// PushBack inserts a new element e with value v at the back of list l and returns e.
func (l *List[T]) PushBack(v T) *Element[T] {
l.lazyInit()
return l.insertValue(v, l.root.prev)
}
// InsertBefore inserts a new element e with value v immediately before mark and returns e.
// If mark is not an element of l, the list is not modified.
// The mark must not be nil.
func (l *List[T]) InsertBefore(v T, mark *Element[T]) *Element[T] {
if mark.list != l {
return nil
}
// see comment in List.Remove about initialization of l
return l.insertValue(v, mark.prev)
}
// InsertAfter inserts a new element e with value v immediately after mark and returns e.
// If mark is not an element of l, the list is not modified.
// The mark must not be nil.
func (l *List[T]) InsertAfter(v T, mark *Element[T]) *Element[T] {
if mark.list != l {
return nil
}
// see comment in List.Remove about initialization of l
return l.insertValue(v, mark)
}
// MoveToFront moves element e to the front of list l.
// If e is not an element of l, the list is not modified.
// The element must not be nil.
func (l *List[T]) MoveToFront(e *Element[T]) {
if e.list != l || l.root.next == e {
return
}
// see comment in List.Remove about initialization of l
l.move(e, &l.root)
}
// MoveToBack moves element e to the back of list l.
// If e is not an element of l, the list is not modified.
// The element must not be nil.
func (l *List[T]) MoveToBack(e *Element[T]) {
if e.list != l || l.root.prev == e {
return
}
// see comment in List.Remove about initialization of l
l.move(e, l.root.prev)
}
// MoveBefore moves element e to its new position before mark.
// If e or mark is not an element of l, or e == mark, the list is not modified.
// The element and mark must not be nil.
func (l *List[T]) MoveBefore(e, mark *Element[T]) {
if e.list != l || e == mark || mark.list != l {
return
}
l.move(e, mark.prev)
}
// MoveAfter moves element e to its new position after mark.
// If e or mark is not an element of l, or e == mark, the list is not modified.
// The element and mark must not be nil.
func (l *List[T]) MoveAfter(e, mark *Element[T]) {
if e.list != l || e == mark || mark.list != l {
return
}
l.move(e, mark)
}
// PushBackList inserts a copy of another list at the back of list l.
// The lists l and other may be the same. They must not be nil.
func (l *List[T]) PushBackList(other *List[T]) {
l.lazyInit()
for i, e := other.Len(), other.Front(); i > 0; i, e = i-1, e.Next() {
l.insertValue(e.Value, l.root.prev)
}
}
// PushFrontList inserts a copy of another list at the front of list l.
// The lists l and other may be the same. They must not be nil.
func (l *List[T]) PushFrontList(other *List[T]) {
l.lazyInit()
for i, e := other.Len(), other.Back(); i > 0; i, e = i-1, e.Prev() {
l.insertValue(e.Value, &l.root)
}
}

12
vendor/github.com/buger/jsonparser/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,12 @@
*.test
*.out
*.mprof
.idea
vendor/github.com/buger/goterm/
prof.cpu
prof.mem

11
vendor/github.com/buger/jsonparser/.travis.yml generated vendored Normal file
View File

@@ -0,0 +1,11 @@
language: go
arch:
- amd64
- ppc64le
go:
- 1.7.x
- 1.8.x
- 1.9.x
- 1.10.x
- 1.11.x
script: go test -v ./.

12
vendor/github.com/buger/jsonparser/Dockerfile generated vendored Normal file
View File

@@ -0,0 +1,12 @@
FROM golang:1.6
RUN go get github.com/Jeffail/gabs
RUN go get github.com/bitly/go-simplejson
RUN go get github.com/pquerna/ffjson
RUN go get github.com/antonholmquist/jason
RUN go get github.com/mreiferson/go-ujson
RUN go get -tags=unsafe -u github.com/ugorji/go/codec
RUN go get github.com/mailru/easyjson
WORKDIR /go/src/github.com/buger/jsonparser
ADD . /go/src/github.com/buger/jsonparser

21
vendor/github.com/buger/jsonparser/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016 Leonid Bugaev
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.

36
vendor/github.com/buger/jsonparser/Makefile generated vendored Normal file
View File

@@ -0,0 +1,36 @@
SOURCE = parser.go
CONTAINER = jsonparser
SOURCE_PATH = /go/src/github.com/buger/jsonparser
BENCHMARK = JsonParser
BENCHTIME = 5s
TEST = .
DRUN = docker run -v `pwd`:$(SOURCE_PATH) -i -t $(CONTAINER)
build:
docker build -t $(CONTAINER) .
race:
$(DRUN) --env GORACE="halt_on_error=1" go test ./. $(ARGS) -v -race -timeout 15s
bench:
$(DRUN) go test $(LDFLAGS) -test.benchmem -bench $(BENCHMARK) ./benchmark/ $(ARGS) -benchtime $(BENCHTIME) -v
bench_local:
$(DRUN) go test $(LDFLAGS) -test.benchmem -bench . $(ARGS) -benchtime $(BENCHTIME) -v
profile:
$(DRUN) go test $(LDFLAGS) -test.benchmem -bench $(BENCHMARK) ./benchmark/ $(ARGS) -memprofile mem.mprof -v
$(DRUN) go test $(LDFLAGS) -test.benchmem -bench $(BENCHMARK) ./benchmark/ $(ARGS) -cpuprofile cpu.out -v
$(DRUN) go test $(LDFLAGS) -test.benchmem -bench $(BENCHMARK) ./benchmark/ $(ARGS) -c
test:
$(DRUN) go test $(LDFLAGS) ./ -run $(TEST) -timeout 10s $(ARGS) -v
fmt:
$(DRUN) go fmt ./...
vet:
$(DRUN) go vet ./.
bash:
$(DRUN) /bin/bash

365
vendor/github.com/buger/jsonparser/README.md generated vendored Normal file
View File

@@ -0,0 +1,365 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/buger/jsonparser)](https://goreportcard.com/report/github.com/buger/jsonparser) ![License](https://img.shields.io/dub/l/vibe-d.svg)
# Alternative JSON parser for Go (10x times faster standard library)
It does not require you to know the structure of the payload (eg. create structs), and allows accessing fields by providing the path to them. It is up to **10 times faster** than standard `encoding/json` package (depending on payload size and usage), **allocates no memory**. See benchmarks below.
## Rationale
Originally I made this for a project that relies on a lot of 3rd party APIs that can be unpredictable and complex.
I love simplicity and prefer to avoid external dependecies. `encoding/json` requires you to know exactly your data structures, or if you prefer to use `map[string]interface{}` instead, it will be very slow and hard to manage.
I investigated what's on the market and found that most libraries are just wrappers around `encoding/json`, there is few options with own parsers (`ffjson`, `easyjson`), but they still requires you to create data structures.
Goal of this project is to push JSON parser to the performance limits and not sacrifice with compliance and developer user experience.
## Example
For the given JSON our goal is to extract the user's full name, number of github followers and avatar.
```go
import "github.com/buger/jsonparser"
...
data := []byte(`{
"person": {
"name": {
"first": "Leonid",
"last": "Bugaev",
"fullName": "Leonid Bugaev"
},
"github": {
"handle": "buger",
"followers": 109
},
"avatars": [
{ "url": "https://avatars1.githubusercontent.com/u/14009?v=3&s=460", "type": "thumbnail" }
]
},
"company": {
"name": "Acme"
}
}`)
// You can specify key path by providing arguments to Get function
jsonparser.Get(data, "person", "name", "fullName")
// There is `GetInt` and `GetBoolean` helpers if you exactly know key data type
jsonparser.GetInt(data, "person", "github", "followers")
// When you try to get object, it will return you []byte slice pointer to data containing it
// In `company` it will be `{"name": "Acme"}`
jsonparser.Get(data, "company")
// If the key doesn't exist it will throw an error
var size int64
if value, err := jsonparser.GetInt(data, "company", "size"); err == nil {
size = value
}
// You can use `ArrayEach` helper to iterate items [item1, item2 .... itemN]
jsonparser.ArrayEach(data, func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
fmt.Println(jsonparser.Get(value, "url"))
}, "person", "avatars")
// Or use can access fields by index!
jsonparser.GetString(data, "person", "avatars", "[0]", "url")
// You can use `ObjectEach` helper to iterate objects { "key1":object1, "key2":object2, .... "keyN":objectN }
jsonparser.ObjectEach(data, func(key []byte, value []byte, dataType jsonparser.ValueType, offset int) error {
fmt.Printf("Key: '%s'\n Value: '%s'\n Type: %s\n", string(key), string(value), dataType)
return nil
}, "person", "name")
// The most efficient way to extract multiple keys is `EachKey`
paths := [][]string{
[]string{"person", "name", "fullName"},
[]string{"person", "avatars", "[0]", "url"},
[]string{"company", "url"},
}
jsonparser.EachKey(data, func(idx int, value []byte, vt jsonparser.ValueType, err error){
switch idx {
case 0: // []string{"person", "name", "fullName"}
...
case 1: // []string{"person", "avatars", "[0]", "url"}
...
case 2: // []string{"company", "url"},
...
}
}, paths...)
// For more information see docs below
```
## Need to speedup your app?
I'm available for consulting and can help you push your app performance to the limits. Ping me at: leonsbox@gmail.com.
## Reference
Library API is really simple. You just need the `Get` method to perform any operation. The rest is just helpers around it.
You also can view API at [godoc.org](https://godoc.org/github.com/buger/jsonparser)
### **`Get`**
```go
func Get(data []byte, keys ...string) (value []byte, dataType jsonparser.ValueType, offset int, err error)
```
Receives data structure, and key path to extract value from.
Returns:
* `value` - Pointer to original data structure containing key value, or just empty slice if nothing found or error
* `dataType` - Can be: `NotExist`, `String`, `Number`, `Object`, `Array`, `Boolean` or `Null`
* `offset` - Offset from provided data structure where key value ends. Used mostly internally, for example for `ArrayEach` helper.
* `err` - If the key is not found or any other parsing issue, it should return error. If key not found it also sets `dataType` to `NotExist`
Accepts multiple keys to specify path to JSON value (in case of quering nested structures).
If no keys are provided it will try to extract the closest JSON value (simple ones or object/array), useful for reading streams or arrays, see `ArrayEach` implementation.
Note that keys can be an array indexes: `jsonparser.GetInt("person", "avatars", "[0]", "url")`, pretty cool, yeah?
### **`GetString`**
```go
func GetString(data []byte, keys ...string) (val string, err error)
```
Returns strings properly handing escaped and unicode characters. Note that this will cause additional memory allocations.
### **`GetUnsafeString`**
If you need string in your app, and ready to sacrifice with support of escaped symbols in favor of speed. It returns string mapped to existing byte slice memory, without any allocations:
```go
s, _, := jsonparser.GetUnsafeString(data, "person", "name", "title")
switch s {
case 'CEO':
...
case 'Engineer'
...
...
}
```
Note that `unsafe` here means that your string will exist until GC will free underlying byte slice, for most of cases it means that you can use this string only in current context, and should not pass it anywhere externally: through channels or any other way.
### **`GetBoolean`**, **`GetInt`** and **`GetFloat`**
```go
func GetBoolean(data []byte, keys ...string) (val bool, err error)
func GetFloat(data []byte, keys ...string) (val float64, err error)
func GetInt(data []byte, keys ...string) (val int64, err error)
```
If you know the key type, you can use the helpers above.
If key data type do not match, it will return error.
### **`ArrayEach`**
```go
func ArrayEach(data []byte, cb func(value []byte, dataType jsonparser.ValueType, offset int, err error), keys ...string)
```
Needed for iterating arrays, accepts a callback function with the same return arguments as `Get`.
### **`ObjectEach`**
```go
func ObjectEach(data []byte, callback func(key []byte, value []byte, dataType ValueType, offset int) error, keys ...string) (err error)
```
Needed for iterating object, accepts a callback function. Example:
```go
var handler func([]byte, []byte, jsonparser.ValueType, int) error
handler = func(key []byte, value []byte, dataType jsonparser.ValueType, offset int) error {
//do stuff here
}
jsonparser.ObjectEach(myJson, handler)
```
### **`EachKey`**
```go
func EachKey(data []byte, cb func(idx int, value []byte, dataType jsonparser.ValueType, err error), paths ...[]string)
```
When you need to read multiple keys, and you do not afraid of low-level API `EachKey` is your friend. It read payload only single time, and calls callback function once path is found. For example when you call multiple times `Get`, it has to process payload multiple times, each time you call it. Depending on payload `EachKey` can be multiple times faster than `Get`. Path can use nested keys as well!
```go
paths := [][]string{
[]string{"uuid"},
[]string{"tz"},
[]string{"ua"},
[]string{"st"},
}
var data SmallPayload
jsonparser.EachKey(smallFixture, func(idx int, value []byte, vt jsonparser.ValueType, err error){
switch idx {
case 0:
data.Uuid, _ = value
case 1:
v, _ := jsonparser.ParseInt(value)
data.Tz = int(v)
case 2:
data.Ua, _ = value
case 3:
v, _ := jsonparser.ParseInt(value)
data.St = int(v)
}
}, paths...)
```
### **`Set`**
```go
func Set(data []byte, setValue []byte, keys ...string) (value []byte, err error)
```
Receives existing data structure, key path to set, and value to set at that key. *This functionality is experimental.*
Returns:
* `value` - Pointer to original data structure with updated or added key value.
* `err` - If any parsing issue, it should return error.
Accepts multiple keys to specify path to JSON value (in case of updating or creating nested structures).
Note that keys can be an array indexes: `jsonparser.Set(data, []byte("http://github.com"), "person", "avatars", "[0]", "url")`
### **`Delete`**
```go
func Delete(data []byte, keys ...string) value []byte
```
Receives existing data structure, and key path to delete. *This functionality is experimental.*
Returns:
* `value` - Pointer to original data structure with key path deleted if it can be found. If there is no key path, then the whole data structure is deleted.
Accepts multiple keys to specify path to JSON value (in case of updating or creating nested structures).
Note that keys can be an array indexes: `jsonparser.Delete(data, "person", "avatars", "[0]", "url")`
## What makes it so fast?
* It does not rely on `encoding/json`, `reflection` or `interface{}`, the only real package dependency is `bytes`.
* Operates with JSON payload on byte level, providing you pointers to the original data structure: no memory allocation.
* No automatic type conversions, by default everything is a []byte, but it provides you value type, so you can convert by yourself (there is few helpers included).
* Does not parse full record, only keys you specified
## Benchmarks
There are 3 benchmark types, trying to simulate real-life usage for small, medium and large JSON payloads.
For each metric, the lower value is better. Time/op is in nanoseconds. Values better than standard encoding/json marked as bold text.
Benchmarks run on standard Linode 1024 box.
Compared libraries:
* https://golang.org/pkg/encoding/json
* https://github.com/Jeffail/gabs
* https://github.com/a8m/djson
* https://github.com/bitly/go-simplejson
* https://github.com/antonholmquist/jason
* https://github.com/mreiferson/go-ujson
* https://github.com/ugorji/go/codec
* https://github.com/pquerna/ffjson
* https://github.com/mailru/easyjson
* https://github.com/buger/jsonparser
#### TLDR
If you want to skip next sections we have 2 winner: `jsonparser` and `easyjson`.
`jsonparser` is up to 10 times faster than standard `encoding/json` package (depending on payload size and usage), and almost infinitely (literally) better in memory consumption because it operates with data on byte level, and provide direct slice pointers.
`easyjson` wins in CPU in medium tests and frankly i'm impressed with this package: it is remarkable results considering that it is almost drop-in replacement for `encoding/json` (require some code generation).
It's hard to fully compare `jsonparser` and `easyjson` (or `ffson`), they a true parsers and fully process record, unlike `jsonparser` which parse only keys you specified.
If you searching for replacement of `encoding/json` while keeping structs, `easyjson` is an amazing choice. If you want to process dynamic JSON, have memory constrains, or more control over your data you should try `jsonparser`.
`jsonparser` performance heavily depends on usage, and it works best when you do not need to process full record, only some keys. The more calls you need to make, the slower it will be, in contrast `easyjson` (or `ffjson`, `encoding/json`) parser record only 1 time, and then you can make as many calls as you want.
With great power comes great responsibility! :)
#### Small payload
Each test processes 190 bytes of http log as a JSON record.
It should read multiple fields.
https://github.com/buger/jsonparser/blob/master/benchmark/benchmark_small_payload_test.go
Library | time/op | bytes/op | allocs/op
------ | ------- | -------- | -------
encoding/json struct | 7879 | 880 | 18
encoding/json interface{} | 8946 | 1521 | 38
Jeffail/gabs | 10053 | 1649 | 46
bitly/go-simplejson | 10128 | 2241 | 36
antonholmquist/jason | 27152 | 7237 | 101
github.com/ugorji/go/codec | 8806 | 2176 | 31
mreiferson/go-ujson | **7008** | **1409** | 37
a8m/djson | 3862 | 1249 | 30
pquerna/ffjson | **3769** | **624** | **15**
mailru/easyjson | **2002** | **192** | **9**
buger/jsonparser | **1367** | **0** | **0**
buger/jsonparser (EachKey API) | **809** | **0** | **0**
Winners are ffjson, easyjson and jsonparser, where jsonparser is up to 9.8x faster than encoding/json and 4.6x faster than ffjson, and slightly faster than easyjson.
If you look at memory allocation, jsonparser has no rivals, as it makes no data copy and operates with raw []byte structures and pointers to it.
#### Medium payload
Each test processes a 2.4kb JSON record (based on Clearbit API).
It should read multiple nested fields and 1 array.
https://github.com/buger/jsonparser/blob/master/benchmark/benchmark_medium_payload_test.go
| Library | time/op | bytes/op | allocs/op |
| ------- | ------- | -------- | --------- |
| encoding/json struct | 57749 | 1336 | 29 |
| encoding/json interface{} | 79297 | 10627 | 215 |
| Jeffail/gabs | 83807 | 11202 | 235 |
| bitly/go-simplejson | 88187 | 17187 | 220 |
| antonholmquist/jason | 94099 | 19013 | 247 |
| github.com/ugorji/go/codec | 114719 | 6712 | 152 |
| mreiferson/go-ujson | **56972** | 11547 | 270 |
| a8m/djson | 28525 | 10196 | 198 |
| pquerna/ffjson | **20298** | **856** | **20** |
| mailru/easyjson | **10512** | **336** | **12** |
| buger/jsonparser | **15955** | **0** | **0** |
| buger/jsonparser (EachKey API) | **8916** | **0** | **0** |
The difference between ffjson and jsonparser in CPU usage is smaller, while the memory consumption difference is growing. On the other hand `easyjson` shows remarkable performance for medium payload.
`gabs`, `go-simplejson` and `jason` are based on encoding/json and map[string]interface{} and actually only helpers for unstructured JSON, their performance correlate with `encoding/json interface{}`, and they will skip next round.
`go-ujson` while have its own parser, shows same performance as `encoding/json`, also skips next round. Same situation with `ugorji/go/codec`, but it showed unexpectedly bad performance for complex payloads.
#### Large payload
Each test processes a 24kb JSON record (based on Discourse API)
It should read 2 arrays, and for each item in array get a few fields.
Basically it means processing a full JSON file.
https://github.com/buger/jsonparser/blob/master/benchmark/benchmark_large_payload_test.go
| Library | time/op | bytes/op | allocs/op |
| --- | --- | --- | --- |
| encoding/json struct | 748336 | 8272 | 307 |
| encoding/json interface{} | 1224271 | 215425 | 3395 |
| a8m/djson | 510082 | 213682 | 2845 |
| pquerna/ffjson | **312271** | **7792** | **298** |
| mailru/easyjson | **154186** | **6992** | **288** |
| buger/jsonparser | **85308** | **0** | **0** |
`jsonparser` now is a winner, but do not forget that it is way more lightweight parser than `ffson` or `easyjson`, and they have to parser all the data, while `jsonparser` parse only what you need. All `ffjson`, `easysjon` and `jsonparser` have their own parsing code, and does not depend on `encoding/json` or `interface{}`, thats one of the reasons why they are so fast. `easyjson` also use a bit of `unsafe` package to reduce memory consuption (in theory it can lead to some unexpected GC issue, but i did not tested enough)
Also last benchmark did not included `EachKey` test, because in this particular case we need to read lot of Array values, and using `ArrayEach` is more efficient.
## Questions and support
All bug-reports and suggestions should go though Github Issues.
## Contributing
1. Fork it
2. Create your feature branch (git checkout -b my-new-feature)
3. Commit your changes (git commit -am 'Added some feature')
4. Push to the branch (git push origin my-new-feature)
5. Create new Pull Request
## Development
All my development happens using Docker, and repo include some Make tasks to simplify development.
* `make build` - builds docker image, usually can be called only once
* `make test` - run tests
* `make fmt` - run go fmt
* `make bench` - run benchmarks (if you need to run only single benchmark modify `BENCHMARK` variable in make file)
* `make profile` - runs benchmark and generate 3 files- `cpu.out`, `mem.mprof` and `benchmark.test` binary, which can be used for `go tool pprof`
* `make bash` - enter container (i use it for running `go tool pprof` above)

47
vendor/github.com/buger/jsonparser/bytes.go generated vendored Normal file
View File

@@ -0,0 +1,47 @@
package jsonparser
import (
bio "bytes"
)
// minInt64 '-9223372036854775808' is the smallest representable number in int64
const minInt64 = `9223372036854775808`
// About 2x faster then strconv.ParseInt because it only supports base 10, which is enough for JSON
func parseInt(bytes []byte) (v int64, ok bool, overflow bool) {
if len(bytes) == 0 {
return 0, false, false
}
var neg bool = false
if bytes[0] == '-' {
neg = true
bytes = bytes[1:]
}
var b int64 = 0
for _, c := range bytes {
if c >= '0' && c <= '9' {
b = (10 * v) + int64(c-'0')
} else {
return 0, false, false
}
if overflow = (b < v); overflow {
break
}
v = b
}
if overflow {
if neg && bio.Equal(bytes, []byte(minInt64)) {
return b, true, false
}
return 0, false, true
}
if neg {
return -v, true, false
} else {
return v, true, false
}
}

25
vendor/github.com/buger/jsonparser/bytes_safe.go generated vendored Normal file
View File

@@ -0,0 +1,25 @@
// +build appengine appenginevm
package jsonparser
import (
"strconv"
)
// See fastbytes_unsafe.go for explanation on why *[]byte is used (signatures must be consistent with those in that file)
func equalStr(b *[]byte, s string) bool {
return string(*b) == s
}
func parseFloat(b *[]byte) (float64, error) {
return strconv.ParseFloat(string(*b), 64)
}
func bytesToString(b *[]byte) string {
return string(*b)
}
func StringToBytes(s string) []byte {
return []byte(s)
}

44
vendor/github.com/buger/jsonparser/bytes_unsafe.go generated vendored Normal file
View File

@@ -0,0 +1,44 @@
// +build !appengine,!appenginevm
package jsonparser
import (
"reflect"
"strconv"
"unsafe"
"runtime"
)
//
// The reason for using *[]byte rather than []byte in parameters is an optimization. As of Go 1.6,
// the compiler cannot perfectly inline the function when using a non-pointer slice. That is,
// the non-pointer []byte parameter version is slower than if its function body is manually
// inlined, whereas the pointer []byte version is equally fast to the manually inlined
// version. Instruction count in assembly taken from "go tool compile" confirms this difference.
//
// TODO: Remove hack after Go 1.7 release
//
func equalStr(b *[]byte, s string) bool {
return *(*string)(unsafe.Pointer(b)) == s
}
func parseFloat(b *[]byte) (float64, error) {
return strconv.ParseFloat(*(*string)(unsafe.Pointer(b)), 64)
}
// A hack until issue golang/go#2632 is fixed.
// See: https://github.com/golang/go/issues/2632
func bytesToString(b *[]byte) string {
return *(*string)(unsafe.Pointer(b))
}
func StringToBytes(s string) []byte {
b := make([]byte, 0, 0)
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
bh.Data = sh.Data
bh.Cap = sh.Len
bh.Len = sh.Len
runtime.KeepAlive(s)
return b
}

173
vendor/github.com/buger/jsonparser/escape.go generated vendored Normal file
View File

@@ -0,0 +1,173 @@
package jsonparser
import (
"bytes"
"unicode/utf8"
)
// JSON Unicode stuff: see https://tools.ietf.org/html/rfc7159#section-7
const supplementalPlanesOffset = 0x10000
const highSurrogateOffset = 0xD800
const lowSurrogateOffset = 0xDC00
const basicMultilingualPlaneReservedOffset = 0xDFFF
const basicMultilingualPlaneOffset = 0xFFFF
func combineUTF16Surrogates(high, low rune) rune {
return supplementalPlanesOffset + (high-highSurrogateOffset)<<10 + (low - lowSurrogateOffset)
}
const badHex = -1
func h2I(c byte) int {
switch {
case c >= '0' && c <= '9':
return int(c - '0')
case c >= 'A' && c <= 'F':
return int(c - 'A' + 10)
case c >= 'a' && c <= 'f':
return int(c - 'a' + 10)
}
return badHex
}
// decodeSingleUnicodeEscape decodes a single \uXXXX escape sequence. The prefix \u is assumed to be present and
// is not checked.
// In JSON, these escapes can either come alone or as part of "UTF16 surrogate pairs" that must be handled together.
// This function only handles one; decodeUnicodeEscape handles this more complex case.
func decodeSingleUnicodeEscape(in []byte) (rune, bool) {
// We need at least 6 characters total
if len(in) < 6 {
return utf8.RuneError, false
}
// Convert hex to decimal
h1, h2, h3, h4 := h2I(in[2]), h2I(in[3]), h2I(in[4]), h2I(in[5])
if h1 == badHex || h2 == badHex || h3 == badHex || h4 == badHex {
return utf8.RuneError, false
}
// Compose the hex digits
return rune(h1<<12 + h2<<8 + h3<<4 + h4), true
}
// isUTF16EncodedRune checks if a rune is in the range for non-BMP characters,
// which is used to describe UTF16 chars.
// Source: https://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane
func isUTF16EncodedRune(r rune) bool {
return highSurrogateOffset <= r && r <= basicMultilingualPlaneReservedOffset
}
func decodeUnicodeEscape(in []byte) (rune, int) {
if r, ok := decodeSingleUnicodeEscape(in); !ok {
// Invalid Unicode escape
return utf8.RuneError, -1
} else if r <= basicMultilingualPlaneOffset && !isUTF16EncodedRune(r) {
// Valid Unicode escape in Basic Multilingual Plane
return r, 6
} else if r2, ok := decodeSingleUnicodeEscape(in[6:]); !ok { // Note: previous decodeSingleUnicodeEscape success guarantees at least 6 bytes remain
// UTF16 "high surrogate" without manditory valid following Unicode escape for the "low surrogate"
return utf8.RuneError, -1
} else if r2 < lowSurrogateOffset {
// Invalid UTF16 "low surrogate"
return utf8.RuneError, -1
} else {
// Valid UTF16 surrogate pair
return combineUTF16Surrogates(r, r2), 12
}
}
// backslashCharEscapeTable: when '\X' is found for some byte X, it is to be replaced with backslashCharEscapeTable[X]
var backslashCharEscapeTable = [...]byte{
'"': '"',
'\\': '\\',
'/': '/',
'b': '\b',
'f': '\f',
'n': '\n',
'r': '\r',
't': '\t',
}
// unescapeToUTF8 unescapes the single escape sequence starting at 'in' into 'out' and returns
// how many characters were consumed from 'in' and emitted into 'out'.
// If a valid escape sequence does not appear as a prefix of 'in', (-1, -1) to signal the error.
func unescapeToUTF8(in, out []byte) (inLen int, outLen int) {
if len(in) < 2 || in[0] != '\\' {
// Invalid escape due to insufficient characters for any escape or no initial backslash
return -1, -1
}
// https://tools.ietf.org/html/rfc7159#section-7
switch e := in[1]; e {
case '"', '\\', '/', 'b', 'f', 'n', 'r', 't':
// Valid basic 2-character escapes (use lookup table)
out[0] = backslashCharEscapeTable[e]
return 2, 1
case 'u':
// Unicode escape
if r, inLen := decodeUnicodeEscape(in); inLen == -1 {
// Invalid Unicode escape
return -1, -1
} else {
// Valid Unicode escape; re-encode as UTF8
outLen := utf8.EncodeRune(out, r)
return inLen, outLen
}
}
return -1, -1
}
// unescape unescapes the string contained in 'in' and returns it as a slice.
// If 'in' contains no escaped characters:
// Returns 'in'.
// Else, if 'out' is of sufficient capacity (guaranteed if cap(out) >= len(in)):
// 'out' is used to build the unescaped string and is returned with no extra allocation
// Else:
// A new slice is allocated and returned.
func Unescape(in, out []byte) ([]byte, error) {
firstBackslash := bytes.IndexByte(in, '\\')
if firstBackslash == -1 {
return in, nil
}
// Get a buffer of sufficient size (allocate if needed)
if cap(out) < len(in) {
out = make([]byte, len(in))
} else {
out = out[0:len(in)]
}
// Copy the first sequence of unescaped bytes to the output and obtain a buffer pointer (subslice)
copy(out, in[:firstBackslash])
in = in[firstBackslash:]
buf := out[firstBackslash:]
for len(in) > 0 {
// Unescape the next escaped character
inLen, bufLen := unescapeToUTF8(in, buf)
if inLen == -1 {
return nil, MalformedStringEscapeError
}
in = in[inLen:]
buf = buf[bufLen:]
// Copy everything up until the next backslash
nextBackslash := bytes.IndexByte(in, '\\')
if nextBackslash == -1 {
copy(buf, in)
buf = buf[len(in):]
break
} else {
copy(buf, in[:nextBackslash])
buf = buf[nextBackslash:]
in = in[nextBackslash:]
}
}
// Trim the out buffer to the amount that was actually emitted
return out[:len(out)-len(buf)], nil
}

117
vendor/github.com/buger/jsonparser/fuzz.go generated vendored Normal file
View File

@@ -0,0 +1,117 @@
package jsonparser
func FuzzParseString(data []byte) int {
r, err := ParseString(data)
if err != nil || r == "" {
return 0
}
return 1
}
func FuzzEachKey(data []byte) int {
paths := [][]string{
{"name"},
{"order"},
{"nested", "a"},
{"nested", "b"},
{"nested2", "a"},
{"nested", "nested3", "b"},
{"arr", "[1]", "b"},
{"arrInt", "[3]"},
{"arrInt", "[5]"},
{"nested"},
{"arr", "["},
{"a\n", "b\n"},
}
EachKey(data, func(idx int, value []byte, vt ValueType, err error) {}, paths...)
return 1
}
func FuzzDelete(data []byte) int {
Delete(data, "test")
return 1
}
func FuzzSet(data []byte) int {
_, err := Set(data, []byte(`"new value"`), "test")
if err != nil {
return 0
}
return 1
}
func FuzzObjectEach(data []byte) int {
_ = ObjectEach(data, func(key, value []byte, valueType ValueType, off int) error {
return nil
})
return 1
}
func FuzzParseFloat(data []byte) int {
_, err := ParseFloat(data)
if err != nil {
return 0
}
return 1
}
func FuzzParseInt(data []byte) int {
_, err := ParseInt(data)
if err != nil {
return 0
}
return 1
}
func FuzzParseBool(data []byte) int {
_, err := ParseBoolean(data)
if err != nil {
return 0
}
return 1
}
func FuzzTokenStart(data []byte) int {
_ = tokenStart(data)
return 1
}
func FuzzGetString(data []byte) int {
_, err := GetString(data, "test")
if err != nil {
return 0
}
return 1
}
func FuzzGetFloat(data []byte) int {
_, err := GetFloat(data, "test")
if err != nil {
return 0
}
return 1
}
func FuzzGetInt(data []byte) int {
_, err := GetInt(data, "test")
if err != nil {
return 0
}
return 1
}
func FuzzGetBoolean(data []byte) int {
_, err := GetBoolean(data, "test")
if err != nil {
return 0
}
return 1
}
func FuzzGetUnsafeString(data []byte) int {
_, err := GetUnsafeString(data, "test")
if err != nil {
return 0
}
return 1
}

47
vendor/github.com/buger/jsonparser/oss-fuzz-build.sh generated vendored Normal file
View File

@@ -0,0 +1,47 @@
#!/bin/bash -eu
git clone https://github.com/dvyukov/go-fuzz-corpus
zip corpus.zip go-fuzz-corpus/json/corpus/*
cp corpus.zip $OUT/fuzzparsestring_seed_corpus.zip
compile_go_fuzzer github.com/buger/jsonparser FuzzParseString fuzzparsestring
cp corpus.zip $OUT/fuzzeachkey_seed_corpus.zip
compile_go_fuzzer github.com/buger/jsonparser FuzzEachKey fuzzeachkey
cp corpus.zip $OUT/fuzzdelete_seed_corpus.zip
compile_go_fuzzer github.com/buger/jsonparser FuzzDelete fuzzdelete
cp corpus.zip $OUT/fuzzset_seed_corpus.zip
compile_go_fuzzer github.com/buger/jsonparser FuzzSet fuzzset
cp corpus.zip $OUT/fuzzobjecteach_seed_corpus.zip
compile_go_fuzzer github.com/buger/jsonparser FuzzObjectEach fuzzobjecteach
cp corpus.zip $OUT/fuzzparsefloat_seed_corpus.zip
compile_go_fuzzer github.com/buger/jsonparser FuzzParseFloat fuzzparsefloat
cp corpus.zip $OUT/fuzzparseint_seed_corpus.zip
compile_go_fuzzer github.com/buger/jsonparser FuzzParseInt fuzzparseint
cp corpus.zip $OUT/fuzzparsebool_seed_corpus.zip
compile_go_fuzzer github.com/buger/jsonparser FuzzParseBool fuzzparsebool
cp corpus.zip $OUT/fuzztokenstart_seed_corpus.zip
compile_go_fuzzer github.com/buger/jsonparser FuzzTokenStart fuzztokenstart
cp corpus.zip $OUT/fuzzgetstring_seed_corpus.zip
compile_go_fuzzer github.com/buger/jsonparser FuzzGetString fuzzgetstring
cp corpus.zip $OUT/fuzzgetfloat_seed_corpus.zip
compile_go_fuzzer github.com/buger/jsonparser FuzzGetFloat fuzzgetfloat
cp corpus.zip $OUT/fuzzgetint_seed_corpus.zip
compile_go_fuzzer github.com/buger/jsonparser FuzzGetInt fuzzgetint
cp corpus.zip $OUT/fuzzgetboolean_seed_corpus.zip
compile_go_fuzzer github.com/buger/jsonparser FuzzGetBoolean fuzzgetboolean
cp corpus.zip $OUT/fuzzgetunsafestring_seed_corpus.zip
compile_go_fuzzer github.com/buger/jsonparser FuzzGetUnsafeString fuzzgetunsafestring

1283
vendor/github.com/buger/jsonparser/parser.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

16
vendor/github.com/davidmz/go-pageant/LICENSE.txt generated vendored Normal file
View File

@@ -0,0 +1,16 @@
Copyright (c) 2014 David Mzareulyan
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.

9
vendor/github.com/davidmz/go-pageant/README.md generated vendored Normal file
View File

@@ -0,0 +1,9 @@
[![GoDoc](https://godoc.org/github.com/davidmz/go-pageant?status.svg)](https://godoc.org/github.com/davidmz/go-pageant)
Package pageant provides an interface to PyTTY pageant.exe utility.
This package is windows-only.
See documentation on [GoDoc](http://godoc.org/github.com/davidmz/go-pageant)
License: MIT

42
vendor/github.com/davidmz/go-pageant/io_windows.go generated vendored Normal file
View File

@@ -0,0 +1,42 @@
package pageant
import (
"io"
"sync"
"golang.org/x/crypto/ssh/agent"
)
// New returns new ssh-agent instance (see http://golang.org/x/crypto/ssh/agent)
func New() agent.Agent {
return agent.NewClient(&conn{})
}
type conn struct {
sync.Mutex
buf []byte
}
func (c *conn) Write(p []byte) (int, error) {
c.Lock()
defer c.Unlock()
resp, err := query(p)
if err != nil {
return 0, err
}
c.buf = append(c.buf, resp...)
return len(p), nil
}
func (c *conn) Read(p []byte) (int, error) {
c.Lock()
defer c.Unlock()
if len(c.buf) == 0 {
return 0, io.EOF
}
n := copy(p, c.buf)
c.buf = c.buf[n:]
return n, nil
}

134
vendor/github.com/davidmz/go-pageant/pageant_windows.go generated vendored Normal file
View File

@@ -0,0 +1,134 @@
// Package pageant provides an interface to PyTTY pageant.exe utility.
// This package is windows-only
package pageant
// see https://github.com/Yasushi/putty/blob/master/windows/winpgntc.c#L155
// see https://github.com/paramiko/paramiko/blob/master/paramiko/win_pageant.py
import (
"encoding/binary"
"errors"
"fmt"
"sync"
"syscall"
"unsafe"
)
// MaxMessageLen defines maximum size of message can be sent to pageant
const MaxMessageLen = 8192
var (
// ErrPageantNotFound returns when pageant process not found
ErrPageantNotFound = errors.New("pageant process not found")
// ErrSendMessage returns when message to pageant cannt be sent
ErrSendMessage = errors.New("error sending message")
// ErrMessageTooLong returns when message is too long (see MaxMessageLen)
ErrMessageTooLong = errors.New("message too long")
// ErrInvalidMessageFormat returns when message have invalid fomat
ErrInvalidMessageFormat = errors.New("invalid message format")
// ErrResponseTooLong returns when response from pageant is too long
ErrResponseTooLong = errors.New("response too long")
)
/////////////////////////
const (
agentCopydataID = 0x804e50ba
wmCopydata = 74
)
type copyData struct {
dwData uintptr
cbData uint32
lpData unsafe.Pointer
}
var (
lock sync.Mutex
winFindWindow = winAPI("user32.dll", "FindWindowW")
winGetCurrentThreadID = winAPI("kernel32.dll", "GetCurrentThreadId")
winSendMessage = winAPI("user32.dll", "SendMessageW")
)
func winAPI(dllName, funcName string) func(...uintptr) (uintptr, uintptr, error) {
proc := syscall.MustLoadDLL(dllName).MustFindProc(funcName)
return func(a ...uintptr) (uintptr, uintptr, error) { return proc.Call(a...) }
}
// Available returns true if Pageant is started
func Available() bool { return pageantWindow() != 0 }
// Query sends message msg to Pageant and returns response or error.
// 'msg' is raw agent request with length prefix
// Response is raw agent response with length prefix
func query(msg []byte) ([]byte, error) {
if len(msg) > MaxMessageLen {
return nil, ErrMessageTooLong
}
msgLen := binary.BigEndian.Uint32(msg[:4])
if len(msg) != int(msgLen)+4 {
return nil, ErrInvalidMessageFormat
}
lock.Lock()
defer lock.Unlock()
paWin := pageantWindow()
if paWin == 0 {
return nil, ErrPageantNotFound
}
thID, _, _ := winGetCurrentThreadID()
mapName := fmt.Sprintf("PageantRequest%08x", thID)
pMapName, _ := syscall.UTF16PtrFromString(mapName)
mmap, err := syscall.CreateFileMapping(syscall.InvalidHandle, nil, syscall.PAGE_READWRITE, 0, MaxMessageLen+4, pMapName)
if err != nil {
return nil, err
}
defer syscall.CloseHandle(mmap)
ptr, err := syscall.MapViewOfFile(mmap, syscall.FILE_MAP_WRITE, 0, 0, 0)
if err != nil {
return nil, err
}
defer syscall.UnmapViewOfFile(ptr)
mmSlice := (*(*[MaxMessageLen]byte)(unsafe.Pointer(ptr)))[:]
copy(mmSlice, msg)
mapNameBytesZ := append([]byte(mapName), 0)
cds := copyData{
dwData: agentCopydataID,
cbData: uint32(len(mapNameBytesZ)),
lpData: unsafe.Pointer(&(mapNameBytesZ[0])),
}
resp, _, _ := winSendMessage(paWin, wmCopydata, 0, uintptr(unsafe.Pointer(&cds)))
if resp == 0 {
return nil, ErrSendMessage
}
respLen := binary.BigEndian.Uint32(mmSlice[:4])
if respLen > MaxMessageLen-4 {
return nil, ErrResponseTooLong
}
respData := make([]byte, respLen+4)
copy(respData, mmSlice)
return respData, nil
}
func pageantWindow() uintptr {
nameP, _ := syscall.UTF16PtrFromString("Pageant")
h, _, _ := winFindWindow(uintptr(unsafe.Pointer(nameP)), uintptr(unsafe.Pointer(nameP)))
return h
}

0
vendor/github.com/felixge/httpsnoop/.gitignore generated vendored Normal file
View File

19
vendor/github.com/felixge/httpsnoop/LICENSE.txt generated vendored Normal file
View File

@@ -0,0 +1,19 @@
Copyright (c) 2016 Felix Geisendörfer (felix@debuggable.com)
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.

10
vendor/github.com/felixge/httpsnoop/Makefile generated vendored Normal file
View File

@@ -0,0 +1,10 @@
.PHONY: ci generate clean
ci: clean generate
go test -race -v ./...
generate:
go generate .
clean:
rm -rf *_generated*.go

95
vendor/github.com/felixge/httpsnoop/README.md generated vendored Normal file
View File

@@ -0,0 +1,95 @@
# httpsnoop
Package httpsnoop provides an easy way to capture http related metrics (i.e.
response time, bytes written, and http status code) from your application's
http.Handlers.
Doing this requires non-trivial wrapping of the http.ResponseWriter interface,
which is also exposed for users interested in a more low-level API.
[![Go Reference](https://pkg.go.dev/badge/github.com/felixge/httpsnoop.svg)](https://pkg.go.dev/github.com/felixge/httpsnoop)
[![Build Status](https://github.com/felixge/httpsnoop/actions/workflows/main.yaml/badge.svg)](https://github.com/felixge/httpsnoop/actions/workflows/main.yaml)
## Usage Example
```go
// myH is your app's http handler, perhaps a http.ServeMux or similar.
var myH http.Handler
// wrappedH wraps myH in order to log every request.
wrappedH := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
m := httpsnoop.CaptureMetrics(myH, w, r)
log.Printf(
"%s %s (code=%d dt=%s written=%d)",
r.Method,
r.URL,
m.Code,
m.Duration,
m.Written,
)
})
http.ListenAndServe(":8080", wrappedH)
```
## Why this package exists
Instrumenting an application's http.Handler is surprisingly difficult.
However if you google for e.g. "capture ResponseWriter status code" you'll find
lots of advise and code examples that suggest it to be a fairly trivial
undertaking. Unfortunately everything I've seen so far has a high chance of
breaking your application.
The main problem is that a `http.ResponseWriter` often implements additional
interfaces such as `http.Flusher`, `http.CloseNotifier`, `http.Hijacker`, `http.Pusher`, and
`io.ReaderFrom`. So the naive approach of just wrapping `http.ResponseWriter`
in your own struct that also implements the `http.ResponseWriter` interface
will hide the additional interfaces mentioned above. This has a high change of
introducing subtle bugs into any non-trivial application.
Another approach I've seen people take is to return a struct that implements
all of the interfaces above. However, that's also problematic, because it's
difficult to fake some of these interfaces behaviors when the underlying
`http.ResponseWriter` doesn't have an implementation. It's also dangerous,
because an application may choose to operate differently, merely because it
detects the presence of these additional interfaces.
This package solves this problem by checking which additional interfaces a
`http.ResponseWriter` implements, returning a wrapped version implementing the
exact same set of interfaces.
Additionally this package properly handles edge cases such as `WriteHeader` not
being called, or called more than once, as well as concurrent calls to
`http.ResponseWriter` methods, and even calls happening after the wrapped
`ServeHTTP` has already returned.
Unfortunately this package is not perfect either. It's possible that it is
still missing some interfaces provided by the go core (let me know if you find
one), and it won't work for applications adding their own interfaces into the
mix. You can however use `httpsnoop.Unwrap(w)` to access the underlying
`http.ResponseWriter` and type-assert the result to its other interfaces.
However, hopefully the explanation above has sufficiently scared you of rolling
your own solution to this problem. httpsnoop may still break your application,
but at least it tries to avoid it as much as possible.
Anyway, the real problem here is that smuggling additional interfaces inside
`http.ResponseWriter` is a problematic design choice, but it probably goes as
deep as the Go language specification itself. But that's okay, I still prefer
Go over the alternatives ;).
## Performance
```
BenchmarkBaseline-8 20000 94912 ns/op
BenchmarkCaptureMetrics-8 20000 95461 ns/op
```
As you can see, using `CaptureMetrics` on a vanilla http.Handler introduces an
overhead of ~500 ns per http request on my machine. However, the margin of
error appears to be larger than that, therefor it should be reasonable to
assume that the overhead introduced by `CaptureMetrics` is absolutely
negligible.
## License
MIT

86
vendor/github.com/felixge/httpsnoop/capture_metrics.go generated vendored Normal file
View File

@@ -0,0 +1,86 @@
package httpsnoop
import (
"io"
"net/http"
"time"
)
// Metrics holds metrics captured from CaptureMetrics.
type Metrics struct {
// Code is the first http response code passed to the WriteHeader func of
// the ResponseWriter. If no such call is made, a default code of 200 is
// assumed instead.
Code int
// Duration is the time it took to execute the handler.
Duration time.Duration
// Written is the number of bytes successfully written by the Write or
// ReadFrom function of the ResponseWriter. ResponseWriters may also write
// data to their underlaying connection directly (e.g. headers), but those
// are not tracked. Therefor the number of Written bytes will usually match
// the size of the response body.
Written int64
}
// CaptureMetrics wraps the given hnd, executes it with the given w and r, and
// returns the metrics it captured from it.
func CaptureMetrics(hnd http.Handler, w http.ResponseWriter, r *http.Request) Metrics {
return CaptureMetricsFn(w, func(ww http.ResponseWriter) {
hnd.ServeHTTP(ww, r)
})
}
// CaptureMetricsFn wraps w and calls fn with the wrapped w and returns the
// resulting metrics. This is very similar to CaptureMetrics (which is just
// sugar on top of this func), but is a more usable interface if your
// application doesn't use the Go http.Handler interface.
func CaptureMetricsFn(w http.ResponseWriter, fn func(http.ResponseWriter)) Metrics {
m := Metrics{Code: http.StatusOK}
m.CaptureMetrics(w, fn)
return m
}
// CaptureMetrics wraps w and calls fn with the wrapped w and updates
// Metrics m with the resulting metrics. This is similar to CaptureMetricsFn,
// but allows one to customize starting Metrics object.
func (m *Metrics) CaptureMetrics(w http.ResponseWriter, fn func(http.ResponseWriter)) {
var (
start = time.Now()
headerWritten bool
hooks = Hooks{
WriteHeader: func(next WriteHeaderFunc) WriteHeaderFunc {
return func(code int) {
next(code)
if !(code >= 100 && code <= 199) && !headerWritten {
m.Code = code
headerWritten = true
}
}
},
Write: func(next WriteFunc) WriteFunc {
return func(p []byte) (int, error) {
n, err := next(p)
m.Written += int64(n)
headerWritten = true
return n, err
}
},
ReadFrom: func(next ReadFromFunc) ReadFromFunc {
return func(src io.Reader) (int64, error) {
n, err := next(src)
headerWritten = true
m.Written += n
return n, err
}
},
}
)
fn(Wrap(w, hooks))
m.Duration += time.Since(start)
}

10
vendor/github.com/felixge/httpsnoop/docs.go generated vendored Normal file
View File

@@ -0,0 +1,10 @@
// Package httpsnoop provides an easy way to capture http related metrics (i.e.
// response time, bytes written, and http status code) from your application's
// http.Handlers.
//
// Doing this requires non-trivial wrapping of the http.ResponseWriter
// interface, which is also exposed for users interested in a more low-level
// API.
package httpsnoop
//go:generate go run codegen/main.go

View File

@@ -0,0 +1,436 @@
// +build go1.8
// Code generated by "httpsnoop/codegen"; DO NOT EDIT.
package httpsnoop
import (
"bufio"
"io"
"net"
"net/http"
)
// HeaderFunc is part of the http.ResponseWriter interface.
type HeaderFunc func() http.Header
// WriteHeaderFunc is part of the http.ResponseWriter interface.
type WriteHeaderFunc func(code int)
// WriteFunc is part of the http.ResponseWriter interface.
type WriteFunc func(b []byte) (int, error)
// FlushFunc is part of the http.Flusher interface.
type FlushFunc func()
// CloseNotifyFunc is part of the http.CloseNotifier interface.
type CloseNotifyFunc func() <-chan bool
// HijackFunc is part of the http.Hijacker interface.
type HijackFunc func() (net.Conn, *bufio.ReadWriter, error)
// ReadFromFunc is part of the io.ReaderFrom interface.
type ReadFromFunc func(src io.Reader) (int64, error)
// PushFunc is part of the http.Pusher interface.
type PushFunc func(target string, opts *http.PushOptions) error
// Hooks defines a set of method interceptors for methods included in
// http.ResponseWriter as well as some others. You can think of them as
// middleware for the function calls they target. See Wrap for more details.
type Hooks struct {
Header func(HeaderFunc) HeaderFunc
WriteHeader func(WriteHeaderFunc) WriteHeaderFunc
Write func(WriteFunc) WriteFunc
Flush func(FlushFunc) FlushFunc
CloseNotify func(CloseNotifyFunc) CloseNotifyFunc
Hijack func(HijackFunc) HijackFunc
ReadFrom func(ReadFromFunc) ReadFromFunc
Push func(PushFunc) PushFunc
}
// Wrap returns a wrapped version of w that provides the exact same interface
// as w. Specifically if w implements any combination of:
//
// - http.Flusher
// - http.CloseNotifier
// - http.Hijacker
// - io.ReaderFrom
// - http.Pusher
//
// The wrapped version will implement the exact same combination. If no hooks
// are set, the wrapped version also behaves exactly as w. Hooks targeting
// methods not supported by w are ignored. Any other hooks will intercept the
// method they target and may modify the call's arguments and/or return values.
// The CaptureMetrics implementation serves as a working example for how the
// hooks can be used.
func Wrap(w http.ResponseWriter, hooks Hooks) http.ResponseWriter {
rw := &rw{w: w, h: hooks}
_, i0 := w.(http.Flusher)
_, i1 := w.(http.CloseNotifier)
_, i2 := w.(http.Hijacker)
_, i3 := w.(io.ReaderFrom)
_, i4 := w.(http.Pusher)
switch {
// combination 1/32
case !i0 && !i1 && !i2 && !i3 && !i4:
return struct {
Unwrapper
http.ResponseWriter
}{rw, rw}
// combination 2/32
case !i0 && !i1 && !i2 && !i3 && i4:
return struct {
Unwrapper
http.ResponseWriter
http.Pusher
}{rw, rw, rw}
// combination 3/32
case !i0 && !i1 && !i2 && i3 && !i4:
return struct {
Unwrapper
http.ResponseWriter
io.ReaderFrom
}{rw, rw, rw}
// combination 4/32
case !i0 && !i1 && !i2 && i3 && i4:
return struct {
Unwrapper
http.ResponseWriter
io.ReaderFrom
http.Pusher
}{rw, rw, rw, rw}
// combination 5/32
case !i0 && !i1 && i2 && !i3 && !i4:
return struct {
Unwrapper
http.ResponseWriter
http.Hijacker
}{rw, rw, rw}
// combination 6/32
case !i0 && !i1 && i2 && !i3 && i4:
return struct {
Unwrapper
http.ResponseWriter
http.Hijacker
http.Pusher
}{rw, rw, rw, rw}
// combination 7/32
case !i0 && !i1 && i2 && i3 && !i4:
return struct {
Unwrapper
http.ResponseWriter
http.Hijacker
io.ReaderFrom
}{rw, rw, rw, rw}
// combination 8/32
case !i0 && !i1 && i2 && i3 && i4:
return struct {
Unwrapper
http.ResponseWriter
http.Hijacker
io.ReaderFrom
http.Pusher
}{rw, rw, rw, rw, rw}
// combination 9/32
case !i0 && i1 && !i2 && !i3 && !i4:
return struct {
Unwrapper
http.ResponseWriter
http.CloseNotifier
}{rw, rw, rw}
// combination 10/32
case !i0 && i1 && !i2 && !i3 && i4:
return struct {
Unwrapper
http.ResponseWriter
http.CloseNotifier
http.Pusher
}{rw, rw, rw, rw}
// combination 11/32
case !i0 && i1 && !i2 && i3 && !i4:
return struct {
Unwrapper
http.ResponseWriter
http.CloseNotifier
io.ReaderFrom
}{rw, rw, rw, rw}
// combination 12/32
case !i0 && i1 && !i2 && i3 && i4:
return struct {
Unwrapper
http.ResponseWriter
http.CloseNotifier
io.ReaderFrom
http.Pusher
}{rw, rw, rw, rw, rw}
// combination 13/32
case !i0 && i1 && i2 && !i3 && !i4:
return struct {
Unwrapper
http.ResponseWriter
http.CloseNotifier
http.Hijacker
}{rw, rw, rw, rw}
// combination 14/32
case !i0 && i1 && i2 && !i3 && i4:
return struct {
Unwrapper
http.ResponseWriter
http.CloseNotifier
http.Hijacker
http.Pusher
}{rw, rw, rw, rw, rw}
// combination 15/32
case !i0 && i1 && i2 && i3 && !i4:
return struct {
Unwrapper
http.ResponseWriter
http.CloseNotifier
http.Hijacker
io.ReaderFrom
}{rw, rw, rw, rw, rw}
// combination 16/32
case !i0 && i1 && i2 && i3 && i4:
return struct {
Unwrapper
http.ResponseWriter
http.CloseNotifier
http.Hijacker
io.ReaderFrom
http.Pusher
}{rw, rw, rw, rw, rw, rw}
// combination 17/32
case i0 && !i1 && !i2 && !i3 && !i4:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
}{rw, rw, rw}
// combination 18/32
case i0 && !i1 && !i2 && !i3 && i4:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.Pusher
}{rw, rw, rw, rw}
// combination 19/32
case i0 && !i1 && !i2 && i3 && !i4:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
io.ReaderFrom
}{rw, rw, rw, rw}
// combination 20/32
case i0 && !i1 && !i2 && i3 && i4:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
io.ReaderFrom
http.Pusher
}{rw, rw, rw, rw, rw}
// combination 21/32
case i0 && !i1 && i2 && !i3 && !i4:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.Hijacker
}{rw, rw, rw, rw}
// combination 22/32
case i0 && !i1 && i2 && !i3 && i4:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.Hijacker
http.Pusher
}{rw, rw, rw, rw, rw}
// combination 23/32
case i0 && !i1 && i2 && i3 && !i4:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.Hijacker
io.ReaderFrom
}{rw, rw, rw, rw, rw}
// combination 24/32
case i0 && !i1 && i2 && i3 && i4:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.Hijacker
io.ReaderFrom
http.Pusher
}{rw, rw, rw, rw, rw, rw}
// combination 25/32
case i0 && i1 && !i2 && !i3 && !i4:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.CloseNotifier
}{rw, rw, rw, rw}
// combination 26/32
case i0 && i1 && !i2 && !i3 && i4:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.CloseNotifier
http.Pusher
}{rw, rw, rw, rw, rw}
// combination 27/32
case i0 && i1 && !i2 && i3 && !i4:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.CloseNotifier
io.ReaderFrom
}{rw, rw, rw, rw, rw}
// combination 28/32
case i0 && i1 && !i2 && i3 && i4:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.CloseNotifier
io.ReaderFrom
http.Pusher
}{rw, rw, rw, rw, rw, rw}
// combination 29/32
case i0 && i1 && i2 && !i3 && !i4:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.CloseNotifier
http.Hijacker
}{rw, rw, rw, rw, rw}
// combination 30/32
case i0 && i1 && i2 && !i3 && i4:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.CloseNotifier
http.Hijacker
http.Pusher
}{rw, rw, rw, rw, rw, rw}
// combination 31/32
case i0 && i1 && i2 && i3 && !i4:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.CloseNotifier
http.Hijacker
io.ReaderFrom
}{rw, rw, rw, rw, rw, rw}
// combination 32/32
case i0 && i1 && i2 && i3 && i4:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.CloseNotifier
http.Hijacker
io.ReaderFrom
http.Pusher
}{rw, rw, rw, rw, rw, rw, rw}
}
panic("unreachable")
}
type rw struct {
w http.ResponseWriter
h Hooks
}
func (w *rw) Unwrap() http.ResponseWriter {
return w.w
}
func (w *rw) Header() http.Header {
f := w.w.(http.ResponseWriter).Header
if w.h.Header != nil {
f = w.h.Header(f)
}
return f()
}
func (w *rw) WriteHeader(code int) {
f := w.w.(http.ResponseWriter).WriteHeader
if w.h.WriteHeader != nil {
f = w.h.WriteHeader(f)
}
f(code)
}
func (w *rw) Write(b []byte) (int, error) {
f := w.w.(http.ResponseWriter).Write
if w.h.Write != nil {
f = w.h.Write(f)
}
return f(b)
}
func (w *rw) Flush() {
f := w.w.(http.Flusher).Flush
if w.h.Flush != nil {
f = w.h.Flush(f)
}
f()
}
func (w *rw) CloseNotify() <-chan bool {
f := w.w.(http.CloseNotifier).CloseNotify
if w.h.CloseNotify != nil {
f = w.h.CloseNotify(f)
}
return f()
}
func (w *rw) Hijack() (net.Conn, *bufio.ReadWriter, error) {
f := w.w.(http.Hijacker).Hijack
if w.h.Hijack != nil {
f = w.h.Hijack(f)
}
return f()
}
func (w *rw) ReadFrom(src io.Reader) (int64, error) {
f := w.w.(io.ReaderFrom).ReadFrom
if w.h.ReadFrom != nil {
f = w.h.ReadFrom(f)
}
return f(src)
}
func (w *rw) Push(target string, opts *http.PushOptions) error {
f := w.w.(http.Pusher).Push
if w.h.Push != nil {
f = w.h.Push(f)
}
return f(target, opts)
}
type Unwrapper interface {
Unwrap() http.ResponseWriter
}
// Unwrap returns the underlying http.ResponseWriter from within zero or more
// layers of httpsnoop wrappers.
func Unwrap(w http.ResponseWriter) http.ResponseWriter {
if rw, ok := w.(Unwrapper); ok {
// recurse until rw.Unwrap() returns a non-Unwrapper
return Unwrap(rw.Unwrap())
} else {
return w
}
}

View File

@@ -0,0 +1,278 @@
// +build !go1.8
// Code generated by "httpsnoop/codegen"; DO NOT EDIT.
package httpsnoop
import (
"bufio"
"io"
"net"
"net/http"
)
// HeaderFunc is part of the http.ResponseWriter interface.
type HeaderFunc func() http.Header
// WriteHeaderFunc is part of the http.ResponseWriter interface.
type WriteHeaderFunc func(code int)
// WriteFunc is part of the http.ResponseWriter interface.
type WriteFunc func(b []byte) (int, error)
// FlushFunc is part of the http.Flusher interface.
type FlushFunc func()
// CloseNotifyFunc is part of the http.CloseNotifier interface.
type CloseNotifyFunc func() <-chan bool
// HijackFunc is part of the http.Hijacker interface.
type HijackFunc func() (net.Conn, *bufio.ReadWriter, error)
// ReadFromFunc is part of the io.ReaderFrom interface.
type ReadFromFunc func(src io.Reader) (int64, error)
// Hooks defines a set of method interceptors for methods included in
// http.ResponseWriter as well as some others. You can think of them as
// middleware for the function calls they target. See Wrap for more details.
type Hooks struct {
Header func(HeaderFunc) HeaderFunc
WriteHeader func(WriteHeaderFunc) WriteHeaderFunc
Write func(WriteFunc) WriteFunc
Flush func(FlushFunc) FlushFunc
CloseNotify func(CloseNotifyFunc) CloseNotifyFunc
Hijack func(HijackFunc) HijackFunc
ReadFrom func(ReadFromFunc) ReadFromFunc
}
// Wrap returns a wrapped version of w that provides the exact same interface
// as w. Specifically if w implements any combination of:
//
// - http.Flusher
// - http.CloseNotifier
// - http.Hijacker
// - io.ReaderFrom
//
// The wrapped version will implement the exact same combination. If no hooks
// are set, the wrapped version also behaves exactly as w. Hooks targeting
// methods not supported by w are ignored. Any other hooks will intercept the
// method they target and may modify the call's arguments and/or return values.
// The CaptureMetrics implementation serves as a working example for how the
// hooks can be used.
func Wrap(w http.ResponseWriter, hooks Hooks) http.ResponseWriter {
rw := &rw{w: w, h: hooks}
_, i0 := w.(http.Flusher)
_, i1 := w.(http.CloseNotifier)
_, i2 := w.(http.Hijacker)
_, i3 := w.(io.ReaderFrom)
switch {
// combination 1/16
case !i0 && !i1 && !i2 && !i3:
return struct {
Unwrapper
http.ResponseWriter
}{rw, rw}
// combination 2/16
case !i0 && !i1 && !i2 && i3:
return struct {
Unwrapper
http.ResponseWriter
io.ReaderFrom
}{rw, rw, rw}
// combination 3/16
case !i0 && !i1 && i2 && !i3:
return struct {
Unwrapper
http.ResponseWriter
http.Hijacker
}{rw, rw, rw}
// combination 4/16
case !i0 && !i1 && i2 && i3:
return struct {
Unwrapper
http.ResponseWriter
http.Hijacker
io.ReaderFrom
}{rw, rw, rw, rw}
// combination 5/16
case !i0 && i1 && !i2 && !i3:
return struct {
Unwrapper
http.ResponseWriter
http.CloseNotifier
}{rw, rw, rw}
// combination 6/16
case !i0 && i1 && !i2 && i3:
return struct {
Unwrapper
http.ResponseWriter
http.CloseNotifier
io.ReaderFrom
}{rw, rw, rw, rw}
// combination 7/16
case !i0 && i1 && i2 && !i3:
return struct {
Unwrapper
http.ResponseWriter
http.CloseNotifier
http.Hijacker
}{rw, rw, rw, rw}
// combination 8/16
case !i0 && i1 && i2 && i3:
return struct {
Unwrapper
http.ResponseWriter
http.CloseNotifier
http.Hijacker
io.ReaderFrom
}{rw, rw, rw, rw, rw}
// combination 9/16
case i0 && !i1 && !i2 && !i3:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
}{rw, rw, rw}
// combination 10/16
case i0 && !i1 && !i2 && i3:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
io.ReaderFrom
}{rw, rw, rw, rw}
// combination 11/16
case i0 && !i1 && i2 && !i3:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.Hijacker
}{rw, rw, rw, rw}
// combination 12/16
case i0 && !i1 && i2 && i3:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.Hijacker
io.ReaderFrom
}{rw, rw, rw, rw, rw}
// combination 13/16
case i0 && i1 && !i2 && !i3:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.CloseNotifier
}{rw, rw, rw, rw}
// combination 14/16
case i0 && i1 && !i2 && i3:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.CloseNotifier
io.ReaderFrom
}{rw, rw, rw, rw, rw}
// combination 15/16
case i0 && i1 && i2 && !i3:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.CloseNotifier
http.Hijacker
}{rw, rw, rw, rw, rw}
// combination 16/16
case i0 && i1 && i2 && i3:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.CloseNotifier
http.Hijacker
io.ReaderFrom
}{rw, rw, rw, rw, rw, rw}
}
panic("unreachable")
}
type rw struct {
w http.ResponseWriter
h Hooks
}
func (w *rw) Unwrap() http.ResponseWriter {
return w.w
}
func (w *rw) Header() http.Header {
f := w.w.(http.ResponseWriter).Header
if w.h.Header != nil {
f = w.h.Header(f)
}
return f()
}
func (w *rw) WriteHeader(code int) {
f := w.w.(http.ResponseWriter).WriteHeader
if w.h.WriteHeader != nil {
f = w.h.WriteHeader(f)
}
f(code)
}
func (w *rw) Write(b []byte) (int, error) {
f := w.w.(http.ResponseWriter).Write
if w.h.Write != nil {
f = w.h.Write(f)
}
return f(b)
}
func (w *rw) Flush() {
f := w.w.(http.Flusher).Flush
if w.h.Flush != nil {
f = w.h.Flush(f)
}
f()
}
func (w *rw) CloseNotify() <-chan bool {
f := w.w.(http.CloseNotifier).CloseNotify
if w.h.CloseNotify != nil {
f = w.h.CloseNotify(f)
}
return f()
}
func (w *rw) Hijack() (net.Conn, *bufio.ReadWriter, error) {
f := w.w.(http.Hijacker).Hijack
if w.h.Hijack != nil {
f = w.h.Hijack(f)
}
return f()
}
func (w *rw) ReadFrom(src io.Reader) (int64, error) {
f := w.w.(io.ReaderFrom).ReadFrom
if w.h.ReadFrom != nil {
f = w.h.ReadFrom(f)
}
return f(src)
}
type Unwrapper interface {
Unwrap() http.ResponseWriter
}
// Unwrap returns the underlying http.ResponseWriter from within zero or more
// layers of httpsnoop wrappers.
func Unwrap(w http.ResponseWriter) http.ResponseWriter {
if rw, ok := w.(Unwrapper); ok {
// recurse until rw.Unwrap() returns a non-Unwrapper
return Unwrap(rw.Unwrap())
} else {
return w
}
}

29
vendor/github.com/go-fed/httpsig/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2018, go-fed
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 the copyright holder 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 HOLDER 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.

94
vendor/github.com/go-fed/httpsig/README.md generated vendored Normal file
View File

@@ -0,0 +1,94 @@
# httpsig
`go get github.com/go-fed/httpsig`
Implementation of [HTTP Signatures](https://tools.ietf.org/html/draft-cavage-http-signatures).
Supports many different combinations of MAC, HMAC signing of hash, or RSA
signing of hash schemes. Its goals are:
* Have a very simple interface for signing and validating
* Support a variety of signing algorithms and combinations
* Support setting either headers (`Authorization` or `Signature`)
* Remaining flexible with headers included in the signing string
* Support both HTTP requests and responses
* Explicitly not support known-cryptographically weak algorithms
* Support automatic signing and validating Digest headers
## How to use
`import "github.com/go-fed/httpsig"`
### Signing
Signing a request or response requires creating a new `Signer` and using it:
```
func sign(privateKey crypto.PrivateKey, pubKeyId string, r *http.Request) error {
prefs := []httpsig.Algorithm{httpsig.RSA_SHA512, httpsig.RSA_SHA256}
digestAlgorithm := DigestSha256
// The "Date" and "Digest" headers must already be set on r, as well as r.URL.
headersToSign := []string{httpsig.RequestTarget, "date", "digest"}
signer, chosenAlgo, err := httpsig.NewSigner(prefs, digestAlgorithm, headersToSign, httpsig.Signature)
if err != nil {
return err
}
// To sign the digest, we need to give the signer a copy of the body...
// ...but it is optional, no digest will be signed if given "nil"
body := ...
// If r were a http.ResponseWriter, call SignResponse instead.
return signer.SignRequest(privateKey, pubKeyId, r, body)
}
```
`Signer`s are not safe for concurrent use by goroutines, so be sure to guard
access:
```
type server struct {
signer httpsig.Signer
mu *sync.Mutex
}
func (s *server) handlerFunc(w http.ResponseWriter, r *http.Request) {
privateKey := ...
pubKeyId := ...
// Set headers and such on w
s.mu.Lock()
defer s.mu.Unlock()
// To sign the digest, we need to give the signer a copy of the response body...
// ...but it is optional, no digest will be signed if given "nil"
body := ...
err := s.signer.SignResponse(privateKey, pubKeyId, w, body)
if err != nil {
...
}
...
}
```
The `pubKeyId` will be used at verification time.
### Verifying
Verifying requires an application to use the `pubKeyId` to both retrieve the key
needed for verification as well as determine the algorithm to use. Use a
`Verifier`:
```
func verify(r *http.Request) error {
verifier, err := httpsig.NewVerifier(r)
if err != nil {
return err
}
pubKeyId := verifier.KeyId()
var algo httpsig.Algorithm = ...
var pubKey crypto.PublicKey = ...
// The verifier will verify the Digest in addition to the HTTP signature
return verifier.Verify(pubKey, algo)
}
```
`Verifier`s are not safe for concurrent use by goroutines, but since they are
constructed on a per-request or per-response basis it should not be a common
restriction.

532
vendor/github.com/go-fed/httpsig/algorithms.go generated vendored Normal file
View File

@@ -0,0 +1,532 @@
package httpsig
import (
"crypto"
"crypto/ecdsa"
"crypto/hmac"
"crypto/rsa"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"crypto/subtle" // Use should trigger great care
"encoding/asn1"
"errors"
"fmt"
"hash"
"io"
"math/big"
"strings"
"golang.org/x/crypto/blake2b"
"golang.org/x/crypto/blake2s"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/ripemd160"
"golang.org/x/crypto/sha3"
"golang.org/x/crypto/ssh"
)
const (
hmacPrefix = "hmac"
rsaPrefix = "rsa"
sshPrefix = "ssh"
ecdsaPrefix = "ecdsa"
ed25519Prefix = "ed25519"
md4String = "md4"
md5String = "md5"
sha1String = "sha1"
sha224String = "sha224"
sha256String = "sha256"
sha384String = "sha384"
sha512String = "sha512"
md5sha1String = "md5sha1"
ripemd160String = "ripemd160"
sha3_224String = "sha3-224"
sha3_256String = "sha3-256"
sha3_384String = "sha3-384"
sha3_512String = "sha3-512"
sha512_224String = "sha512-224"
sha512_256String = "sha512-256"
blake2s_256String = "blake2s-256"
blake2b_256String = "blake2b-256"
blake2b_384String = "blake2b-384"
blake2b_512String = "blake2b-512"
)
var blake2Algorithms = map[crypto.Hash]bool{
crypto.BLAKE2s_256: true,
crypto.BLAKE2b_256: true,
crypto.BLAKE2b_384: true,
crypto.BLAKE2b_512: true,
}
var hashToDef = map[crypto.Hash]struct {
name string
new func(key []byte) (hash.Hash, error) // Only MACers will accept a key
}{
// Which standard names these?
// The spec lists the following as a canonical reference, which is dead:
// http://www.iana.org/assignments/signature-algorithms
//
// Note that the forbidden hashes have an invalid 'new' function.
crypto.MD4: {md4String, func(key []byte) (hash.Hash, error) { return nil, nil }},
crypto.MD5: {md5String, func(key []byte) (hash.Hash, error) { return nil, nil }},
// Temporarily enable SHA1 because of issue https://github.com/golang/go/issues/37278
crypto.SHA1: {sha1String, func(key []byte) (hash.Hash, error) { return sha1.New(), nil }},
crypto.SHA224: {sha224String, func(key []byte) (hash.Hash, error) { return sha256.New224(), nil }},
crypto.SHA256: {sha256String, func(key []byte) (hash.Hash, error) { return sha256.New(), nil }},
crypto.SHA384: {sha384String, func(key []byte) (hash.Hash, error) { return sha512.New384(), nil }},
crypto.SHA512: {sha512String, func(key []byte) (hash.Hash, error) { return sha512.New(), nil }},
crypto.MD5SHA1: {md5sha1String, func(key []byte) (hash.Hash, error) { return nil, nil }},
crypto.RIPEMD160: {ripemd160String, func(key []byte) (hash.Hash, error) { return ripemd160.New(), nil }},
crypto.SHA3_224: {sha3_224String, func(key []byte) (hash.Hash, error) { return sha3.New224(), nil }},
crypto.SHA3_256: {sha3_256String, func(key []byte) (hash.Hash, error) { return sha3.New256(), nil }},
crypto.SHA3_384: {sha3_384String, func(key []byte) (hash.Hash, error) { return sha3.New384(), nil }},
crypto.SHA3_512: {sha3_512String, func(key []byte) (hash.Hash, error) { return sha3.New512(), nil }},
crypto.SHA512_224: {sha512_224String, func(key []byte) (hash.Hash, error) { return sha512.New512_224(), nil }},
crypto.SHA512_256: {sha512_256String, func(key []byte) (hash.Hash, error) { return sha512.New512_256(), nil }},
crypto.BLAKE2s_256: {blake2s_256String, func(key []byte) (hash.Hash, error) { return blake2s.New256(key) }},
crypto.BLAKE2b_256: {blake2b_256String, func(key []byte) (hash.Hash, error) { return blake2b.New256(key) }},
crypto.BLAKE2b_384: {blake2b_384String, func(key []byte) (hash.Hash, error) { return blake2b.New384(key) }},
crypto.BLAKE2b_512: {blake2b_512String, func(key []byte) (hash.Hash, error) { return blake2b.New512(key) }},
}
var stringToHash map[string]crypto.Hash
const (
defaultAlgorithm = RSA_SHA256
defaultAlgorithmHashing = sha256String
)
func init() {
stringToHash = make(map[string]crypto.Hash, len(hashToDef))
for k, v := range hashToDef {
stringToHash[v.name] = k
}
// This should guarantee that at runtime the defaultAlgorithm will not
// result in errors when fetching a macer or signer (see algorithms.go)
if ok, err := isAvailable(string(defaultAlgorithmHashing)); err != nil {
panic(err)
} else if !ok {
panic(fmt.Sprintf("the default httpsig algorithm is unavailable: %q", defaultAlgorithm))
}
}
func isForbiddenHash(h crypto.Hash) bool {
switch h {
// Not actually cryptographically secure
case crypto.MD4:
fallthrough
case crypto.MD5:
fallthrough
case crypto.MD5SHA1: // shorthand for crypto/tls, not actually implemented
return true
}
// Still cryptographically secure
return false
}
// signer is an internally public type.
type signer interface {
Sign(rand io.Reader, p crypto.PrivateKey, sig []byte) ([]byte, error)
Verify(pub crypto.PublicKey, toHash, signature []byte) error
String() string
}
// macer is an internally public type.
type macer interface {
Sign(sig, key []byte) ([]byte, error)
Equal(sig, actualMAC, key []byte) (bool, error)
String() string
}
var _ macer = &hmacAlgorithm{}
type hmacAlgorithm struct {
fn func(key []byte) (hash.Hash, error)
kind crypto.Hash
}
func (h *hmacAlgorithm) Sign(sig, key []byte) ([]byte, error) {
hs, err := h.fn(key)
if err = setSig(hs, sig); err != nil {
return nil, err
}
return hs.Sum(nil), nil
}
func (h *hmacAlgorithm) Equal(sig, actualMAC, key []byte) (bool, error) {
hs, err := h.fn(key)
if err != nil {
return false, err
}
defer hs.Reset()
err = setSig(hs, sig)
if err != nil {
return false, err
}
expected := hs.Sum(nil)
return hmac.Equal(actualMAC, expected), nil
}
func (h *hmacAlgorithm) String() string {
return fmt.Sprintf("%s-%s", hmacPrefix, hashToDef[h.kind].name)
}
var _ signer = &rsaAlgorithm{}
type rsaAlgorithm struct {
hash.Hash
kind crypto.Hash
sshSigner ssh.Signer
}
func (r *rsaAlgorithm) setSig(b []byte) error {
n, err := r.Write(b)
if err != nil {
r.Reset()
return err
} else if n != len(b) {
r.Reset()
return fmt.Errorf("could only write %d of %d bytes of signature to hash", n, len(b))
}
return nil
}
func (r *rsaAlgorithm) Sign(rand io.Reader, p crypto.PrivateKey, sig []byte) ([]byte, error) {
if r.sshSigner != nil {
sshsig, err := r.sshSigner.Sign(rand, sig)
if err != nil {
return nil, err
}
return sshsig.Blob, nil
}
defer r.Reset()
if err := r.setSig(sig); err != nil {
return nil, err
}
rsaK, ok := p.(*rsa.PrivateKey)
if !ok {
return nil, errors.New("crypto.PrivateKey is not *rsa.PrivateKey")
}
return rsa.SignPKCS1v15(rand, rsaK, r.kind, r.Sum(nil))
}
func (r *rsaAlgorithm) Verify(pub crypto.PublicKey, toHash, signature []byte) error {
defer r.Reset()
rsaK, ok := pub.(*rsa.PublicKey)
if !ok {
return errors.New("crypto.PublicKey is not *rsa.PublicKey")
}
if err := r.setSig(toHash); err != nil {
return err
}
return rsa.VerifyPKCS1v15(rsaK, r.kind, r.Sum(nil), signature)
}
func (r *rsaAlgorithm) String() string {
return fmt.Sprintf("%s-%s", rsaPrefix, hashToDef[r.kind].name)
}
var _ signer = &ed25519Algorithm{}
type ed25519Algorithm struct {
sshSigner ssh.Signer
}
func (r *ed25519Algorithm) Sign(rand io.Reader, p crypto.PrivateKey, sig []byte) ([]byte, error) {
if r.sshSigner != nil {
sshsig, err := r.sshSigner.Sign(rand, sig)
if err != nil {
return nil, err
}
return sshsig.Blob, nil
}
ed25519K, ok := p.(ed25519.PrivateKey)
if !ok {
return nil, errors.New("crypto.PrivateKey is not ed25519.PrivateKey")
}
return ed25519.Sign(ed25519K, sig), nil
}
func (r *ed25519Algorithm) Verify(pub crypto.PublicKey, toHash, signature []byte) error {
ed25519K, ok := pub.(ed25519.PublicKey)
if !ok {
return errors.New("crypto.PublicKey is not ed25519.PublicKey")
}
if ed25519.Verify(ed25519K, toHash, signature) {
return nil
}
return errors.New("ed25519 verify failed")
}
func (r *ed25519Algorithm) String() string {
return fmt.Sprintf("%s", ed25519Prefix)
}
var _ signer = &ecdsaAlgorithm{}
type ecdsaAlgorithm struct {
hash.Hash
kind crypto.Hash
}
func (r *ecdsaAlgorithm) setSig(b []byte) error {
n, err := r.Write(b)
if err != nil {
r.Reset()
return err
} else if n != len(b) {
r.Reset()
return fmt.Errorf("could only write %d of %d bytes of signature to hash", n, len(b))
}
return nil
}
type ECDSASignature struct {
R, S *big.Int
}
func (r *ecdsaAlgorithm) Sign(rand io.Reader, p crypto.PrivateKey, sig []byte) ([]byte, error) {
defer r.Reset()
if err := r.setSig(sig); err != nil {
return nil, err
}
ecdsaK, ok := p.(*ecdsa.PrivateKey)
if !ok {
return nil, errors.New("crypto.PrivateKey is not *ecdsa.PrivateKey")
}
R, S, err := ecdsa.Sign(rand, ecdsaK, r.Sum(nil))
if err != nil {
return nil, err
}
signature := ECDSASignature{R: R, S: S}
bytes, err := asn1.Marshal(signature)
return bytes, err
}
func (r *ecdsaAlgorithm) Verify(pub crypto.PublicKey, toHash, signature []byte) error {
defer r.Reset()
ecdsaK, ok := pub.(*ecdsa.PublicKey)
if !ok {
return errors.New("crypto.PublicKey is not *ecdsa.PublicKey")
}
if err := r.setSig(toHash); err != nil {
return err
}
sig := new(ECDSASignature)
_, err := asn1.Unmarshal(signature, sig)
if err != nil {
return err
}
if ecdsa.Verify(ecdsaK, r.Sum(nil), sig.R, sig.S) {
return nil
} else {
return errors.New("Invalid signature")
}
}
func (r *ecdsaAlgorithm) String() string {
return fmt.Sprintf("%s-%s", ecdsaPrefix, hashToDef[r.kind].name)
}
var _ macer = &blakeMacAlgorithm{}
type blakeMacAlgorithm struct {
fn func(key []byte) (hash.Hash, error)
kind crypto.Hash
}
func (r *blakeMacAlgorithm) Sign(sig, key []byte) ([]byte, error) {
hs, err := r.fn(key)
if err != nil {
return nil, err
}
if err = setSig(hs, sig); err != nil {
return nil, err
}
return hs.Sum(nil), nil
}
func (r *blakeMacAlgorithm) Equal(sig, actualMAC, key []byte) (bool, error) {
hs, err := r.fn(key)
if err != nil {
return false, err
}
defer hs.Reset()
err = setSig(hs, sig)
if err != nil {
return false, err
}
expected := hs.Sum(nil)
return subtle.ConstantTimeCompare(actualMAC, expected) == 1, nil
}
func (r *blakeMacAlgorithm) String() string {
return fmt.Sprintf("%s", hashToDef[r.kind].name)
}
func setSig(a hash.Hash, b []byte) error {
n, err := a.Write(b)
if err != nil {
a.Reset()
return err
} else if n != len(b) {
a.Reset()
return fmt.Errorf("could only write %d of %d bytes of signature to hash", n, len(b))
}
return nil
}
// IsSupportedHttpSigAlgorithm returns true if the string is supported by this
// library, is not a hash known to be weak, and is supported by the hardware.
func IsSupportedHttpSigAlgorithm(algo string) bool {
a, err := isAvailable(algo)
return a && err == nil
}
// isAvailable is an internally public function
func isAvailable(algo string) (bool, error) {
c, ok := stringToHash[algo]
if !ok {
return false, fmt.Errorf("no match for %q", algo)
}
if isForbiddenHash(c) {
return false, fmt.Errorf("forbidden hash type in %q", algo)
}
return c.Available(), nil
}
func newAlgorithmConstructor(algo string) (fn func(k []byte) (hash.Hash, error), c crypto.Hash, e error) {
ok := false
c, ok = stringToHash[algo]
if !ok {
e = fmt.Errorf("no match for %q", algo)
return
}
if isForbiddenHash(c) {
e = fmt.Errorf("forbidden hash type in %q", algo)
return
}
algoDef, ok := hashToDef[c]
if !ok {
e = fmt.Errorf("have crypto.Hash %v but no definition", c)
return
}
fn = func(key []byte) (hash.Hash, error) {
h, err := algoDef.new(key)
if err != nil {
return nil, err
}
return h, nil
}
return
}
func newAlgorithm(algo string, key []byte) (hash.Hash, crypto.Hash, error) {
fn, c, err := newAlgorithmConstructor(algo)
if err != nil {
return nil, c, err
}
h, err := fn(key)
return h, c, err
}
func signerFromSSHSigner(sshSigner ssh.Signer, s string) (signer, error) {
switch {
case strings.HasPrefix(s, rsaPrefix):
return &rsaAlgorithm{
sshSigner: sshSigner,
}, nil
case strings.HasPrefix(s, ed25519Prefix):
return &ed25519Algorithm{
sshSigner: sshSigner,
}, nil
default:
return nil, fmt.Errorf("no signer matching %q", s)
}
}
// signerFromString is an internally public method constructor
func signerFromString(s string) (signer, error) {
s = strings.ToLower(s)
isEcdsa := false
isEd25519 := false
var algo string = ""
if strings.HasPrefix(s, ecdsaPrefix) {
algo = strings.TrimPrefix(s, ecdsaPrefix+"-")
isEcdsa = true
} else if strings.HasPrefix(s, rsaPrefix) {
algo = strings.TrimPrefix(s, rsaPrefix+"-")
} else if strings.HasPrefix(s, ed25519Prefix) {
isEd25519 = true
algo = "sha512"
} else {
return nil, fmt.Errorf("no signer matching %q", s)
}
hash, cHash, err := newAlgorithm(algo, nil)
if err != nil {
return nil, err
}
if isEd25519 {
return &ed25519Algorithm{}, nil
}
if isEcdsa {
return &ecdsaAlgorithm{
Hash: hash,
kind: cHash,
}, nil
}
return &rsaAlgorithm{
Hash: hash,
kind: cHash,
}, nil
}
// macerFromString is an internally public method constructor
func macerFromString(s string) (macer, error) {
s = strings.ToLower(s)
if strings.HasPrefix(s, hmacPrefix) {
algo := strings.TrimPrefix(s, hmacPrefix+"-")
hashFn, cHash, err := newAlgorithmConstructor(algo)
if err != nil {
return nil, err
}
// Ensure below does not panic
_, err = hashFn(nil)
if err != nil {
return nil, err
}
return &hmacAlgorithm{
fn: func(key []byte) (hash.Hash, error) {
return hmac.New(func() hash.Hash {
h, e := hashFn(nil)
if e != nil {
panic(e)
}
return h
}, key), nil
},
kind: cHash,
}, nil
} else if bl, ok := stringToHash[s]; ok && blake2Algorithms[bl] {
hashFn, cHash, err := newAlgorithmConstructor(s)
if err != nil {
return nil, err
}
return &blakeMacAlgorithm{
fn: hashFn,
kind: cHash,
}, nil
} else {
return nil, fmt.Errorf("no MACer matching %q", s)
}
}

120
vendor/github.com/go-fed/httpsig/digest.go generated vendored Normal file
View File

@@ -0,0 +1,120 @@
package httpsig
import (
"bytes"
"crypto"
"encoding/base64"
"fmt"
"hash"
"net/http"
"strings"
)
type DigestAlgorithm string
const (
DigestSha256 DigestAlgorithm = "SHA-256"
DigestSha512 = "SHA-512"
)
var digestToDef = map[DigestAlgorithm]crypto.Hash{
DigestSha256: crypto.SHA256,
DigestSha512: crypto.SHA512,
}
// IsSupportedDigestAlgorithm returns true if hte string is supported by this
// library, is not a hash known to be weak, and is supported by the hardware.
func IsSupportedDigestAlgorithm(algo string) bool {
uc := DigestAlgorithm(strings.ToUpper(algo))
c, ok := digestToDef[uc]
return ok && c.Available()
}
func getHash(alg DigestAlgorithm) (h hash.Hash, toUse DigestAlgorithm, err error) {
upper := DigestAlgorithm(strings.ToUpper(string(alg)))
c, ok := digestToDef[upper]
if !ok {
err = fmt.Errorf("unknown or unsupported Digest algorithm: %s", alg)
} else if !c.Available() {
err = fmt.Errorf("unavailable Digest algorithm: %s", alg)
} else {
h = c.New()
toUse = upper
}
return
}
const (
digestHeader = "Digest"
digestDelim = "="
)
func addDigest(r *http.Request, algo DigestAlgorithm, b []byte) (err error) {
_, ok := r.Header[digestHeader]
if ok {
err = fmt.Errorf("cannot add Digest: Digest is already set")
return
}
var h hash.Hash
var a DigestAlgorithm
h, a, err = getHash(algo)
if err != nil {
return
}
h.Write(b)
sum := h.Sum(nil)
r.Header.Add(digestHeader,
fmt.Sprintf("%s%s%s",
a,
digestDelim,
base64.StdEncoding.EncodeToString(sum[:])))
return
}
func addDigestResponse(r http.ResponseWriter, algo DigestAlgorithm, b []byte) (err error) {
_, ok := r.Header()[digestHeader]
if ok {
err = fmt.Errorf("cannot add Digest: Digest is already set")
return
}
var h hash.Hash
var a DigestAlgorithm
h, a, err = getHash(algo)
if err != nil {
return
}
h.Write(b)
sum := h.Sum(nil)
r.Header().Add(digestHeader,
fmt.Sprintf("%s%s%s",
a,
digestDelim,
base64.StdEncoding.EncodeToString(sum[:])))
return
}
func verifyDigest(r *http.Request, body *bytes.Buffer) (err error) {
d := r.Header.Get(digestHeader)
if len(d) == 0 {
err = fmt.Errorf("cannot verify Digest: request has no Digest header")
return
}
elem := strings.SplitN(d, digestDelim, 2)
if len(elem) != 2 {
err = fmt.Errorf("cannot verify Digest: malformed Digest: %s", d)
return
}
var h hash.Hash
h, _, err = getHash(DigestAlgorithm(elem[0]))
if err != nil {
return
}
h.Write(body.Bytes())
sum := h.Sum(nil)
encSum := base64.StdEncoding.EncodeToString(sum[:])
if encSum != elem[1] {
err = fmt.Errorf("cannot verify Digest: header Digest does not match the digest of the request body")
return
}
return
}

361
vendor/github.com/go-fed/httpsig/httpsig.go generated vendored Normal file
View File

@@ -0,0 +1,361 @@
// Implements HTTP request and response signing and verification. Supports the
// major MAC and asymmetric key signature algorithms. It has several safety
// restrictions: One, none of the widely known non-cryptographically safe
// algorithms are permitted; Two, the RSA SHA256 algorithms must be available in
// the binary (and it should, barring export restrictions); Finally, the library
// assumes either the 'Authorizationn' or 'Signature' headers are to be set (but
// not both).
package httpsig
import (
"crypto"
"fmt"
"net/http"
"strings"
"time"
"golang.org/x/crypto/ssh"
)
// Algorithm specifies a cryptography secure algorithm for signing HTTP requests
// and responses.
type Algorithm string
const (
// MAC-based algoirthms.
HMAC_SHA224 Algorithm = hmacPrefix + "-" + sha224String
HMAC_SHA256 Algorithm = hmacPrefix + "-" + sha256String
HMAC_SHA384 Algorithm = hmacPrefix + "-" + sha384String
HMAC_SHA512 Algorithm = hmacPrefix + "-" + sha512String
HMAC_RIPEMD160 Algorithm = hmacPrefix + "-" + ripemd160String
HMAC_SHA3_224 Algorithm = hmacPrefix + "-" + sha3_224String
HMAC_SHA3_256 Algorithm = hmacPrefix + "-" + sha3_256String
HMAC_SHA3_384 Algorithm = hmacPrefix + "-" + sha3_384String
HMAC_SHA3_512 Algorithm = hmacPrefix + "-" + sha3_512String
HMAC_SHA512_224 Algorithm = hmacPrefix + "-" + sha512_224String
HMAC_SHA512_256 Algorithm = hmacPrefix + "-" + sha512_256String
HMAC_BLAKE2S_256 Algorithm = hmacPrefix + "-" + blake2s_256String
HMAC_BLAKE2B_256 Algorithm = hmacPrefix + "-" + blake2b_256String
HMAC_BLAKE2B_384 Algorithm = hmacPrefix + "-" + blake2b_384String
HMAC_BLAKE2B_512 Algorithm = hmacPrefix + "-" + blake2b_512String
BLAKE2S_256 Algorithm = blake2s_256String
BLAKE2B_256 Algorithm = blake2b_256String
BLAKE2B_384 Algorithm = blake2b_384String
BLAKE2B_512 Algorithm = blake2b_512String
// RSA-based algorithms.
RSA_SHA1 Algorithm = rsaPrefix + "-" + sha1String
RSA_SHA224 Algorithm = rsaPrefix + "-" + sha224String
// RSA_SHA256 is the default algorithm.
RSA_SHA256 Algorithm = rsaPrefix + "-" + sha256String
RSA_SHA384 Algorithm = rsaPrefix + "-" + sha384String
RSA_SHA512 Algorithm = rsaPrefix + "-" + sha512String
RSA_RIPEMD160 Algorithm = rsaPrefix + "-" + ripemd160String
// ECDSA algorithms
ECDSA_SHA224 Algorithm = ecdsaPrefix + "-" + sha224String
ECDSA_SHA256 Algorithm = ecdsaPrefix + "-" + sha256String
ECDSA_SHA384 Algorithm = ecdsaPrefix + "-" + sha384String
ECDSA_SHA512 Algorithm = ecdsaPrefix + "-" + sha512String
ECDSA_RIPEMD160 Algorithm = ecdsaPrefix + "-" + ripemd160String
// ED25519 algorithms
// can only be SHA512
ED25519 Algorithm = ed25519Prefix
// Just because you can glue things together, doesn't mean they will
// work. The following options are not supported.
rsa_SHA3_224 Algorithm = rsaPrefix + "-" + sha3_224String
rsa_SHA3_256 Algorithm = rsaPrefix + "-" + sha3_256String
rsa_SHA3_384 Algorithm = rsaPrefix + "-" + sha3_384String
rsa_SHA3_512 Algorithm = rsaPrefix + "-" + sha3_512String
rsa_SHA512_224 Algorithm = rsaPrefix + "-" + sha512_224String
rsa_SHA512_256 Algorithm = rsaPrefix + "-" + sha512_256String
rsa_BLAKE2S_256 Algorithm = rsaPrefix + "-" + blake2s_256String
rsa_BLAKE2B_256 Algorithm = rsaPrefix + "-" + blake2b_256String
rsa_BLAKE2B_384 Algorithm = rsaPrefix + "-" + blake2b_384String
rsa_BLAKE2B_512 Algorithm = rsaPrefix + "-" + blake2b_512String
)
// HTTP Signatures can be applied to different HTTP headers, depending on the
// expected application behavior.
type SignatureScheme string
const (
// Signature will place the HTTP Signature into the 'Signature' HTTP
// header.
Signature SignatureScheme = "Signature"
// Authorization will place the HTTP Signature into the 'Authorization'
// HTTP header.
Authorization SignatureScheme = "Authorization"
)
const (
// The HTTP Signatures specification uses the "Signature" auth-scheme
// for the Authorization header. This is coincidentally named, but not
// semantically the same, as the "Signature" HTTP header value.
signatureAuthScheme = "Signature"
)
// There are subtle differences to the values in the header. The Authorization
// header has an 'auth-scheme' value that must be prefixed to the rest of the
// key and values.
func (s SignatureScheme) authScheme() string {
switch s {
case Authorization:
return signatureAuthScheme
default:
return ""
}
}
// Signers will sign HTTP requests or responses based on the algorithms and
// headers selected at creation time.
//
// Signers are not safe to use between multiple goroutines.
//
// Note that signatures do set the deprecated 'algorithm' parameter for
// backwards compatibility.
type Signer interface {
// SignRequest signs the request using a private key. The public key id
// is used by the HTTP server to identify which key to use to verify the
// signature.
//
// If the Signer was created using a MAC based algorithm, then the key
// is expected to be of type []byte. If the Signer was created using an
// RSA based algorithm, then the private key is expected to be of type
// *rsa.PrivateKey.
//
// A Digest (RFC 3230) will be added to the request. The body provided
// must match the body used in the request, and is allowed to be nil.
// The Digest ensures the request body is not tampered with in flight,
// and if the signer is created to also sign the "Digest" header, the
// HTTP Signature will then ensure both the Digest and body are not both
// modified to maliciously represent different content.
SignRequest(pKey crypto.PrivateKey, pubKeyId string, r *http.Request, body []byte) error
// SignResponse signs the response using a private key. The public key
// id is used by the HTTP client to identify which key to use to verify
// the signature.
//
// If the Signer was created using a MAC based algorithm, then the key
// is expected to be of type []byte. If the Signer was created using an
// RSA based algorithm, then the private key is expected to be of type
// *rsa.PrivateKey.
//
// A Digest (RFC 3230) will be added to the response. The body provided
// must match the body written in the response, and is allowed to be
// nil. The Digest ensures the response body is not tampered with in
// flight, and if the signer is created to also sign the "Digest"
// header, the HTTP Signature will then ensure both the Digest and body
// are not both modified to maliciously represent different content.
SignResponse(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter, body []byte) error
}
// NewSigner creates a new Signer with the provided algorithm preferences to
// make HTTP signatures. Only the first available algorithm will be used, which
// is returned by this function along with the Signer. If none of the preferred
// algorithms were available, then the default algorithm is used. The headers
// specified will be included into the HTTP signatures.
//
// The Digest will also be calculated on a request's body using the provided
// digest algorithm, if "Digest" is one of the headers listed.
//
// The provided scheme determines which header is populated with the HTTP
// Signature.
//
// An error is returned if an unknown or a known cryptographically insecure
// Algorithm is provided.
func NewSigner(prefs []Algorithm, dAlgo DigestAlgorithm, headers []string, scheme SignatureScheme, expiresIn int64) (Signer, Algorithm, error) {
for _, pref := range prefs {
s, err := newSigner(pref, dAlgo, headers, scheme, expiresIn)
if err != nil {
continue
}
return s, pref, err
}
s, err := newSigner(defaultAlgorithm, dAlgo, headers, scheme, expiresIn)
return s, defaultAlgorithm, err
}
// Signers will sign HTTP requests or responses based on the algorithms and
// headers selected at creation time.
//
// Signers are not safe to use between multiple goroutines.
//
// Note that signatures do set the deprecated 'algorithm' parameter for
// backwards compatibility.
type SSHSigner interface {
// SignRequest signs the request using ssh.Signer.
// The public key id is used by the HTTP server to identify which key to use
// to verify the signature.
//
// A Digest (RFC 3230) will be added to the request. The body provided
// must match the body used in the request, and is allowed to be nil.
// The Digest ensures the request body is not tampered with in flight,
// and if the signer is created to also sign the "Digest" header, the
// HTTP Signature will then ensure both the Digest and body are not both
// modified to maliciously represent different content.
SignRequest(pubKeyId string, r *http.Request, body []byte) error
// SignResponse signs the response using ssh.Signer. The public key
// id is used by the HTTP client to identify which key to use to verify
// the signature.
//
// A Digest (RFC 3230) will be added to the response. The body provided
// must match the body written in the response, and is allowed to be
// nil. The Digest ensures the response body is not tampered with in
// flight, and if the signer is created to also sign the "Digest"
// header, the HTTP Signature will then ensure both the Digest and body
// are not both modified to maliciously represent different content.
SignResponse(pubKeyId string, r http.ResponseWriter, body []byte) error
}
// NewwSSHSigner creates a new Signer using the specified ssh.Signer
// At the moment only ed25519 ssh keys are supported.
// The headers specified will be included into the HTTP signatures.
//
// The Digest will also be calculated on a request's body using the provided
// digest algorithm, if "Digest" is one of the headers listed.
//
// The provided scheme determines which header is populated with the HTTP
// Signature.
func NewSSHSigner(s ssh.Signer, dAlgo DigestAlgorithm, headers []string, scheme SignatureScheme, expiresIn int64) (SSHSigner, Algorithm, error) {
sshAlgo := getSSHAlgorithm(s.PublicKey().Type())
if sshAlgo == "" {
return nil, "", fmt.Errorf("key type: %s not supported yet.", s.PublicKey().Type())
}
signer, err := newSSHSigner(s, sshAlgo, dAlgo, headers, scheme, expiresIn)
if err != nil {
return nil, "", err
}
return signer, sshAlgo, nil
}
func getSSHAlgorithm(pkType string) Algorithm {
switch {
case strings.HasPrefix(pkType, sshPrefix+"-"+ed25519Prefix):
return ED25519
case strings.HasPrefix(pkType, sshPrefix+"-"+rsaPrefix):
return RSA_SHA1
}
return ""
}
// Verifier verifies HTTP Signatures.
//
// It will determine which of the supported headers has the parameters
// that define the signature.
//
// Verifiers are not safe to use between multiple goroutines.
//
// Note that verification ignores the deprecated 'algorithm' parameter.
type Verifier interface {
// KeyId gets the public key id that the signature is signed with.
//
// Note that the application is expected to determine the algorithm
// used based on metadata or out-of-band information for this key id.
KeyId() string
// Verify accepts the public key specified by KeyId and returns an
// error if verification fails or if the signature is malformed. The
// algorithm must be the one used to create the signature in order to
// pass verification. The algorithm is determined based on metadata or
// out-of-band information for the key id.
//
// If the signature was created using a MAC based algorithm, then the
// key is expected to be of type []byte. If the signature was created
// using an RSA based algorithm, then the public key is expected to be
// of type *rsa.PublicKey.
Verify(pKey crypto.PublicKey, algo Algorithm) error
}
const (
// host is treated specially because golang may not include it in the
// request header map on the server side of a request.
hostHeader = "Host"
)
// NewVerifier verifies the given request. It returns an error if the HTTP
// Signature parameters are not present in any headers, are present in more than
// one header, are malformed, or are missing required parameters. It ignores
// unknown HTTP Signature parameters.
func NewVerifier(r *http.Request) (Verifier, error) {
h := r.Header
if _, hasHostHeader := h[hostHeader]; len(r.Host) > 0 && !hasHostHeader {
h[hostHeader] = []string{r.Host}
}
return newVerifier(h, func(h http.Header, toInclude []string, created int64, expires int64) (string, error) {
return signatureString(h, toInclude, addRequestTarget(r), created, expires)
})
}
// NewResponseVerifier verifies the given response. It returns errors under the
// same conditions as NewVerifier.
func NewResponseVerifier(r *http.Response) (Verifier, error) {
return newVerifier(r.Header, func(h http.Header, toInclude []string, created int64, expires int64) (string, error) {
return signatureString(h, toInclude, requestTargetNotPermitted, created, expires)
})
}
func newSSHSigner(sshSigner ssh.Signer, algo Algorithm, dAlgo DigestAlgorithm, headers []string, scheme SignatureScheme, expiresIn int64) (SSHSigner, error) {
var expires, created int64 = 0, 0
if expiresIn != 0 {
created = time.Now().Unix()
expires = created + expiresIn
}
s, err := signerFromSSHSigner(sshSigner, string(algo))
if err != nil {
return nil, fmt.Errorf("no crypto implementation available for ssh algo %q", algo)
}
a := &asymmSSHSigner{
asymmSigner: &asymmSigner{
s: s,
dAlgo: dAlgo,
headers: headers,
targetHeader: scheme,
prefix: scheme.authScheme(),
created: created,
expires: expires,
},
}
return a, nil
}
func newSigner(algo Algorithm, dAlgo DigestAlgorithm, headers []string, scheme SignatureScheme, expiresIn int64) (Signer, error) {
var expires, created int64 = 0, 0
if expiresIn != 0 {
created = time.Now().Unix()
expires = created + expiresIn
}
s, err := signerFromString(string(algo))
if err == nil {
a := &asymmSigner{
s: s,
dAlgo: dAlgo,
headers: headers,
targetHeader: scheme,
prefix: scheme.authScheme(),
created: created,
expires: expires,
}
return a, nil
}
m, err := macerFromString(string(algo))
if err != nil {
return nil, fmt.Errorf("no crypto implementation available for %q", algo)
}
c := &macSigner{
m: m,
dAlgo: dAlgo,
headers: headers,
targetHeader: scheme,
prefix: scheme.authScheme(),
created: created,
expires: expires,
}
return c, nil
}

334
vendor/github.com/go-fed/httpsig/signing.go generated vendored Normal file
View File

@@ -0,0 +1,334 @@
package httpsig
import (
"bytes"
"crypto"
"crypto/rand"
"encoding/base64"
"fmt"
"net/http"
"net/textproto"
"strconv"
"strings"
)
const (
// Signature Parameters
keyIdParameter = "keyId"
algorithmParameter = "algorithm"
headersParameter = "headers"
signatureParameter = "signature"
prefixSeparater = " "
parameterKVSeparater = "="
parameterValueDelimiter = "\""
parameterSeparater = ","
headerParameterValueDelim = " "
// RequestTarget specifies to include the http request method and
// entire URI in the signature. Pass it as a header to NewSigner.
RequestTarget = "(request-target)"
createdKey = "created"
expiresKey = "expires"
dateHeader = "date"
// Signature String Construction
headerFieldDelimiter = ": "
headersDelimiter = "\n"
headerValueDelimiter = ", "
requestTargetSeparator = " "
)
var defaultHeaders = []string{dateHeader}
var _ Signer = &macSigner{}
type macSigner struct {
m macer
makeDigest bool
dAlgo DigestAlgorithm
headers []string
targetHeader SignatureScheme
prefix string
created int64
expires int64
}
func (m *macSigner) SignRequest(pKey crypto.PrivateKey, pubKeyId string, r *http.Request, body []byte) error {
if body != nil {
err := addDigest(r, m.dAlgo, body)
if err != nil {
return err
}
}
s, err := m.signatureString(r)
if err != nil {
return err
}
enc, err := m.signSignature(pKey, s)
if err != nil {
return err
}
setSignatureHeader(r.Header, string(m.targetHeader), m.prefix, pubKeyId, m.m.String(), enc, m.headers, m.created, m.expires)
return nil
}
func (m *macSigner) SignResponse(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter, body []byte) error {
if body != nil {
err := addDigestResponse(r, m.dAlgo, body)
if err != nil {
return err
}
}
s, err := m.signatureStringResponse(r)
if err != nil {
return err
}
enc, err := m.signSignature(pKey, s)
if err != nil {
return err
}
setSignatureHeader(r.Header(), string(m.targetHeader), m.prefix, pubKeyId, m.m.String(), enc, m.headers, m.created, m.expires)
return nil
}
func (m *macSigner) signSignature(pKey crypto.PrivateKey, s string) (string, error) {
pKeyBytes, ok := pKey.([]byte)
if !ok {
return "", fmt.Errorf("private key for MAC signing must be of type []byte")
}
sig, err := m.m.Sign([]byte(s), pKeyBytes)
if err != nil {
return "", err
}
enc := base64.StdEncoding.EncodeToString(sig)
return enc, nil
}
func (m *macSigner) signatureString(r *http.Request) (string, error) {
return signatureString(r.Header, m.headers, addRequestTarget(r), m.created, m.expires)
}
func (m *macSigner) signatureStringResponse(r http.ResponseWriter) (string, error) {
return signatureString(r.Header(), m.headers, requestTargetNotPermitted, m.created, m.expires)
}
var _ Signer = &asymmSigner{}
type asymmSigner struct {
s signer
makeDigest bool
dAlgo DigestAlgorithm
headers []string
targetHeader SignatureScheme
prefix string
created int64
expires int64
}
func (a *asymmSigner) SignRequest(pKey crypto.PrivateKey, pubKeyId string, r *http.Request, body []byte) error {
if body != nil {
err := addDigest(r, a.dAlgo, body)
if err != nil {
return err
}
}
s, err := a.signatureString(r)
if err != nil {
return err
}
enc, err := a.signSignature(pKey, s)
if err != nil {
return err
}
setSignatureHeader(r.Header, string(a.targetHeader), a.prefix, pubKeyId, a.s.String(), enc, a.headers, a.created, a.expires)
return nil
}
func (a *asymmSigner) SignResponse(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter, body []byte) error {
if body != nil {
err := addDigestResponse(r, a.dAlgo, body)
if err != nil {
return err
}
}
s, err := a.signatureStringResponse(r)
if err != nil {
return err
}
enc, err := a.signSignature(pKey, s)
if err != nil {
return err
}
setSignatureHeader(r.Header(), string(a.targetHeader), a.prefix, pubKeyId, a.s.String(), enc, a.headers, a.created, a.expires)
return nil
}
func (a *asymmSigner) signSignature(pKey crypto.PrivateKey, s string) (string, error) {
sig, err := a.s.Sign(rand.Reader, pKey, []byte(s))
if err != nil {
return "", err
}
enc := base64.StdEncoding.EncodeToString(sig)
return enc, nil
}
func (a *asymmSigner) signatureString(r *http.Request) (string, error) {
return signatureString(r.Header, a.headers, addRequestTarget(r), a.created, a.expires)
}
func (a *asymmSigner) signatureStringResponse(r http.ResponseWriter) (string, error) {
return signatureString(r.Header(), a.headers, requestTargetNotPermitted, a.created, a.expires)
}
var _ SSHSigner = &asymmSSHSigner{}
type asymmSSHSigner struct {
*asymmSigner
}
func (a *asymmSSHSigner) SignRequest(pubKeyId string, r *http.Request, body []byte) error {
return a.asymmSigner.SignRequest(nil, pubKeyId, r, body)
}
func (a *asymmSSHSigner) SignResponse(pubKeyId string, r http.ResponseWriter, body []byte) error {
return a.asymmSigner.SignResponse(nil, pubKeyId, r, body)
}
func setSignatureHeader(h http.Header, targetHeader, prefix, pubKeyId, algo, enc string, headers []string, created int64, expires int64) {
if len(headers) == 0 {
headers = defaultHeaders
}
var b bytes.Buffer
// KeyId
b.WriteString(prefix)
if len(prefix) > 0 {
b.WriteString(prefixSeparater)
}
b.WriteString(keyIdParameter)
b.WriteString(parameterKVSeparater)
b.WriteString(parameterValueDelimiter)
b.WriteString(pubKeyId)
b.WriteString(parameterValueDelimiter)
b.WriteString(parameterSeparater)
// Algorithm
b.WriteString(algorithmParameter)
b.WriteString(parameterKVSeparater)
b.WriteString(parameterValueDelimiter)
b.WriteString("hs2019") //real algorithm is hidden, see newest version of spec draft
b.WriteString(parameterValueDelimiter)
b.WriteString(parameterSeparater)
hasCreated := false
hasExpires := false
for _, h := range headers {
val := strings.ToLower(h)
if val == "("+createdKey+")" {
hasCreated = true
} else if val == "("+expiresKey+")" {
hasExpires = true
}
}
// Created
if hasCreated == true {
b.WriteString(createdKey)
b.WriteString(parameterKVSeparater)
b.WriteString(strconv.FormatInt(created, 10))
b.WriteString(parameterSeparater)
}
// Expires
if hasExpires == true {
b.WriteString(expiresKey)
b.WriteString(parameterKVSeparater)
b.WriteString(strconv.FormatInt(expires, 10))
b.WriteString(parameterSeparater)
}
// Headers
b.WriteString(headersParameter)
b.WriteString(parameterKVSeparater)
b.WriteString(parameterValueDelimiter)
for i, h := range headers {
b.WriteString(strings.ToLower(h))
if i != len(headers)-1 {
b.WriteString(headerParameterValueDelim)
}
}
b.WriteString(parameterValueDelimiter)
b.WriteString(parameterSeparater)
// Signature
b.WriteString(signatureParameter)
b.WriteString(parameterKVSeparater)
b.WriteString(parameterValueDelimiter)
b.WriteString(enc)
b.WriteString(parameterValueDelimiter)
h.Add(targetHeader, b.String())
}
func requestTargetNotPermitted(b *bytes.Buffer) error {
return fmt.Errorf("cannot sign with %q on anything other than an http request", RequestTarget)
}
func addRequestTarget(r *http.Request) func(b *bytes.Buffer) error {
return func(b *bytes.Buffer) error {
b.WriteString(RequestTarget)
b.WriteString(headerFieldDelimiter)
b.WriteString(strings.ToLower(r.Method))
b.WriteString(requestTargetSeparator)
b.WriteString(r.URL.Path)
if r.URL.RawQuery != "" {
b.WriteString("?")
b.WriteString(r.URL.RawQuery)
}
return nil
}
}
func signatureString(values http.Header, include []string, requestTargetFn func(b *bytes.Buffer) error, created int64, expires int64) (string, error) {
if len(include) == 0 {
include = defaultHeaders
}
var b bytes.Buffer
for n, i := range include {
i := strings.ToLower(i)
if i == RequestTarget {
err := requestTargetFn(&b)
if err != nil {
return "", err
}
} else if i == "("+expiresKey+")" {
if expires == 0 {
return "", fmt.Errorf("missing expires value")
}
b.WriteString(i)
b.WriteString(headerFieldDelimiter)
b.WriteString(strconv.FormatInt(expires, 10))
} else if i == "("+createdKey+")" {
if created == 0 {
return "", fmt.Errorf("missing created value")
}
b.WriteString(i)
b.WriteString(headerFieldDelimiter)
b.WriteString(strconv.FormatInt(created, 10))
} else {
hv, ok := values[textproto.CanonicalMIMEHeaderKey(i)]
if !ok {
return "", fmt.Errorf("missing header %q", i)
}
b.WriteString(i)
b.WriteString(headerFieldDelimiter)
for i, v := range hv {
b.WriteString(strings.TrimSpace(v))
if i < len(hv)-1 {
b.WriteString(headerValueDelimiter)
}
}
}
if n < len(include)-1 {
b.WriteString(headersDelimiter)
}
}
return b.String(), nil
}

184
vendor/github.com/go-fed/httpsig/verifying.go generated vendored Normal file
View File

@@ -0,0 +1,184 @@
package httpsig
import (
"crypto"
"encoding/base64"
"errors"
"fmt"
"net/http"
"strconv"
"strings"
"time"
)
var _ Verifier = &verifier{}
type verifier struct {
header http.Header
kId string
signature string
created int64
expires int64
headers []string
sigStringFn func(http.Header, []string, int64, int64) (string, error)
}
func newVerifier(h http.Header, sigStringFn func(http.Header, []string, int64, int64) (string, error)) (*verifier, error) {
scheme, s, err := getSignatureScheme(h)
if err != nil {
return nil, err
}
kId, sig, headers, created, expires, err := getSignatureComponents(scheme, s)
if created != 0 {
//check if created is not in the future, we assume a maximum clock offset of 10 seconds
now := time.Now().Unix()
if created-now > 10 {
return nil, errors.New("created is in the future")
}
}
if expires != 0 {
//check if expires is in the past, we assume a maximum clock offset of 10 seconds
now := time.Now().Unix()
if now-expires > 10 {
return nil, errors.New("signature expired")
}
}
if err != nil {
return nil, err
}
return &verifier{
header: h,
kId: kId,
signature: sig,
created: created,
expires: expires,
headers: headers,
sigStringFn: sigStringFn,
}, nil
}
func (v *verifier) KeyId() string {
return v.kId
}
func (v *verifier) Verify(pKey crypto.PublicKey, algo Algorithm) error {
s, err := signerFromString(string(algo))
if err == nil {
return v.asymmVerify(s, pKey)
}
m, err := macerFromString(string(algo))
if err == nil {
return v.macVerify(m, pKey)
}
return fmt.Errorf("no crypto implementation available for %q", algo)
}
func (v *verifier) macVerify(m macer, pKey crypto.PublicKey) error {
key, ok := pKey.([]byte)
if !ok {
return fmt.Errorf("public key for MAC verifying must be of type []byte")
}
signature, err := v.sigStringFn(v.header, v.headers, v.created, v.expires)
if err != nil {
return err
}
actualMAC, err := base64.StdEncoding.DecodeString(v.signature)
if err != nil {
return err
}
ok, err = m.Equal([]byte(signature), actualMAC, key)
if err != nil {
return err
} else if !ok {
return fmt.Errorf("invalid http signature")
}
return nil
}
func (v *verifier) asymmVerify(s signer, pKey crypto.PublicKey) error {
toHash, err := v.sigStringFn(v.header, v.headers, v.created, v.expires)
if err != nil {
return err
}
signature, err := base64.StdEncoding.DecodeString(v.signature)
if err != nil {
return err
}
err = s.Verify(pKey, []byte(toHash), signature)
if err != nil {
return err
}
return nil
}
func getSignatureScheme(h http.Header) (scheme SignatureScheme, val string, err error) {
s := h.Get(string(Signature))
sigHasAll := strings.Contains(s, keyIdParameter) ||
strings.Contains(s, headersParameter) ||
strings.Contains(s, signatureParameter)
a := h.Get(string(Authorization))
authHasAll := strings.Contains(a, keyIdParameter) ||
strings.Contains(a, headersParameter) ||
strings.Contains(a, signatureParameter)
if sigHasAll && authHasAll {
err = fmt.Errorf("both %q and %q have signature parameters", Signature, Authorization)
return
} else if !sigHasAll && !authHasAll {
err = fmt.Errorf("neither %q nor %q have signature parameters", Signature, Authorization)
return
} else if sigHasAll {
val = s
scheme = Signature
return
} else { // authHasAll
val = a
scheme = Authorization
return
}
}
func getSignatureComponents(scheme SignatureScheme, s string) (kId, sig string, headers []string, created int64, expires int64, err error) {
if as := scheme.authScheme(); len(as) > 0 {
s = strings.TrimPrefix(s, as+prefixSeparater)
}
params := strings.Split(s, parameterSeparater)
for _, p := range params {
kv := strings.SplitN(p, parameterKVSeparater, 2)
if len(kv) != 2 {
err = fmt.Errorf("malformed http signature parameter: %v", kv)
return
}
k := kv[0]
v := strings.Trim(kv[1], parameterValueDelimiter)
switch k {
case keyIdParameter:
kId = v
case createdKey:
created, err = strconv.ParseInt(v, 10, 64)
if err != nil {
return
}
case expiresKey:
expires, err = strconv.ParseInt(v, 10, 64)
if err != nil {
return
}
case algorithmParameter:
// Deprecated, ignore
case headersParameter:
headers = strings.Split(v, headerParameterValueDelim)
case signatureParameter:
sig = v
default:
// Ignore unrecognized parameters
}
}
if len(kId) == 0 {
err = fmt.Errorf("missing %q parameter in http signature", keyIdParameter)
} else if len(sig) == 0 {
err = fmt.Errorf("missing %q parameter in http signature", signatureParameter)
} else if len(headers) == 0 { // Optional
headers = defaultHeaders
}
return
}

26
vendor/github.com/go-logr/logr/.golangci.yaml generated vendored Normal file
View File

@@ -0,0 +1,26 @@
run:
timeout: 1m
tests: true
linters:
disable-all: true
enable:
- asciicheck
- errcheck
- forcetypeassert
- gocritic
- gofmt
- goimports
- gosimple
- govet
- ineffassign
- misspell
- revive
- staticcheck
- typecheck
- unused
issues:
exclude-use-default: false
max-issues-per-linter: 0
max-same-issues: 10

6
vendor/github.com/go-logr/logr/CHANGELOG.md generated vendored Normal file
View File

@@ -0,0 +1,6 @@
# CHANGELOG
## v1.0.0-rc1
This is the first logged release. Major changes (including breaking changes)
have occurred since earlier tags.

17
vendor/github.com/go-logr/logr/CONTRIBUTING.md generated vendored Normal file
View File

@@ -0,0 +1,17 @@
# Contributing
Logr is open to pull-requests, provided they fit within the intended scope of
the project. Specifically, this library aims to be VERY small and minimalist,
with no external dependencies.
## Compatibility
This project intends to follow [semantic versioning](http://semver.org) and
is very strict about compatibility. Any proposed changes MUST follow those
rules.
## Performance
As a logging library, logr must be as light-weight as possible. Any proposed
code change must include results of running the [benchmark](./benchmark)
before and after the change.

201
vendor/github.com/go-logr/logr/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

406
vendor/github.com/go-logr/logr/README.md generated vendored Normal file
View File

@@ -0,0 +1,406 @@
# A minimal logging API for Go
[![Go Reference](https://pkg.go.dev/badge/github.com/go-logr/logr.svg)](https://pkg.go.dev/github.com/go-logr/logr)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/go-logr/logr/badge)](https://securityscorecards.dev/viewer/?platform=github.com&org=go-logr&repo=logr)
logr offers an(other) opinion on how Go programs and libraries can do logging
without becoming coupled to a particular logging implementation. This is not
an implementation of logging - it is an API. In fact it is two APIs with two
different sets of users.
The `Logger` type is intended for application and library authors. It provides
a relatively small API which can be used everywhere you want to emit logs. It
defers the actual act of writing logs (to files, to stdout, or whatever) to the
`LogSink` interface.
The `LogSink` interface is intended for logging library implementers. It is a
pure interface which can be implemented by logging frameworks to provide the actual logging
functionality.
This decoupling allows application and library developers to write code in
terms of `logr.Logger` (which has very low dependency fan-out) while the
implementation of logging is managed "up stack" (e.g. in or near `main()`.)
Application developers can then switch out implementations as necessary.
Many people assert that libraries should not be logging, and as such efforts
like this are pointless. Those people are welcome to convince the authors of
the tens-of-thousands of libraries that *DO* write logs that they are all
wrong. In the meantime, logr takes a more practical approach.
## Typical usage
Somewhere, early in an application's life, it will make a decision about which
logging library (implementation) it actually wants to use. Something like:
```
func main() {
// ... other setup code ...
// Create the "root" logger. We have chosen the "logimpl" implementation,
// which takes some initial parameters and returns a logr.Logger.
logger := logimpl.New(param1, param2)
// ... other setup code ...
```
Most apps will call into other libraries, create structures to govern the flow,
etc. The `logr.Logger` object can be passed to these other libraries, stored
in structs, or even used as a package-global variable, if needed. For example:
```
app := createTheAppObject(logger)
app.Run()
```
Outside of this early setup, no other packages need to know about the choice of
implementation. They write logs in terms of the `logr.Logger` that they
received:
```
type appObject struct {
// ... other fields ...
logger logr.Logger
// ... other fields ...
}
func (app *appObject) Run() {
app.logger.Info("starting up", "timestamp", time.Now())
// ... app code ...
```
## Background
If the Go standard library had defined an interface for logging, this project
probably would not be needed. Alas, here we are.
When the Go developers started developing such an interface with
[slog](https://github.com/golang/go/issues/56345), they adopted some of the
logr design but also left out some parts and changed others:
| Feature | logr | slog |
|---------|------|------|
| High-level API | `Logger` (passed by value) | `Logger` (passed by [pointer](https://github.com/golang/go/issues/59126)) |
| Low-level API | `LogSink` | `Handler` |
| Stack unwinding | done by `LogSink` | done by `Logger` |
| Skipping helper functions | `WithCallDepth`, `WithCallStackHelper` | [not supported by Logger](https://github.com/golang/go/issues/59145) |
| Generating a value for logging on demand | `Marshaler` | `LogValuer` |
| Log levels | >= 0, higher meaning "less important" | positive and negative, with 0 for "info" and higher meaning "more important" |
| Error log entries | always logged, don't have a verbosity level | normal log entries with level >= `LevelError` |
| Passing logger via context | `NewContext`, `FromContext` | no API |
| Adding a name to a logger | `WithName` | no API |
| Modify verbosity of log entries in a call chain | `V` | no API |
| Grouping of key/value pairs | not supported | `WithGroup`, `GroupValue` |
| Pass context for extracting additional values | no API | API variants like `InfoCtx` |
The high-level slog API is explicitly meant to be one of many different APIs
that can be layered on top of a shared `slog.Handler`. logr is one such
alternative API, with [interoperability](#slog-interoperability) provided by
some conversion functions.
### Inspiration
Before you consider this package, please read [this blog post by the
inimitable Dave Cheney][warning-makes-no-sense]. We really appreciate what
he has to say, and it largely aligns with our own experiences.
### Differences from Dave's ideas
The main differences are:
1. Dave basically proposes doing away with the notion of a logging API in favor
of `fmt.Printf()`. We disagree, especially when you consider things like output
locations, timestamps, file and line decorations, and structured logging. This
package restricts the logging API to just 2 types of logs: info and error.
Info logs are things you want to tell the user which are not errors. Error
logs are, well, errors. If your code receives an `error` from a subordinate
function call and is logging that `error` *and not returning it*, use error
logs.
2. Verbosity-levels on info logs. This gives developers a chance to indicate
arbitrary grades of importance for info logs, without assigning names with
semantic meaning such as "warning", "trace", and "debug." Superficially this
may feel very similar, but the primary difference is the lack of semantics.
Because verbosity is a numerical value, it's safe to assume that an app running
with higher verbosity means more (and less important) logs will be generated.
## Implementations (non-exhaustive)
There are implementations for the following logging libraries:
- **a function** (can bridge to non-structured libraries): [funcr](https://github.com/go-logr/logr/tree/master/funcr)
- **a testing.T** (for use in Go tests, with JSON-like output): [testr](https://github.com/go-logr/logr/tree/master/testr)
- **github.com/google/glog**: [glogr](https://github.com/go-logr/glogr)
- **k8s.io/klog** (for Kubernetes): [klogr](https://git.k8s.io/klog/klogr)
- **a testing.T** (with klog-like text output): [ktesting](https://git.k8s.io/klog/ktesting)
- **go.uber.org/zap**: [zapr](https://github.com/go-logr/zapr)
- **log** (the Go standard library logger): [stdr](https://github.com/go-logr/stdr)
- **github.com/sirupsen/logrus**: [logrusr](https://github.com/bombsimon/logrusr)
- **github.com/wojas/genericr**: [genericr](https://github.com/wojas/genericr) (makes it easy to implement your own backend)
- **logfmt** (Heroku style [logging](https://www.brandur.org/logfmt)): [logfmtr](https://github.com/iand/logfmtr)
- **github.com/rs/zerolog**: [zerologr](https://github.com/go-logr/zerologr)
- **github.com/go-kit/log**: [gokitlogr](https://github.com/tonglil/gokitlogr) (also compatible with github.com/go-kit/kit/log since v0.12.0)
- **bytes.Buffer** (writing to a buffer): [bufrlogr](https://github.com/tonglil/buflogr) (useful for ensuring values were logged, like during testing)
## slog interoperability
Interoperability goes both ways, using the `logr.Logger` API with a `slog.Handler`
and using the `slog.Logger` API with a `logr.LogSink`. `FromSlogHandler` and
`ToSlogHandler` convert between a `logr.Logger` and a `slog.Handler`.
As usual, `slog.New` can be used to wrap such a `slog.Handler` in the high-level
slog API.
### Using a `logr.LogSink` as backend for slog
Ideally, a logr sink implementation should support both logr and slog by
implementing both the normal logr interface(s) and `SlogSink`. Because
of a conflict in the parameters of the common `Enabled` method, it is [not
possible to implement both slog.Handler and logr.Sink in the same
type](https://github.com/golang/go/issues/59110).
If both are supported, log calls can go from the high-level APIs to the backend
without the need to convert parameters. `FromSlogHandler` and `ToSlogHandler` can
convert back and forth without adding additional wrappers, with one exception:
when `Logger.V` was used to adjust the verbosity for a `slog.Handler`, then
`ToSlogHandler` has to use a wrapper which adjusts the verbosity for future
log calls.
Such an implementation should also support values that implement specific
interfaces from both packages for logging (`logr.Marshaler`, `slog.LogValuer`,
`slog.GroupValue`). logr does not convert those.
Not supporting slog has several drawbacks:
- Recording source code locations works correctly if the handler gets called
through `slog.Logger`, but may be wrong in other cases. That's because a
`logr.Sink` does its own stack unwinding instead of using the program counter
provided by the high-level API.
- slog levels <= 0 can be mapped to logr levels by negating the level without a
loss of information. But all slog levels > 0 (e.g. `slog.LevelWarning` as
used by `slog.Logger.Warn`) must be mapped to 0 before calling the sink
because logr does not support "more important than info" levels.
- The slog group concept is supported by prefixing each key in a key/value
pair with the group names, separated by a dot. For structured output like
JSON it would be better to group the key/value pairs inside an object.
- Special slog values and interfaces don't work as expected.
- The overhead is likely to be higher.
These drawbacks are severe enough that applications using a mixture of slog and
logr should switch to a different backend.
### Using a `slog.Handler` as backend for logr
Using a plain `slog.Handler` without support for logr works better than the
other direction:
- All logr verbosity levels can be mapped 1:1 to their corresponding slog level
by negating them.
- Stack unwinding is done by the `SlogSink` and the resulting program
counter is passed to the `slog.Handler`.
- Names added via `Logger.WithName` are gathered and recorded in an additional
attribute with `logger` as key and the names separated by slash as value.
- `Logger.Error` is turned into a log record with `slog.LevelError` as level
and an additional attribute with `err` as key, if an error was provided.
The main drawback is that `logr.Marshaler` will not be supported. Types should
ideally support both `logr.Marshaler` and `slog.Valuer`. If compatibility
with logr implementations without slog support is not important, then
`slog.Valuer` is sufficient.
### Context support for slog
Storing a logger in a `context.Context` is not supported by
slog. `NewContextWithSlogLogger` and `FromContextAsSlogLogger` can be
used to fill this gap. They store and retrieve a `slog.Logger` pointer
under the same context key that is also used by `NewContext` and
`FromContext` for `logr.Logger` value.
When `NewContextWithSlogLogger` is followed by `FromContext`, the latter will
automatically convert the `slog.Logger` to a
`logr.Logger`. `FromContextAsSlogLogger` does the same for the other direction.
With this approach, binaries which use either slog or logr are as efficient as
possible with no unnecessary allocations. This is also why the API stores a
`slog.Logger` pointer: when storing a `slog.Handler`, creating a `slog.Logger`
on retrieval would need to allocate one.
The downside is that switching back and forth needs more allocations. Because
logr is the API that is already in use by different packages, in particular
Kubernetes, the recommendation is to use the `logr.Logger` API in code which
uses contextual logging.
An alternative to adding values to a logger and storing that logger in the
context is to store the values in the context and to configure a logging
backend to extract those values when emitting log entries. This only works when
log calls are passed the context, which is not supported by the logr API.
With the slog API, it is possible, but not
required. https://github.com/veqryn/slog-context is a package for slog which
provides additional support code for this approach. It also contains wrappers
for the context functions in logr, so developers who prefer to not use the logr
APIs directly can use those instead and the resulting code will still be
interoperable with logr.
## FAQ
### Conceptual
#### Why structured logging?
- **Structured logs are more easily queryable**: Since you've got
key-value pairs, it's much easier to query your structured logs for
particular values by filtering on the contents of a particular key --
think searching request logs for error codes, Kubernetes reconcilers for
the name and namespace of the reconciled object, etc.
- **Structured logging makes it easier to have cross-referenceable logs**:
Similarly to searchability, if you maintain conventions around your
keys, it becomes easy to gather all log lines related to a particular
concept.
- **Structured logs allow better dimensions of filtering**: if you have
structure to your logs, you've got more precise control over how much
information is logged -- you might choose in a particular configuration
to log certain keys but not others, only log lines where a certain key
matches a certain value, etc., instead of just having v-levels and names
to key off of.
- **Structured logs better represent structured data**: sometimes, the
data that you want to log is inherently structured (think tuple-link
objects.) Structured logs allow you to preserve that structure when
outputting.
#### Why V-levels?
**V-levels give operators an easy way to control the chattiness of log
operations**. V-levels provide a way for a given package to distinguish
the relative importance or verbosity of a given log message. Then, if
a particular logger or package is logging too many messages, the user
of the package can simply change the v-levels for that library.
#### Why not named levels, like Info/Warning/Error?
Read [Dave Cheney's post][warning-makes-no-sense]. Then read [Differences
from Dave's ideas](#differences-from-daves-ideas).
#### Why not allow format strings, too?
**Format strings negate many of the benefits of structured logs**:
- They're not easily searchable without resorting to fuzzy searching,
regular expressions, etc.
- They don't store structured data well, since contents are flattened into
a string.
- They're not cross-referenceable.
- They don't compress easily, since the message is not constant.
(Unless you turn positional parameters into key-value pairs with numerical
keys, at which point you've gotten key-value logging with meaningless
keys.)
### Practical
#### Why key-value pairs, and not a map?
Key-value pairs are *much* easier to optimize, especially around
allocations. Zap (a structured logger that inspired logr's interface) has
[performance measurements](https://github.com/uber-go/zap#performance)
that show this quite nicely.
While the interface ends up being a little less obvious, you get
potentially better performance, plus avoid making users type
`map[string]string{}` every time they want to log.
#### What if my V-levels differ between libraries?
That's fine. Control your V-levels on a per-logger basis, and use the
`WithName` method to pass different loggers to different libraries.
Generally, you should take care to ensure that you have relatively
consistent V-levels within a given logger, however, as this makes deciding
on what verbosity of logs to request easier.
#### But I really want to use a format string!
That's not actually a question. Assuming your question is "how do
I convert my mental model of logging with format strings to logging with
constant messages":
1. Figure out what the error actually is, as you'd write in a TL;DR style,
and use that as a message.
2. For every place you'd write a format specifier, look to the word before
it, and add that as a key value pair.
For instance, consider the following examples (all taken from spots in the
Kubernetes codebase):
- `klog.V(4).Infof("Client is returning errors: code %v, error %v",
responseCode, err)` becomes `logger.Error(err, "client returned an
error", "code", responseCode)`
- `klog.V(4).Infof("Got a Retry-After %ds response for attempt %d to %v",
seconds, retries, url)` becomes `logger.V(4).Info("got a retry-after
response when requesting url", "attempt", retries, "after
seconds", seconds, "url", url)`
If you *really* must use a format string, use it in a key's value, and
call `fmt.Sprintf` yourself. For instance: `log.Printf("unable to
reflect over type %T")` becomes `logger.Info("unable to reflect over
type", "type", fmt.Sprintf("%T"))`. In general though, the cases where
this is necessary should be few and far between.
#### How do I choose my V-levels?
This is basically the only hard constraint: increase V-levels to denote
more verbose or more debug-y logs.
Otherwise, you can start out with `0` as "you always want to see this",
`1` as "common logging that you might *possibly* want to turn off", and
`10` as "I would like to performance-test your log collection stack."
Then gradually choose levels in between as you need them, working your way
down from 10 (for debug and trace style logs) and up from 1 (for chattier
info-type logs). For reference, slog pre-defines -4 for debug logs
(corresponds to 4 in logr), which matches what is
[recommended for Kubernetes](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md#what-method-to-use).
#### How do I choose my keys?
Keys are fairly flexible, and can hold more or less any string
value. For best compatibility with implementations and consistency
with existing code in other projects, there are a few conventions you
should consider.
- Make your keys human-readable.
- Constant keys are generally a good idea.
- Be consistent across your codebase.
- Keys should naturally match parts of the message string.
- Use lower case for simple keys and
[lowerCamelCase](https://en.wiktionary.org/wiki/lowerCamelCase) for
more complex ones. Kubernetes is one example of a project that has
[adopted that
convention](https://github.com/kubernetes/community/blob/HEAD/contributors/devel/sig-instrumentation/migration-to-structured-logging.md#name-arguments).
While key names are mostly unrestricted (and spaces are acceptable),
it's generally a good idea to stick to printable ascii characters, or at
least match the general character set of your log lines.
#### Why should keys be constant values?
The point of structured logging is to make later log processing easier. Your
keys are, effectively, the schema of each log message. If you use different
keys across instances of the same log line, you will make your structured logs
much harder to use. `Sprintf()` is for values, not for keys!
#### Why is this not a pure interface?
The Logger type is implemented as a struct in order to allow the Go compiler to
optimize things like high-V `Info` logs that are not triggered. Not all of
these implementations are implemented yet, but this structure was suggested as
a way to ensure they *can* be implemented. All of the real work is behind the
`LogSink` interface.
[warning-makes-no-sense]: http://dave.cheney.net/2015/11/05/lets-talk-about-logging

18
vendor/github.com/go-logr/logr/SECURITY.md generated vendored Normal file
View File

@@ -0,0 +1,18 @@
# Security Policy
If you have discovered a security vulnerability in this project, please report it
privately. **Do not disclose it as a public issue.** This gives us time to work with you
to fix the issue before public exposure, reducing the chance that the exploit will be
used before a patch is released.
You may submit the report in the following ways:
- send an email to go-logr-security@googlegroups.com
- send us a [private vulnerability report](https://github.com/go-logr/logr/security/advisories/new)
Please provide the following information in your report:
- A description of the vulnerability and its impact
- How to reproduce the issue
We ask that you give us 90 days to work on a fix before public exposure.

33
vendor/github.com/go-logr/logr/context.go generated vendored Normal file
View File

@@ -0,0 +1,33 @@
/*
Copyright 2023 The logr Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package logr
// contextKey is how we find Loggers in a context.Context. With Go < 1.21,
// the value is always a Logger value. With Go >= 1.21, the value can be a
// Logger value or a slog.Logger pointer.
type contextKey struct{}
// notFoundError exists to carry an IsNotFound method.
type notFoundError struct{}
func (notFoundError) Error() string {
return "no logr.Logger was present"
}
func (notFoundError) IsNotFound() bool {
return true
}

49
vendor/github.com/go-logr/logr/context_noslog.go generated vendored Normal file
View File

@@ -0,0 +1,49 @@
//go:build !go1.21
// +build !go1.21
/*
Copyright 2019 The logr Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package logr
import (
"context"
)
// FromContext returns a Logger from ctx or an error if no Logger is found.
func FromContext(ctx context.Context) (Logger, error) {
if v, ok := ctx.Value(contextKey{}).(Logger); ok {
return v, nil
}
return Logger{}, notFoundError{}
}
// FromContextOrDiscard returns a Logger from ctx. If no Logger is found, this
// returns a Logger that discards all log messages.
func FromContextOrDiscard(ctx context.Context) Logger {
if v, ok := ctx.Value(contextKey{}).(Logger); ok {
return v
}
return Discard()
}
// NewContext returns a new Context, derived from ctx, which carries the
// provided Logger.
func NewContext(ctx context.Context, logger Logger) context.Context {
return context.WithValue(ctx, contextKey{}, logger)
}

83
vendor/github.com/go-logr/logr/context_slog.go generated vendored Normal file
View File

@@ -0,0 +1,83 @@
//go:build go1.21
// +build go1.21
/*
Copyright 2019 The logr Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package logr
import (
"context"
"fmt"
"log/slog"
)
// FromContext returns a Logger from ctx or an error if no Logger is found.
func FromContext(ctx context.Context) (Logger, error) {
v := ctx.Value(contextKey{})
if v == nil {
return Logger{}, notFoundError{}
}
switch v := v.(type) {
case Logger:
return v, nil
case *slog.Logger:
return FromSlogHandler(v.Handler()), nil
default:
// Not reached.
panic(fmt.Sprintf("unexpected value type for logr context key: %T", v))
}
}
// FromContextAsSlogLogger returns a slog.Logger from ctx or nil if no such Logger is found.
func FromContextAsSlogLogger(ctx context.Context) *slog.Logger {
v := ctx.Value(contextKey{})
if v == nil {
return nil
}
switch v := v.(type) {
case Logger:
return slog.New(ToSlogHandler(v))
case *slog.Logger:
return v
default:
// Not reached.
panic(fmt.Sprintf("unexpected value type for logr context key: %T", v))
}
}
// FromContextOrDiscard returns a Logger from ctx. If no Logger is found, this
// returns a Logger that discards all log messages.
func FromContextOrDiscard(ctx context.Context) Logger {
if logger, err := FromContext(ctx); err == nil {
return logger
}
return Discard()
}
// NewContext returns a new Context, derived from ctx, which carries the
// provided Logger.
func NewContext(ctx context.Context, logger Logger) context.Context {
return context.WithValue(ctx, contextKey{}, logger)
}
// NewContextWithSlogLogger returns a new Context, derived from ctx, which carries the
// provided slog.Logger.
func NewContextWithSlogLogger(ctx context.Context, logger *slog.Logger) context.Context {
return context.WithValue(ctx, contextKey{}, logger)
}

24
vendor/github.com/go-logr/logr/discard.go generated vendored Normal file
View File

@@ -0,0 +1,24 @@
/*
Copyright 2020 The logr Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package logr
// Discard returns a Logger that discards all messages logged to it. It can be
// used whenever the caller is not interested in the logs. Logger instances
// produced by this function always compare as equal.
func Discard() Logger {
return New(nil)
}

911
vendor/github.com/go-logr/logr/funcr/funcr.go generated vendored Normal file
View File

@@ -0,0 +1,911 @@
/*
Copyright 2021 The logr Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package funcr implements formatting of structured log messages and
// optionally captures the call site and timestamp.
//
// The simplest way to use it is via its implementation of a
// github.com/go-logr/logr.LogSink with output through an arbitrary
// "write" function. See New and NewJSON for details.
//
// # Custom LogSinks
//
// For users who need more control, a funcr.Formatter can be embedded inside
// your own custom LogSink implementation. This is useful when the LogSink
// needs to implement additional methods, for example.
//
// # Formatting
//
// This will respect logr.Marshaler, fmt.Stringer, and error interfaces for
// values which are being logged. When rendering a struct, funcr will use Go's
// standard JSON tags (all except "string").
package funcr
import (
"bytes"
"encoding"
"encoding/json"
"fmt"
"path/filepath"
"reflect"
"runtime"
"strconv"
"strings"
"time"
"github.com/go-logr/logr"
)
// New returns a logr.Logger which is implemented by an arbitrary function.
func New(fn func(prefix, args string), opts Options) logr.Logger {
return logr.New(newSink(fn, NewFormatter(opts)))
}
// NewJSON returns a logr.Logger which is implemented by an arbitrary function
// and produces JSON output.
func NewJSON(fn func(obj string), opts Options) logr.Logger {
fnWrapper := func(_, obj string) {
fn(obj)
}
return logr.New(newSink(fnWrapper, NewFormatterJSON(opts)))
}
// Underlier exposes access to the underlying logging function. Since
// callers only have a logr.Logger, they have to know which
// implementation is in use, so this interface is less of an
// abstraction and more of a way to test type conversion.
type Underlier interface {
GetUnderlying() func(prefix, args string)
}
func newSink(fn func(prefix, args string), formatter Formatter) logr.LogSink {
l := &fnlogger{
Formatter: formatter,
write: fn,
}
// For skipping fnlogger.Info and fnlogger.Error.
l.Formatter.AddCallDepth(1)
return l
}
// Options carries parameters which influence the way logs are generated.
type Options struct {
// LogCaller tells funcr to add a "caller" key to some or all log lines.
// This has some overhead, so some users might not want it.
LogCaller MessageClass
// LogCallerFunc tells funcr to also log the calling function name. This
// has no effect if caller logging is not enabled (see Options.LogCaller).
LogCallerFunc bool
// LogTimestamp tells funcr to add a "ts" key to log lines. This has some
// overhead, so some users might not want it.
LogTimestamp bool
// TimestampFormat tells funcr how to render timestamps when LogTimestamp
// is enabled. If not specified, a default format will be used. For more
// details, see docs for Go's time.Layout.
TimestampFormat string
// LogInfoLevel tells funcr what key to use to log the info level.
// If not specified, the info level will be logged as "level".
// If this is set to "", the info level will not be logged at all.
LogInfoLevel *string
// Verbosity tells funcr which V logs to produce. Higher values enable
// more logs. Info logs at or below this level will be written, while logs
// above this level will be discarded.
Verbosity int
// RenderBuiltinsHook allows users to mutate the list of key-value pairs
// while a log line is being rendered. The kvList argument follows logr
// conventions - each pair of slice elements is comprised of a string key
// and an arbitrary value (verified and sanitized before calling this
// hook). The value returned must follow the same conventions. This hook
// can be used to audit or modify logged data. For example, you might want
// to prefix all of funcr's built-in keys with some string. This hook is
// only called for built-in (provided by funcr itself) key-value pairs.
// Equivalent hooks are offered for key-value pairs saved via
// logr.Logger.WithValues or Formatter.AddValues (see RenderValuesHook) and
// for user-provided pairs (see RenderArgsHook).
RenderBuiltinsHook func(kvList []any) []any
// RenderValuesHook is the same as RenderBuiltinsHook, except that it is
// only called for key-value pairs saved via logr.Logger.WithValues. See
// RenderBuiltinsHook for more details.
RenderValuesHook func(kvList []any) []any
// RenderArgsHook is the same as RenderBuiltinsHook, except that it is only
// called for key-value pairs passed directly to Info and Error. See
// RenderBuiltinsHook for more details.
RenderArgsHook func(kvList []any) []any
// MaxLogDepth tells funcr how many levels of nested fields (e.g. a struct
// that contains a struct, etc.) it may log. Every time it finds a struct,
// slice, array, or map the depth is increased by one. When the maximum is
// reached, the value will be converted to a string indicating that the max
// depth has been exceeded. If this field is not specified, a default
// value will be used.
MaxLogDepth int
}
// MessageClass indicates which category or categories of messages to consider.
type MessageClass int
const (
// None ignores all message classes.
None MessageClass = iota
// All considers all message classes.
All
// Info only considers info messages.
Info
// Error only considers error messages.
Error
)
// fnlogger inherits some of its LogSink implementation from Formatter
// and just needs to add some glue code.
type fnlogger struct {
Formatter
write func(prefix, args string)
}
func (l fnlogger) WithName(name string) logr.LogSink {
l.Formatter.AddName(name)
return &l
}
func (l fnlogger) WithValues(kvList ...any) logr.LogSink {
l.Formatter.AddValues(kvList)
return &l
}
func (l fnlogger) WithCallDepth(depth int) logr.LogSink {
l.Formatter.AddCallDepth(depth)
return &l
}
func (l fnlogger) Info(level int, msg string, kvList ...any) {
prefix, args := l.FormatInfo(level, msg, kvList)
l.write(prefix, args)
}
func (l fnlogger) Error(err error, msg string, kvList ...any) {
prefix, args := l.FormatError(err, msg, kvList)
l.write(prefix, args)
}
func (l fnlogger) GetUnderlying() func(prefix, args string) {
return l.write
}
// Assert conformance to the interfaces.
var _ logr.LogSink = &fnlogger{}
var _ logr.CallDepthLogSink = &fnlogger{}
var _ Underlier = &fnlogger{}
// NewFormatter constructs a Formatter which emits a JSON-like key=value format.
func NewFormatter(opts Options) Formatter {
return newFormatter(opts, outputKeyValue)
}
// NewFormatterJSON constructs a Formatter which emits strict JSON.
func NewFormatterJSON(opts Options) Formatter {
return newFormatter(opts, outputJSON)
}
// Defaults for Options.
const defaultTimestampFormat = "2006-01-02 15:04:05.000000"
const defaultMaxLogDepth = 16
func newFormatter(opts Options, outfmt outputFormat) Formatter {
if opts.TimestampFormat == "" {
opts.TimestampFormat = defaultTimestampFormat
}
if opts.MaxLogDepth == 0 {
opts.MaxLogDepth = defaultMaxLogDepth
}
if opts.LogInfoLevel == nil {
opts.LogInfoLevel = new(string)
*opts.LogInfoLevel = "level"
}
f := Formatter{
outputFormat: outfmt,
prefix: "",
values: nil,
depth: 0,
opts: &opts,
}
return f
}
// Formatter is an opaque struct which can be embedded in a LogSink
// implementation. It should be constructed with NewFormatter. Some of
// its methods directly implement logr.LogSink.
type Formatter struct {
outputFormat outputFormat
prefix string
values []any
valuesStr string
parentValuesStr string
depth int
opts *Options
group string // for slog groups
groupDepth int
}
// outputFormat indicates which outputFormat to use.
type outputFormat int
const (
// outputKeyValue emits a JSON-like key=value format, but not strict JSON.
outputKeyValue outputFormat = iota
// outputJSON emits strict JSON.
outputJSON
)
// PseudoStruct is a list of key-value pairs that gets logged as a struct.
type PseudoStruct []any
// render produces a log line, ready to use.
func (f Formatter) render(builtins, args []any) string {
// Empirically bytes.Buffer is faster than strings.Builder for this.
buf := bytes.NewBuffer(make([]byte, 0, 1024))
if f.outputFormat == outputJSON {
buf.WriteByte('{') // for the whole line
}
vals := builtins
if hook := f.opts.RenderBuiltinsHook; hook != nil {
vals = hook(f.sanitize(vals))
}
f.flatten(buf, vals, false, false) // keys are ours, no need to escape
continuing := len(builtins) > 0
if f.parentValuesStr != "" {
if continuing {
buf.WriteByte(f.comma())
}
buf.WriteString(f.parentValuesStr)
continuing = true
}
groupDepth := f.groupDepth
if f.group != "" {
if f.valuesStr != "" || len(args) != 0 {
if continuing {
buf.WriteByte(f.comma())
}
buf.WriteString(f.quoted(f.group, true)) // escape user-provided keys
buf.WriteByte(f.colon())
buf.WriteByte('{') // for the group
continuing = false
} else {
// The group was empty
groupDepth--
}
}
if f.valuesStr != "" {
if continuing {
buf.WriteByte(f.comma())
}
buf.WriteString(f.valuesStr)
continuing = true
}
vals = args
if hook := f.opts.RenderArgsHook; hook != nil {
vals = hook(f.sanitize(vals))
}
f.flatten(buf, vals, continuing, true) // escape user-provided keys
for i := 0; i < groupDepth; i++ {
buf.WriteByte('}') // for the groups
}
if f.outputFormat == outputJSON {
buf.WriteByte('}') // for the whole line
}
return buf.String()
}
// flatten renders a list of key-value pairs into a buffer. If continuing is
// true, it assumes that the buffer has previous values and will emit a
// separator (which depends on the output format) before the first pair it
// writes. If escapeKeys is true, the keys are assumed to have
// non-JSON-compatible characters in them and must be evaluated for escapes.
//
// This function returns a potentially modified version of kvList, which
// ensures that there is a value for every key (adding a value if needed) and
// that each key is a string (substituting a key if needed).
func (f Formatter) flatten(buf *bytes.Buffer, kvList []any, continuing bool, escapeKeys bool) []any {
// This logic overlaps with sanitize() but saves one type-cast per key,
// which can be measurable.
if len(kvList)%2 != 0 {
kvList = append(kvList, noValue)
}
copied := false
for i := 0; i < len(kvList); i += 2 {
k, ok := kvList[i].(string)
if !ok {
if !copied {
newList := make([]any, len(kvList))
copy(newList, kvList)
kvList = newList
copied = true
}
k = f.nonStringKey(kvList[i])
kvList[i] = k
}
v := kvList[i+1]
if i > 0 || continuing {
if f.outputFormat == outputJSON {
buf.WriteByte(f.comma())
} else {
// In theory the format could be something we don't understand. In
// practice, we control it, so it won't be.
buf.WriteByte(' ')
}
}
buf.WriteString(f.quoted(k, escapeKeys))
buf.WriteByte(f.colon())
buf.WriteString(f.pretty(v))
}
return kvList
}
func (f Formatter) quoted(str string, escape bool) string {
if escape {
return prettyString(str)
}
// this is faster
return `"` + str + `"`
}
func (f Formatter) comma() byte {
if f.outputFormat == outputJSON {
return ','
}
return ' '
}
func (f Formatter) colon() byte {
if f.outputFormat == outputJSON {
return ':'
}
return '='
}
func (f Formatter) pretty(value any) string {
return f.prettyWithFlags(value, 0, 0)
}
const (
flagRawStruct = 0x1 // do not print braces on structs
)
// TODO: This is not fast. Most of the overhead goes here.
func (f Formatter) prettyWithFlags(value any, flags uint32, depth int) string {
if depth > f.opts.MaxLogDepth {
return `"<max-log-depth-exceeded>"`
}
// Handle types that take full control of logging.
if v, ok := value.(logr.Marshaler); ok {
// Replace the value with what the type wants to get logged.
// That then gets handled below via reflection.
value = invokeMarshaler(v)
}
// Handle types that want to format themselves.
switch v := value.(type) {
case fmt.Stringer:
value = invokeStringer(v)
case error:
value = invokeError(v)
}
// Handling the most common types without reflect is a small perf win.
switch v := value.(type) {
case bool:
return strconv.FormatBool(v)
case string:
return prettyString(v)
case int:
return strconv.FormatInt(int64(v), 10)
case int8:
return strconv.FormatInt(int64(v), 10)
case int16:
return strconv.FormatInt(int64(v), 10)
case int32:
return strconv.FormatInt(int64(v), 10)
case int64:
return strconv.FormatInt(int64(v), 10)
case uint:
return strconv.FormatUint(uint64(v), 10)
case uint8:
return strconv.FormatUint(uint64(v), 10)
case uint16:
return strconv.FormatUint(uint64(v), 10)
case uint32:
return strconv.FormatUint(uint64(v), 10)
case uint64:
return strconv.FormatUint(v, 10)
case uintptr:
return strconv.FormatUint(uint64(v), 10)
case float32:
return strconv.FormatFloat(float64(v), 'f', -1, 32)
case float64:
return strconv.FormatFloat(v, 'f', -1, 64)
case complex64:
return `"` + strconv.FormatComplex(complex128(v), 'f', -1, 64) + `"`
case complex128:
return `"` + strconv.FormatComplex(v, 'f', -1, 128) + `"`
case PseudoStruct:
buf := bytes.NewBuffer(make([]byte, 0, 1024))
v = f.sanitize(v)
if flags&flagRawStruct == 0 {
buf.WriteByte('{')
}
for i := 0; i < len(v); i += 2 {
if i > 0 {
buf.WriteByte(f.comma())
}
k, _ := v[i].(string) // sanitize() above means no need to check success
// arbitrary keys might need escaping
buf.WriteString(prettyString(k))
buf.WriteByte(f.colon())
buf.WriteString(f.prettyWithFlags(v[i+1], 0, depth+1))
}
if flags&flagRawStruct == 0 {
buf.WriteByte('}')
}
return buf.String()
}
buf := bytes.NewBuffer(make([]byte, 0, 256))
t := reflect.TypeOf(value)
if t == nil {
return "null"
}
v := reflect.ValueOf(value)
switch t.Kind() {
case reflect.Bool:
return strconv.FormatBool(v.Bool())
case reflect.String:
return prettyString(v.String())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return strconv.FormatInt(int64(v.Int()), 10)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return strconv.FormatUint(uint64(v.Uint()), 10)
case reflect.Float32:
return strconv.FormatFloat(float64(v.Float()), 'f', -1, 32)
case reflect.Float64:
return strconv.FormatFloat(v.Float(), 'f', -1, 64)
case reflect.Complex64:
return `"` + strconv.FormatComplex(complex128(v.Complex()), 'f', -1, 64) + `"`
case reflect.Complex128:
return `"` + strconv.FormatComplex(v.Complex(), 'f', -1, 128) + `"`
case reflect.Struct:
if flags&flagRawStruct == 0 {
buf.WriteByte('{')
}
printComma := false // testing i>0 is not enough because of JSON omitted fields
for i := 0; i < t.NumField(); i++ {
fld := t.Field(i)
if fld.PkgPath != "" {
// reflect says this field is only defined for non-exported fields.
continue
}
if !v.Field(i).CanInterface() {
// reflect isn't clear exactly what this means, but we can't use it.
continue
}
name := ""
omitempty := false
if tag, found := fld.Tag.Lookup("json"); found {
if tag == "-" {
continue
}
if comma := strings.Index(tag, ","); comma != -1 {
if n := tag[:comma]; n != "" {
name = n
}
rest := tag[comma:]
if strings.Contains(rest, ",omitempty,") || strings.HasSuffix(rest, ",omitempty") {
omitempty = true
}
} else {
name = tag
}
}
if omitempty && isEmpty(v.Field(i)) {
continue
}
if printComma {
buf.WriteByte(f.comma())
}
printComma = true // if we got here, we are rendering a field
if fld.Anonymous && fld.Type.Kind() == reflect.Struct && name == "" {
buf.WriteString(f.prettyWithFlags(v.Field(i).Interface(), flags|flagRawStruct, depth+1))
continue
}
if name == "" {
name = fld.Name
}
// field names can't contain characters which need escaping
buf.WriteString(f.quoted(name, false))
buf.WriteByte(f.colon())
buf.WriteString(f.prettyWithFlags(v.Field(i).Interface(), 0, depth+1))
}
if flags&flagRawStruct == 0 {
buf.WriteByte('}')
}
return buf.String()
case reflect.Slice, reflect.Array:
// If this is outputing as JSON make sure this isn't really a json.RawMessage.
// If so just emit "as-is" and don't pretty it as that will just print
// it as [X,Y,Z,...] which isn't terribly useful vs the string form you really want.
if f.outputFormat == outputJSON {
if rm, ok := value.(json.RawMessage); ok {
// If it's empty make sure we emit an empty value as the array style would below.
if len(rm) > 0 {
buf.Write(rm)
} else {
buf.WriteString("null")
}
return buf.String()
}
}
buf.WriteByte('[')
for i := 0; i < v.Len(); i++ {
if i > 0 {
buf.WriteByte(f.comma())
}
e := v.Index(i)
buf.WriteString(f.prettyWithFlags(e.Interface(), 0, depth+1))
}
buf.WriteByte(']')
return buf.String()
case reflect.Map:
buf.WriteByte('{')
// This does not sort the map keys, for best perf.
it := v.MapRange()
i := 0
for it.Next() {
if i > 0 {
buf.WriteByte(f.comma())
}
// If a map key supports TextMarshaler, use it.
keystr := ""
if m, ok := it.Key().Interface().(encoding.TextMarshaler); ok {
txt, err := m.MarshalText()
if err != nil {
keystr = fmt.Sprintf("<error-MarshalText: %s>", err.Error())
} else {
keystr = string(txt)
}
keystr = prettyString(keystr)
} else {
// prettyWithFlags will produce already-escaped values
keystr = f.prettyWithFlags(it.Key().Interface(), 0, depth+1)
if t.Key().Kind() != reflect.String {
// JSON only does string keys. Unlike Go's standard JSON, we'll
// convert just about anything to a string.
keystr = prettyString(keystr)
}
}
buf.WriteString(keystr)
buf.WriteByte(f.colon())
buf.WriteString(f.prettyWithFlags(it.Value().Interface(), 0, depth+1))
i++
}
buf.WriteByte('}')
return buf.String()
case reflect.Ptr, reflect.Interface:
if v.IsNil() {
return "null"
}
return f.prettyWithFlags(v.Elem().Interface(), 0, depth)
}
return fmt.Sprintf(`"<unhandled-%s>"`, t.Kind().String())
}
func prettyString(s string) string {
// Avoid escaping (which does allocations) if we can.
if needsEscape(s) {
return strconv.Quote(s)
}
b := bytes.NewBuffer(make([]byte, 0, 1024))
b.WriteByte('"')
b.WriteString(s)
b.WriteByte('"')
return b.String()
}
// needsEscape determines whether the input string needs to be escaped or not,
// without doing any allocations.
func needsEscape(s string) bool {
for _, r := range s {
if !strconv.IsPrint(r) || r == '\\' || r == '"' {
return true
}
}
return false
}
func isEmpty(v reflect.Value) bool {
switch v.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0
case reflect.Bool:
return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Complex64, reflect.Complex128:
return v.Complex() == 0
case reflect.Interface, reflect.Ptr:
return v.IsNil()
}
return false
}
func invokeMarshaler(m logr.Marshaler) (ret any) {
defer func() {
if r := recover(); r != nil {
ret = fmt.Sprintf("<panic: %s>", r)
}
}()
return m.MarshalLog()
}
func invokeStringer(s fmt.Stringer) (ret string) {
defer func() {
if r := recover(); r != nil {
ret = fmt.Sprintf("<panic: %s>", r)
}
}()
return s.String()
}
func invokeError(e error) (ret string) {
defer func() {
if r := recover(); r != nil {
ret = fmt.Sprintf("<panic: %s>", r)
}
}()
return e.Error()
}
// Caller represents the original call site for a log line, after considering
// logr.Logger.WithCallDepth and logr.Logger.WithCallStackHelper. The File and
// Line fields will always be provided, while the Func field is optional.
// Users can set the render hook fields in Options to examine logged key-value
// pairs, one of which will be {"caller", Caller} if the Options.LogCaller
// field is enabled for the given MessageClass.
type Caller struct {
// File is the basename of the file for this call site.
File string `json:"file"`
// Line is the line number in the file for this call site.
Line int `json:"line"`
// Func is the function name for this call site, or empty if
// Options.LogCallerFunc is not enabled.
Func string `json:"function,omitempty"`
}
func (f Formatter) caller() Caller {
// +1 for this frame, +1 for Info/Error.
pc, file, line, ok := runtime.Caller(f.depth + 2)
if !ok {
return Caller{"<unknown>", 0, ""}
}
fn := ""
if f.opts.LogCallerFunc {
if fp := runtime.FuncForPC(pc); fp != nil {
fn = fp.Name()
}
}
return Caller{filepath.Base(file), line, fn}
}
const noValue = "<no-value>"
func (f Formatter) nonStringKey(v any) string {
return fmt.Sprintf("<non-string-key: %s>", f.snippet(v))
}
// snippet produces a short snippet string of an arbitrary value.
func (f Formatter) snippet(v any) string {
const snipLen = 16
snip := f.pretty(v)
if len(snip) > snipLen {
snip = snip[:snipLen]
}
return snip
}
// sanitize ensures that a list of key-value pairs has a value for every key
// (adding a value if needed) and that each key is a string (substituting a key
// if needed).
func (f Formatter) sanitize(kvList []any) []any {
if len(kvList)%2 != 0 {
kvList = append(kvList, noValue)
}
for i := 0; i < len(kvList); i += 2 {
_, ok := kvList[i].(string)
if !ok {
kvList[i] = f.nonStringKey(kvList[i])
}
}
return kvList
}
// startGroup opens a new group scope (basically a sub-struct), which locks all
// the current saved values and starts them anew. This is needed to satisfy
// slog.
func (f *Formatter) startGroup(group string) {
// Unnamed groups are just inlined.
if group == "" {
return
}
// Any saved values can no longer be changed.
buf := bytes.NewBuffer(make([]byte, 0, 1024))
continuing := false
if f.parentValuesStr != "" {
buf.WriteString(f.parentValuesStr)
continuing = true
}
if f.group != "" && f.valuesStr != "" {
if continuing {
buf.WriteByte(f.comma())
}
buf.WriteString(f.quoted(f.group, true)) // escape user-provided keys
buf.WriteByte(f.colon())
buf.WriteByte('{') // for the group
continuing = false
}
if f.valuesStr != "" {
if continuing {
buf.WriteByte(f.comma())
}
buf.WriteString(f.valuesStr)
}
// NOTE: We don't close the scope here - that's done later, when a log line
// is actually rendered (because we have N scopes to close).
f.parentValuesStr = buf.String()
// Start collecting new values.
f.group = group
f.groupDepth++
f.valuesStr = ""
f.values = nil
}
// Init configures this Formatter from runtime info, such as the call depth
// imposed by logr itself.
// Note that this receiver is a pointer, so depth can be saved.
func (f *Formatter) Init(info logr.RuntimeInfo) {
f.depth += info.CallDepth
}
// Enabled checks whether an info message at the given level should be logged.
func (f Formatter) Enabled(level int) bool {
return level <= f.opts.Verbosity
}
// GetDepth returns the current depth of this Formatter. This is useful for
// implementations which do their own caller attribution.
func (f Formatter) GetDepth() int {
return f.depth
}
// FormatInfo renders an Info log message into strings. The prefix will be
// empty when no names were set (via AddNames), or when the output is
// configured for JSON.
func (f Formatter) FormatInfo(level int, msg string, kvList []any) (prefix, argsStr string) {
args := make([]any, 0, 64) // using a constant here impacts perf
prefix = f.prefix
if f.outputFormat == outputJSON {
args = append(args, "logger", prefix)
prefix = ""
}
if f.opts.LogTimestamp {
args = append(args, "ts", time.Now().Format(f.opts.TimestampFormat))
}
if policy := f.opts.LogCaller; policy == All || policy == Info {
args = append(args, "caller", f.caller())
}
if key := *f.opts.LogInfoLevel; key != "" {
args = append(args, key, level)
}
args = append(args, "msg", msg)
return prefix, f.render(args, kvList)
}
// FormatError renders an Error log message into strings. The prefix will be
// empty when no names were set (via AddNames), or when the output is
// configured for JSON.
func (f Formatter) FormatError(err error, msg string, kvList []any) (prefix, argsStr string) {
args := make([]any, 0, 64) // using a constant here impacts perf
prefix = f.prefix
if f.outputFormat == outputJSON {
args = append(args, "logger", prefix)
prefix = ""
}
if f.opts.LogTimestamp {
args = append(args, "ts", time.Now().Format(f.opts.TimestampFormat))
}
if policy := f.opts.LogCaller; policy == All || policy == Error {
args = append(args, "caller", f.caller())
}
args = append(args, "msg", msg)
var loggableErr any
if err != nil {
loggableErr = err.Error()
}
args = append(args, "error", loggableErr)
return prefix, f.render(args, kvList)
}
// AddName appends the specified name. funcr uses '/' characters to separate
// name elements. Callers should not pass '/' in the provided name string, but
// this library does not actually enforce that.
func (f *Formatter) AddName(name string) {
if len(f.prefix) > 0 {
f.prefix += "/"
}
f.prefix += name
}
// AddValues adds key-value pairs to the set of saved values to be logged with
// each log line.
func (f *Formatter) AddValues(kvList []any) {
// Three slice args forces a copy.
n := len(f.values)
f.values = append(f.values[:n:n], kvList...)
vals := f.values
if hook := f.opts.RenderValuesHook; hook != nil {
vals = hook(f.sanitize(vals))
}
// Pre-render values, so we don't have to do it on each Info/Error call.
buf := bytes.NewBuffer(make([]byte, 0, 1024))
f.flatten(buf, vals, false, true) // escape user-provided keys
f.valuesStr = buf.String()
}
// AddCallDepth increases the number of stack-frames to skip when attributing
// the log line to a file and line.
func (f *Formatter) AddCallDepth(depth int) {
f.depth += depth
}

105
vendor/github.com/go-logr/logr/funcr/slogsink.go generated vendored Normal file
View File

@@ -0,0 +1,105 @@
//go:build go1.21
// +build go1.21
/*
Copyright 2023 The logr Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package funcr
import (
"context"
"log/slog"
"github.com/go-logr/logr"
)
var _ logr.SlogSink = &fnlogger{}
const extraSlogSinkDepth = 3 // 2 for slog, 1 for SlogSink
func (l fnlogger) Handle(_ context.Context, record slog.Record) error {
kvList := make([]any, 0, 2*record.NumAttrs())
record.Attrs(func(attr slog.Attr) bool {
kvList = attrToKVs(attr, kvList)
return true
})
if record.Level >= slog.LevelError {
l.WithCallDepth(extraSlogSinkDepth).Error(nil, record.Message, kvList...)
} else {
level := l.levelFromSlog(record.Level)
l.WithCallDepth(extraSlogSinkDepth).Info(level, record.Message, kvList...)
}
return nil
}
func (l fnlogger) WithAttrs(attrs []slog.Attr) logr.SlogSink {
kvList := make([]any, 0, 2*len(attrs))
for _, attr := range attrs {
kvList = attrToKVs(attr, kvList)
}
l.AddValues(kvList)
return &l
}
func (l fnlogger) WithGroup(name string) logr.SlogSink {
l.startGroup(name)
return &l
}
// attrToKVs appends a slog.Attr to a logr-style kvList. It handle slog Groups
// and other details of slog.
func attrToKVs(attr slog.Attr, kvList []any) []any {
attrVal := attr.Value.Resolve()
if attrVal.Kind() == slog.KindGroup {
groupVal := attrVal.Group()
grpKVs := make([]any, 0, 2*len(groupVal))
for _, attr := range groupVal {
grpKVs = attrToKVs(attr, grpKVs)
}
if attr.Key == "" {
// slog says we have to inline these
kvList = append(kvList, grpKVs...)
} else {
kvList = append(kvList, attr.Key, PseudoStruct(grpKVs))
}
} else if attr.Key != "" {
kvList = append(kvList, attr.Key, attrVal.Any())
}
return kvList
}
// levelFromSlog adjusts the level by the logger's verbosity and negates it.
// It ensures that the result is >= 0. This is necessary because the result is
// passed to a LogSink and that API did not historically document whether
// levels could be negative or what that meant.
//
// Some example usage:
//
// logrV0 := getMyLogger()
// logrV2 := logrV0.V(2)
// slogV2 := slog.New(logr.ToSlogHandler(logrV2))
// slogV2.Debug("msg") // =~ logrV2.V(4) =~ logrV0.V(6)
// slogV2.Info("msg") // =~ logrV2.V(0) =~ logrV0.V(2)
// slogv2.Warn("msg") // =~ logrV2.V(-4) =~ logrV0.V(0)
func (l fnlogger) levelFromSlog(level slog.Level) int {
result := -level
if result < 0 {
result = 0 // because LogSink doesn't expect negative V levels
}
return int(result)
}

520
vendor/github.com/go-logr/logr/logr.go generated vendored Normal file
View File

@@ -0,0 +1,520 @@
/*
Copyright 2019 The logr Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// This design derives from Dave Cheney's blog:
// http://dave.cheney.net/2015/11/05/lets-talk-about-logging
// Package logr defines a general-purpose logging API and abstract interfaces
// to back that API. Packages in the Go ecosystem can depend on this package,
// while callers can implement logging with whatever backend is appropriate.
//
// # Usage
//
// Logging is done using a Logger instance. Logger is a concrete type with
// methods, which defers the actual logging to a LogSink interface. The main
// methods of Logger are Info() and Error(). Arguments to Info() and Error()
// are key/value pairs rather than printf-style formatted strings, emphasizing
// "structured logging".
//
// With Go's standard log package, we might write:
//
// log.Printf("setting target value %s", targetValue)
//
// With logr's structured logging, we'd write:
//
// logger.Info("setting target", "value", targetValue)
//
// Errors are much the same. Instead of:
//
// log.Printf("failed to open the pod bay door for user %s: %v", user, err)
//
// We'd write:
//
// logger.Error(err, "failed to open the pod bay door", "user", user)
//
// Info() and Error() are very similar, but they are separate methods so that
// LogSink implementations can choose to do things like attach additional
// information (such as stack traces) on calls to Error(). Error() messages are
// always logged, regardless of the current verbosity. If there is no error
// instance available, passing nil is valid.
//
// # Verbosity
//
// Often we want to log information only when the application in "verbose
// mode". To write log lines that are more verbose, Logger has a V() method.
// The higher the V-level of a log line, the less critical it is considered.
// Log-lines with V-levels that are not enabled (as per the LogSink) will not
// be written. Level V(0) is the default, and logger.V(0).Info() has the same
// meaning as logger.Info(). Negative V-levels have the same meaning as V(0).
// Error messages do not have a verbosity level and are always logged.
//
// Where we might have written:
//
// if flVerbose >= 2 {
// log.Printf("an unusual thing happened")
// }
//
// We can write:
//
// logger.V(2).Info("an unusual thing happened")
//
// # Logger Names
//
// Logger instances can have name strings so that all messages logged through
// that instance have additional context. For example, you might want to add
// a subsystem name:
//
// logger.WithName("compactor").Info("started", "time", time.Now())
//
// The WithName() method returns a new Logger, which can be passed to
// constructors or other functions for further use. Repeated use of WithName()
// will accumulate name "segments". These name segments will be joined in some
// way by the LogSink implementation. It is strongly recommended that name
// segments contain simple identifiers (letters, digits, and hyphen), and do
// not contain characters that could muddle the log output or confuse the
// joining operation (e.g. whitespace, commas, periods, slashes, brackets,
// quotes, etc).
//
// # Saved Values
//
// Logger instances can store any number of key/value pairs, which will be
// logged alongside all messages logged through that instance. For example,
// you might want to create a Logger instance per managed object:
//
// With the standard log package, we might write:
//
// log.Printf("decided to set field foo to value %q for object %s/%s",
// targetValue, object.Namespace, object.Name)
//
// With logr we'd write:
//
// // Elsewhere: set up the logger to log the object name.
// obj.logger = mainLogger.WithValues(
// "name", obj.name, "namespace", obj.namespace)
//
// // later on...
// obj.logger.Info("setting foo", "value", targetValue)
//
// # Best Practices
//
// Logger has very few hard rules, with the goal that LogSink implementations
// might have a lot of freedom to differentiate. There are, however, some
// things to consider.
//
// The log message consists of a constant message attached to the log line.
// This should generally be a simple description of what's occurring, and should
// never be a format string. Variable information can then be attached using
// named values.
//
// Keys are arbitrary strings, but should generally be constant values. Values
// may be any Go value, but how the value is formatted is determined by the
// LogSink implementation.
//
// Logger instances are meant to be passed around by value. Code that receives
// such a value can call its methods without having to check whether the
// instance is ready for use.
//
// The zero logger (= Logger{}) is identical to Discard() and discards all log
// entries. Code that receives a Logger by value can simply call it, the methods
// will never crash. For cases where passing a logger is optional, a pointer to Logger
// should be used.
//
// # Key Naming Conventions
//
// Keys are not strictly required to conform to any specification or regex, but
// it is recommended that they:
// - be human-readable and meaningful (not auto-generated or simple ordinals)
// - be constant (not dependent on input data)
// - contain only printable characters
// - not contain whitespace or punctuation
// - use lower case for simple keys and lowerCamelCase for more complex ones
//
// These guidelines help ensure that log data is processed properly regardless
// of the log implementation. For example, log implementations will try to
// output JSON data or will store data for later database (e.g. SQL) queries.
//
// While users are generally free to use key names of their choice, it's
// generally best to avoid using the following keys, as they're frequently used
// by implementations:
// - "caller": the calling information (file/line) of a particular log line
// - "error": the underlying error value in the `Error` method
// - "level": the log level
// - "logger": the name of the associated logger
// - "msg": the log message
// - "stacktrace": the stack trace associated with a particular log line or
// error (often from the `Error` message)
// - "ts": the timestamp for a log line
//
// Implementations are encouraged to make use of these keys to represent the
// above concepts, when necessary (for example, in a pure-JSON output form, it
// would be necessary to represent at least message and timestamp as ordinary
// named values).
//
// # Break Glass
//
// Implementations may choose to give callers access to the underlying
// logging implementation. The recommended pattern for this is:
//
// // Underlier exposes access to the underlying logging implementation.
// // Since callers only have a logr.Logger, they have to know which
// // implementation is in use, so this interface is less of an abstraction
// // and more of way to test type conversion.
// type Underlier interface {
// GetUnderlying() <underlying-type>
// }
//
// Logger grants access to the sink to enable type assertions like this:
//
// func DoSomethingWithImpl(log logr.Logger) {
// if underlier, ok := log.GetSink().(impl.Underlier); ok {
// implLogger := underlier.GetUnderlying()
// ...
// }
// }
//
// Custom `With*` functions can be implemented by copying the complete
// Logger struct and replacing the sink in the copy:
//
// // WithFooBar changes the foobar parameter in the log sink and returns a
// // new logger with that modified sink. It does nothing for loggers where
// // the sink doesn't support that parameter.
// func WithFoobar(log logr.Logger, foobar int) logr.Logger {
// if foobarLogSink, ok := log.GetSink().(FoobarSink); ok {
// log = log.WithSink(foobarLogSink.WithFooBar(foobar))
// }
// return log
// }
//
// Don't use New to construct a new Logger with a LogSink retrieved from an
// existing Logger. Source code attribution might not work correctly and
// unexported fields in Logger get lost.
//
// Beware that the same LogSink instance may be shared by different logger
// instances. Calling functions that modify the LogSink will affect all of
// those.
package logr
// New returns a new Logger instance. This is primarily used by libraries
// implementing LogSink, rather than end users. Passing a nil sink will create
// a Logger which discards all log lines.
func New(sink LogSink) Logger {
logger := Logger{}
logger.setSink(sink)
if sink != nil {
sink.Init(runtimeInfo)
}
return logger
}
// setSink stores the sink and updates any related fields. It mutates the
// logger and thus is only safe to use for loggers that are not currently being
// used concurrently.
func (l *Logger) setSink(sink LogSink) {
l.sink = sink
}
// GetSink returns the stored sink.
func (l Logger) GetSink() LogSink {
return l.sink
}
// WithSink returns a copy of the logger with the new sink.
func (l Logger) WithSink(sink LogSink) Logger {
l.setSink(sink)
return l
}
// Logger is an interface to an abstract logging implementation. This is a
// concrete type for performance reasons, but all the real work is passed on to
// a LogSink. Implementations of LogSink should provide their own constructors
// that return Logger, not LogSink.
//
// The underlying sink can be accessed through GetSink and be modified through
// WithSink. This enables the implementation of custom extensions (see "Break
// Glass" in the package documentation). Normally the sink should be used only
// indirectly.
type Logger struct {
sink LogSink
level int
}
// Enabled tests whether this Logger is enabled. For example, commandline
// flags might be used to set the logging verbosity and disable some info logs.
func (l Logger) Enabled() bool {
// Some implementations of LogSink look at the caller in Enabled (e.g.
// different verbosity levels per package or file), but we only pass one
// CallDepth in (via Init). This means that all calls from Logger to the
// LogSink's Enabled, Info, and Error methods must have the same number of
// frames. In other words, Logger methods can't call other Logger methods
// which call these LogSink methods unless we do it the same in all paths.
return l.sink != nil && l.sink.Enabled(l.level)
}
// Info logs a non-error message with the given key/value pairs as context.
//
// The msg argument should be used to add some constant description to the log
// line. The key/value pairs can then be used to add additional variable
// information. The key/value pairs must alternate string keys and arbitrary
// values.
func (l Logger) Info(msg string, keysAndValues ...any) {
if l.sink == nil {
return
}
if l.sink.Enabled(l.level) { // see comment in Enabled
if withHelper, ok := l.sink.(CallStackHelperLogSink); ok {
withHelper.GetCallStackHelper()()
}
l.sink.Info(l.level, msg, keysAndValues...)
}
}
// Error logs an error, with the given message and key/value pairs as context.
// It functions similarly to Info, but may have unique behavior, and should be
// preferred for logging errors (see the package documentations for more
// information). The log message will always be emitted, regardless of
// verbosity level.
//
// The msg argument should be used to add context to any underlying error,
// while the err argument should be used to attach the actual error that
// triggered this log line, if present. The err parameter is optional
// and nil may be passed instead of an error instance.
func (l Logger) Error(err error, msg string, keysAndValues ...any) {
if l.sink == nil {
return
}
if withHelper, ok := l.sink.(CallStackHelperLogSink); ok {
withHelper.GetCallStackHelper()()
}
l.sink.Error(err, msg, keysAndValues...)
}
// V returns a new Logger instance for a specific verbosity level, relative to
// this Logger. In other words, V-levels are additive. A higher verbosity
// level means a log message is less important. Negative V-levels are treated
// as 0.
func (l Logger) V(level int) Logger {
if l.sink == nil {
return l
}
if level < 0 {
level = 0
}
l.level += level
return l
}
// GetV returns the verbosity level of the logger. If the logger's LogSink is
// nil as in the Discard logger, this will always return 0.
func (l Logger) GetV() int {
// 0 if l.sink nil because of the if check in V above.
return l.level
}
// WithValues returns a new Logger instance with additional key/value pairs.
// See Info for documentation on how key/value pairs work.
func (l Logger) WithValues(keysAndValues ...any) Logger {
if l.sink == nil {
return l
}
l.setSink(l.sink.WithValues(keysAndValues...))
return l
}
// WithName returns a new Logger instance with the specified name element added
// to the Logger's name. Successive calls with WithName append additional
// suffixes to the Logger's name. It's strongly recommended that name segments
// contain only letters, digits, and hyphens (see the package documentation for
// more information).
func (l Logger) WithName(name string) Logger {
if l.sink == nil {
return l
}
l.setSink(l.sink.WithName(name))
return l
}
// WithCallDepth returns a Logger instance that offsets the call stack by the
// specified number of frames when logging call site information, if possible.
// This is useful for users who have helper functions between the "real" call
// site and the actual calls to Logger methods. If depth is 0 the attribution
// should be to the direct caller of this function. If depth is 1 the
// attribution should skip 1 call frame, and so on. Successive calls to this
// are additive.
//
// If the underlying log implementation supports a WithCallDepth(int) method,
// it will be called and the result returned. If the implementation does not
// support CallDepthLogSink, the original Logger will be returned.
//
// To skip one level, WithCallStackHelper() should be used instead of
// WithCallDepth(1) because it works with implementions that support the
// CallDepthLogSink and/or CallStackHelperLogSink interfaces.
func (l Logger) WithCallDepth(depth int) Logger {
if l.sink == nil {
return l
}
if withCallDepth, ok := l.sink.(CallDepthLogSink); ok {
l.setSink(withCallDepth.WithCallDepth(depth))
}
return l
}
// WithCallStackHelper returns a new Logger instance that skips the direct
// caller when logging call site information, if possible. This is useful for
// users who have helper functions between the "real" call site and the actual
// calls to Logger methods and want to support loggers which depend on marking
// each individual helper function, like loggers based on testing.T.
//
// In addition to using that new logger instance, callers also must call the
// returned function.
//
// If the underlying log implementation supports a WithCallDepth(int) method,
// WithCallDepth(1) will be called to produce a new logger. If it supports a
// WithCallStackHelper() method, that will be also called. If the
// implementation does not support either of these, the original Logger will be
// returned.
func (l Logger) WithCallStackHelper() (func(), Logger) {
if l.sink == nil {
return func() {}, l
}
var helper func()
if withCallDepth, ok := l.sink.(CallDepthLogSink); ok {
l.setSink(withCallDepth.WithCallDepth(1))
}
if withHelper, ok := l.sink.(CallStackHelperLogSink); ok {
helper = withHelper.GetCallStackHelper()
} else {
helper = func() {}
}
return helper, l
}
// IsZero returns true if this logger is an uninitialized zero value
func (l Logger) IsZero() bool {
return l.sink == nil
}
// RuntimeInfo holds information that the logr "core" library knows which
// LogSinks might want to know.
type RuntimeInfo struct {
// CallDepth is the number of call frames the logr library adds between the
// end-user and the LogSink. LogSink implementations which choose to print
// the original logging site (e.g. file & line) should climb this many
// additional frames to find it.
CallDepth int
}
// runtimeInfo is a static global. It must not be changed at run time.
var runtimeInfo = RuntimeInfo{
CallDepth: 1,
}
// LogSink represents a logging implementation. End-users will generally not
// interact with this type.
type LogSink interface {
// Init receives optional information about the logr library for LogSink
// implementations that need it.
Init(info RuntimeInfo)
// Enabled tests whether this LogSink is enabled at the specified V-level.
// For example, commandline flags might be used to set the logging
// verbosity and disable some info logs.
Enabled(level int) bool
// Info logs a non-error message with the given key/value pairs as context.
// The level argument is provided for optional logging. This method will
// only be called when Enabled(level) is true. See Logger.Info for more
// details.
Info(level int, msg string, keysAndValues ...any)
// Error logs an error, with the given message and key/value pairs as
// context. See Logger.Error for more details.
Error(err error, msg string, keysAndValues ...any)
// WithValues returns a new LogSink with additional key/value pairs. See
// Logger.WithValues for more details.
WithValues(keysAndValues ...any) LogSink
// WithName returns a new LogSink with the specified name appended. See
// Logger.WithName for more details.
WithName(name string) LogSink
}
// CallDepthLogSink represents a LogSink that knows how to climb the call stack
// to identify the original call site and can offset the depth by a specified
// number of frames. This is useful for users who have helper functions
// between the "real" call site and the actual calls to Logger methods.
// Implementations that log information about the call site (such as file,
// function, or line) would otherwise log information about the intermediate
// helper functions.
//
// This is an optional interface and implementations are not required to
// support it.
type CallDepthLogSink interface {
// WithCallDepth returns a LogSink that will offset the call
// stack by the specified number of frames when logging call
// site information.
//
// If depth is 0, the LogSink should skip exactly the number
// of call frames defined in RuntimeInfo.CallDepth when Info
// or Error are called, i.e. the attribution should be to the
// direct caller of Logger.Info or Logger.Error.
//
// If depth is 1 the attribution should skip 1 call frame, and so on.
// Successive calls to this are additive.
WithCallDepth(depth int) LogSink
}
// CallStackHelperLogSink represents a LogSink that knows how to climb
// the call stack to identify the original call site and can skip
// intermediate helper functions if they mark themselves as
// helper. Go's testing package uses that approach.
//
// This is useful for users who have helper functions between the
// "real" call site and the actual calls to Logger methods.
// Implementations that log information about the call site (such as
// file, function, or line) would otherwise log information about the
// intermediate helper functions.
//
// This is an optional interface and implementations are not required
// to support it. Implementations that choose to support this must not
// simply implement it as WithCallDepth(1), because
// Logger.WithCallStackHelper will call both methods if they are
// present. This should only be implemented for LogSinks that actually
// need it, as with testing.T.
type CallStackHelperLogSink interface {
// GetCallStackHelper returns a function that must be called
// to mark the direct caller as helper function when logging
// call site information.
GetCallStackHelper() func()
}
// Marshaler is an optional interface that logged values may choose to
// implement. Loggers with structured output, such as JSON, should
// log the object return by the MarshalLog method instead of the
// original value.
type Marshaler interface {
// MarshalLog can be used to:
// - ensure that structs are not logged as strings when the original
// value has a String method: return a different type without a
// String method
// - select which fields of a complex type should get logged:
// return a simpler struct with fewer fields
// - log unexported fields: return a different struct
// with exported fields
//
// It may return any value of any type.
MarshalLog() any
}

192
vendor/github.com/go-logr/logr/sloghandler.go generated vendored Normal file
View File

@@ -0,0 +1,192 @@
//go:build go1.21
// +build go1.21
/*
Copyright 2023 The logr Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package logr
import (
"context"
"log/slog"
)
type slogHandler struct {
// May be nil, in which case all logs get discarded.
sink LogSink
// Non-nil if sink is non-nil and implements SlogSink.
slogSink SlogSink
// groupPrefix collects values from WithGroup calls. It gets added as
// prefix to value keys when handling a log record.
groupPrefix string
// levelBias can be set when constructing the handler to influence the
// slog.Level of log records. A positive levelBias reduces the
// slog.Level value. slog has no API to influence this value after the
// handler got created, so it can only be set indirectly through
// Logger.V.
levelBias slog.Level
}
var _ slog.Handler = &slogHandler{}
// groupSeparator is used to concatenate WithGroup names and attribute keys.
const groupSeparator = "."
// GetLevel is used for black box unit testing.
func (l *slogHandler) GetLevel() slog.Level {
return l.levelBias
}
func (l *slogHandler) Enabled(_ context.Context, level slog.Level) bool {
return l.sink != nil && (level >= slog.LevelError || l.sink.Enabled(l.levelFromSlog(level)))
}
func (l *slogHandler) Handle(ctx context.Context, record slog.Record) error {
if l.slogSink != nil {
// Only adjust verbosity level of log entries < slog.LevelError.
if record.Level < slog.LevelError {
record.Level -= l.levelBias
}
return l.slogSink.Handle(ctx, record)
}
// No need to check for nil sink here because Handle will only be called
// when Enabled returned true.
kvList := make([]any, 0, 2*record.NumAttrs())
record.Attrs(func(attr slog.Attr) bool {
kvList = attrToKVs(attr, l.groupPrefix, kvList)
return true
})
if record.Level >= slog.LevelError {
l.sinkWithCallDepth().Error(nil, record.Message, kvList...)
} else {
level := l.levelFromSlog(record.Level)
l.sinkWithCallDepth().Info(level, record.Message, kvList...)
}
return nil
}
// sinkWithCallDepth adjusts the stack unwinding so that when Error or Info
// are called by Handle, code in slog gets skipped.
//
// This offset currently (Go 1.21.0) works for calls through
// slog.New(ToSlogHandler(...)). There's no guarantee that the call
// chain won't change. Wrapping the handler will also break unwinding. It's
// still better than not adjusting at all....
//
// This cannot be done when constructing the handler because FromSlogHandler needs
// access to the original sink without this adjustment. A second copy would
// work, but then WithAttrs would have to be called for both of them.
func (l *slogHandler) sinkWithCallDepth() LogSink {
if sink, ok := l.sink.(CallDepthLogSink); ok {
return sink.WithCallDepth(2)
}
return l.sink
}
func (l *slogHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
if l.sink == nil || len(attrs) == 0 {
return l
}
clone := *l
if l.slogSink != nil {
clone.slogSink = l.slogSink.WithAttrs(attrs)
clone.sink = clone.slogSink
} else {
kvList := make([]any, 0, 2*len(attrs))
for _, attr := range attrs {
kvList = attrToKVs(attr, l.groupPrefix, kvList)
}
clone.sink = l.sink.WithValues(kvList...)
}
return &clone
}
func (l *slogHandler) WithGroup(name string) slog.Handler {
if l.sink == nil {
return l
}
if name == "" {
// slog says to inline empty groups
return l
}
clone := *l
if l.slogSink != nil {
clone.slogSink = l.slogSink.WithGroup(name)
clone.sink = clone.slogSink
} else {
clone.groupPrefix = addPrefix(clone.groupPrefix, name)
}
return &clone
}
// attrToKVs appends a slog.Attr to a logr-style kvList. It handle slog Groups
// and other details of slog.
func attrToKVs(attr slog.Attr, groupPrefix string, kvList []any) []any {
attrVal := attr.Value.Resolve()
if attrVal.Kind() == slog.KindGroup {
groupVal := attrVal.Group()
grpKVs := make([]any, 0, 2*len(groupVal))
prefix := groupPrefix
if attr.Key != "" {
prefix = addPrefix(groupPrefix, attr.Key)
}
for _, attr := range groupVal {
grpKVs = attrToKVs(attr, prefix, grpKVs)
}
kvList = append(kvList, grpKVs...)
} else if attr.Key != "" {
kvList = append(kvList, addPrefix(groupPrefix, attr.Key), attrVal.Any())
}
return kvList
}
func addPrefix(prefix, name string) string {
if prefix == "" {
return name
}
if name == "" {
return prefix
}
return prefix + groupSeparator + name
}
// levelFromSlog adjusts the level by the logger's verbosity and negates it.
// It ensures that the result is >= 0. This is necessary because the result is
// passed to a LogSink and that API did not historically document whether
// levels could be negative or what that meant.
//
// Some example usage:
//
// logrV0 := getMyLogger()
// logrV2 := logrV0.V(2)
// slogV2 := slog.New(logr.ToSlogHandler(logrV2))
// slogV2.Debug("msg") // =~ logrV2.V(4) =~ logrV0.V(6)
// slogV2.Info("msg") // =~ logrV2.V(0) =~ logrV0.V(2)
// slogv2.Warn("msg") // =~ logrV2.V(-4) =~ logrV0.V(0)
func (l *slogHandler) levelFromSlog(level slog.Level) int {
result := -level
result += l.levelBias // in case the original Logger had a V level
if result < 0 {
result = 0 // because LogSink doesn't expect negative V levels
}
return int(result)
}

100
vendor/github.com/go-logr/logr/slogr.go generated vendored Normal file
View File

@@ -0,0 +1,100 @@
//go:build go1.21
// +build go1.21
/*
Copyright 2023 The logr Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package logr
import (
"context"
"log/slog"
)
// FromSlogHandler returns a Logger which writes to the slog.Handler.
//
// The logr verbosity level is mapped to slog levels such that V(0) becomes
// slog.LevelInfo and V(4) becomes slog.LevelDebug.
func FromSlogHandler(handler slog.Handler) Logger {
if handler, ok := handler.(*slogHandler); ok {
if handler.sink == nil {
return Discard()
}
return New(handler.sink).V(int(handler.levelBias))
}
return New(&slogSink{handler: handler})
}
// ToSlogHandler returns a slog.Handler which writes to the same sink as the Logger.
//
// The returned logger writes all records with level >= slog.LevelError as
// error log entries with LogSink.Error, regardless of the verbosity level of
// the Logger:
//
// logger := <some Logger with 0 as verbosity level>
// slog.New(ToSlogHandler(logger.V(10))).Error(...) -> logSink.Error(...)
//
// The level of all other records gets reduced by the verbosity
// level of the Logger and the result is negated. If it happens
// to be negative, then it gets replaced by zero because a LogSink
// is not expected to handled negative levels:
//
// slog.New(ToSlogHandler(logger)).Debug(...) -> logger.GetSink().Info(level=4, ...)
// slog.New(ToSlogHandler(logger)).Warning(...) -> logger.GetSink().Info(level=0, ...)
// slog.New(ToSlogHandler(logger)).Info(...) -> logger.GetSink().Info(level=0, ...)
// slog.New(ToSlogHandler(logger.V(4))).Info(...) -> logger.GetSink().Info(level=4, ...)
func ToSlogHandler(logger Logger) slog.Handler {
if sink, ok := logger.GetSink().(*slogSink); ok && logger.GetV() == 0 {
return sink.handler
}
handler := &slogHandler{sink: logger.GetSink(), levelBias: slog.Level(logger.GetV())}
if slogSink, ok := handler.sink.(SlogSink); ok {
handler.slogSink = slogSink
}
return handler
}
// SlogSink is an optional interface that a LogSink can implement to support
// logging through the slog.Logger or slog.Handler APIs better. It then should
// also support special slog values like slog.Group. When used as a
// slog.Handler, the advantages are:
//
// - stack unwinding gets avoided in favor of logging the pre-recorded PC,
// as intended by slog
// - proper grouping of key/value pairs via WithGroup
// - verbosity levels > slog.LevelInfo can be recorded
// - less overhead
//
// Both APIs (Logger and slog.Logger/Handler) then are supported equally
// well. Developers can pick whatever API suits them better and/or mix
// packages which use either API in the same binary with a common logging
// implementation.
//
// This interface is necessary because the type implementing the LogSink
// interface cannot also implement the slog.Handler interface due to the
// different prototype of the common Enabled method.
//
// An implementation could support both interfaces in two different types, but then
// additional interfaces would be needed to convert between those types in FromSlogHandler
// and ToSlogHandler.
type SlogSink interface {
LogSink
Handle(ctx context.Context, record slog.Record) error
WithAttrs(attrs []slog.Attr) SlogSink
WithGroup(name string) SlogSink
}

120
vendor/github.com/go-logr/logr/slogsink.go generated vendored Normal file
View File

@@ -0,0 +1,120 @@
//go:build go1.21
// +build go1.21
/*
Copyright 2023 The logr Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package logr
import (
"context"
"log/slog"
"runtime"
"time"
)
var (
_ LogSink = &slogSink{}
_ CallDepthLogSink = &slogSink{}
_ Underlier = &slogSink{}
)
// Underlier is implemented by the LogSink returned by NewFromLogHandler.
type Underlier interface {
// GetUnderlying returns the Handler used by the LogSink.
GetUnderlying() slog.Handler
}
const (
// nameKey is used to log the `WithName` values as an additional attribute.
nameKey = "logger"
// errKey is used to log the error parameter of Error as an additional attribute.
errKey = "err"
)
type slogSink struct {
callDepth int
name string
handler slog.Handler
}
func (l *slogSink) Init(info RuntimeInfo) {
l.callDepth = info.CallDepth
}
func (l *slogSink) GetUnderlying() slog.Handler {
return l.handler
}
func (l *slogSink) WithCallDepth(depth int) LogSink {
newLogger := *l
newLogger.callDepth += depth
return &newLogger
}
func (l *slogSink) Enabled(level int) bool {
return l.handler.Enabled(context.Background(), slog.Level(-level))
}
func (l *slogSink) Info(level int, msg string, kvList ...interface{}) {
l.log(nil, msg, slog.Level(-level), kvList...)
}
func (l *slogSink) Error(err error, msg string, kvList ...interface{}) {
l.log(err, msg, slog.LevelError, kvList...)
}
func (l *slogSink) log(err error, msg string, level slog.Level, kvList ...interface{}) {
var pcs [1]uintptr
// skip runtime.Callers, this function, Info/Error, and all helper functions above that.
runtime.Callers(3+l.callDepth, pcs[:])
record := slog.NewRecord(time.Now(), level, msg, pcs[0])
if l.name != "" {
record.AddAttrs(slog.String(nameKey, l.name))
}
if err != nil {
record.AddAttrs(slog.Any(errKey, err))
}
record.Add(kvList...)
_ = l.handler.Handle(context.Background(), record)
}
func (l slogSink) WithName(name string) LogSink {
if l.name != "" {
l.name += "/"
}
l.name += name
return &l
}
func (l slogSink) WithValues(kvList ...interface{}) LogSink {
l.handler = l.handler.WithAttrs(kvListToAttrs(kvList...))
return &l
}
func kvListToAttrs(kvList ...interface{}) []slog.Attr {
// We don't need the record itself, only its Add method.
record := slog.NewRecord(time.Time{}, 0, "", 0)
record.Add(kvList...)
attrs := make([]slog.Attr, 0, record.NumAttrs())
record.Attrs(func(attr slog.Attr) bool {
attrs = append(attrs, attr)
return true
})
return attrs
}

201
vendor/github.com/go-logr/stdr/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

6
vendor/github.com/go-logr/stdr/README.md generated vendored Normal file
View File

@@ -0,0 +1,6 @@
# Minimal Go logging using logr and Go's standard library
[![Go Reference](https://pkg.go.dev/badge/github.com/go-logr/stdr.svg)](https://pkg.go.dev/github.com/go-logr/stdr)
This package implements the [logr interface](https://github.com/go-logr/logr)
in terms of Go's standard log package(https://pkg.go.dev/log).

170
vendor/github.com/go-logr/stdr/stdr.go generated vendored Normal file
View File

@@ -0,0 +1,170 @@
/*
Copyright 2019 The logr Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package stdr implements github.com/go-logr/logr.Logger in terms of
// Go's standard log package.
package stdr
import (
"log"
"os"
"github.com/go-logr/logr"
"github.com/go-logr/logr/funcr"
)
// The global verbosity level. See SetVerbosity().
var globalVerbosity int
// SetVerbosity sets the global level against which all info logs will be
// compared. If this is greater than or equal to the "V" of the logger, the
// message will be logged. A higher value here means more logs will be written.
// The previous verbosity value is returned. This is not concurrent-safe -
// callers must be sure to call it from only one goroutine.
func SetVerbosity(v int) int {
old := globalVerbosity
globalVerbosity = v
return old
}
// New returns a logr.Logger which is implemented by Go's standard log package,
// or something like it. If std is nil, this will use a default logger
// instead.
//
// Example: stdr.New(log.New(os.Stderr, "", log.LstdFlags|log.Lshortfile)))
func New(std StdLogger) logr.Logger {
return NewWithOptions(std, Options{})
}
// NewWithOptions returns a logr.Logger which is implemented by Go's standard
// log package, or something like it. See New for details.
func NewWithOptions(std StdLogger, opts Options) logr.Logger {
if std == nil {
// Go's log.Default() is only available in 1.16 and higher.
std = log.New(os.Stderr, "", log.LstdFlags)
}
if opts.Depth < 0 {
opts.Depth = 0
}
fopts := funcr.Options{
LogCaller: funcr.MessageClass(opts.LogCaller),
}
sl := &logger{
Formatter: funcr.NewFormatter(fopts),
std: std,
}
// For skipping our own logger.Info/Error.
sl.Formatter.AddCallDepth(1 + opts.Depth)
return logr.New(sl)
}
// Options carries parameters which influence the way logs are generated.
type Options struct {
// Depth biases the assumed number of call frames to the "true" caller.
// This is useful when the calling code calls a function which then calls
// stdr (e.g. a logging shim to another API). Values less than zero will
// be treated as zero.
Depth int
// LogCaller tells stdr to add a "caller" key to some or all log lines.
// Go's log package has options to log this natively, too.
LogCaller MessageClass
// TODO: add an option to log the date/time
}
// MessageClass indicates which category or categories of messages to consider.
type MessageClass int
const (
// None ignores all message classes.
None MessageClass = iota
// All considers all message classes.
All
// Info only considers info messages.
Info
// Error only considers error messages.
Error
)
// StdLogger is the subset of the Go stdlib log.Logger API that is needed for
// this adapter.
type StdLogger interface {
// Output is the same as log.Output and log.Logger.Output.
Output(calldepth int, logline string) error
}
type logger struct {
funcr.Formatter
std StdLogger
}
var _ logr.LogSink = &logger{}
var _ logr.CallDepthLogSink = &logger{}
func (l logger) Enabled(level int) bool {
return globalVerbosity >= level
}
func (l logger) Info(level int, msg string, kvList ...interface{}) {
prefix, args := l.FormatInfo(level, msg, kvList)
if prefix != "" {
args = prefix + ": " + args
}
_ = l.std.Output(l.Formatter.GetDepth()+1, args)
}
func (l logger) Error(err error, msg string, kvList ...interface{}) {
prefix, args := l.FormatError(err, msg, kvList)
if prefix != "" {
args = prefix + ": " + args
}
_ = l.std.Output(l.Formatter.GetDepth()+1, args)
}
func (l logger) WithName(name string) logr.LogSink {
l.Formatter.AddName(name)
return &l
}
func (l logger) WithValues(kvList ...interface{}) logr.LogSink {
l.Formatter.AddValues(kvList)
return &l
}
func (l logger) WithCallDepth(depth int) logr.LogSink {
l.Formatter.AddCallDepth(depth)
return &l
}
// Underlier exposes access to the underlying logging implementation. Since
// callers only have a logr.Logger, they have to know which implementation is
// in use, so this interface is less of an abstraction and more of way to test
// type conversion.
type Underlier interface {
GetUnderlying() StdLogger
}
// GetUnderlying returns the StdLogger underneath this logger. Since StdLogger
// is itself an interface, the result may or may not be a Go log.Logger.
func (l logger) GetUnderlying() StdLogger {
return l.std
}

191
vendor/github.com/golang/groupcache/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,191 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, "control" means (i) the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising
permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
"Object" form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
"submitted" means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
2. Grant of Copyright License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
3. Grant of Patent License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
4. Redistribution.
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
You must give any other recipients of the Work or Derivative Works a copy of
this License; and
You must cause any modified files to carry prominent notices stating that You
changed the files; and
You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
If the Work includes a "NOTICE" text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
5. Submission of Contributions.
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
6. Trademarks.
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
7. Disclaimer of Warranty.
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
8. Limitation of Liability.
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability.
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets "[]" replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same "printed page" as the copyright notice for easier identification within
third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Some files were not shown because too many files have changed in this diff Show More