gabs
This commit is contained in:
commit
241793b157
889
gabs.go
Normal file
889
gabs.go
Normal file
@ -0,0 +1,889 @@
|
|||||||
|
// Copyright (c) 2019 Ashley Jeffs
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// Package gabs implements a wrapper around creating and parsing unknown or
|
||||||
|
// dynamic map structures resulting from JSON parsing.
|
||||||
|
package gabs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
//E "github.com/duolabmeng6/goefun/core"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
C "gitlab.lxinyu.cn/lxinyu/go/c"
|
||||||
|
)
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrOutOfBounds indicates an index was out of bounds.
|
||||||
|
ErrOutOfBounds = errors.New("out of bounds")
|
||||||
|
|
||||||
|
// ErrNotObjOrArray is returned when a target is not an object or array type
|
||||||
|
// but needs to be for the intended operation.
|
||||||
|
ErrNotObjOrArray = errors.New("not an object or array")
|
||||||
|
|
||||||
|
// ErrNotObj is returned when a target is not an object but needs to be for
|
||||||
|
// the intended operation.
|
||||||
|
ErrNotObj = errors.New("not an object")
|
||||||
|
|
||||||
|
// ErrInvalidQuery is returned when a seach query was not valid.
|
||||||
|
ErrInvalidQuery = errors.New("invalid search query")
|
||||||
|
|
||||||
|
// ErrNotArray is returned when a target is not an array but needs to be for
|
||||||
|
// the intended operation.
|
||||||
|
ErrNotArray = errors.New("not an array")
|
||||||
|
|
||||||
|
// ErrPathCollision is returned when creating a path failed because an
|
||||||
|
// element collided with an existing value.
|
||||||
|
ErrPathCollision = errors.New("encountered value collision whilst building path")
|
||||||
|
|
||||||
|
// ErrInvalidInputObj is returned when the input value was not a
|
||||||
|
// map[string]interface{}.
|
||||||
|
ErrInvalidInputObj = errors.New("invalid input object")
|
||||||
|
|
||||||
|
// ErrInvalidInputText is returned when the input data could not be parsed.
|
||||||
|
ErrInvalidInputText = errors.New("input text could not be parsed")
|
||||||
|
|
||||||
|
// ErrNotFound is returned when a query leaf is not found.
|
||||||
|
ErrNotFound = errors.New("field not found")
|
||||||
|
|
||||||
|
// ErrInvalidPath is returned when the filepath was not valid.
|
||||||
|
ErrInvalidPath = errors.New("invalid file path")
|
||||||
|
|
||||||
|
// ErrInvalidBuffer is returned when the input buffer contained an invalid
|
||||||
|
// JSON string.
|
||||||
|
ErrInvalidBuffer = errors.New("input buffer contained invalid JSON")
|
||||||
|
)
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// JSONPointerToSlice parses a JSON pointer path
|
||||||
|
// (https://tools.ietf.org/html/rfc6901) and returns the path segments as a
|
||||||
|
// slice.
|
||||||
|
//
|
||||||
|
// Because the characters '~' (%x7E) and '/' (%x2F) have special meanings in
|
||||||
|
// gabs paths, '~' needs to be encoded as '~0' and '/' needs to be encoded as
|
||||||
|
// '~1' when these characters appear in a reference key.
|
||||||
|
func JSONPointerToSlice(path string) ([]string, error) {
|
||||||
|
if len(path) < 1 {
|
||||||
|
return nil, errors.New("failed to resolve JSON pointer: path must not be empty")
|
||||||
|
}
|
||||||
|
if path[0] != '/' {
|
||||||
|
return nil, errors.New("failed to resolve JSON pointer: path must begin with '/'")
|
||||||
|
}
|
||||||
|
hierarchy := strings.Split(path, "/")[1:]
|
||||||
|
for i, v := range hierarchy {
|
||||||
|
v = strings.Replace(v, "~1", "/", -1)
|
||||||
|
v = strings.Replace(v, "~0", "~", -1)
|
||||||
|
hierarchy[i] = v
|
||||||
|
}
|
||||||
|
return hierarchy, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DotPathToSlice returns a slice of path segments parsed out of a dot path.
|
||||||
|
//
|
||||||
|
// Because the characters '~' (%x7E) and '.' (%x2E) have special meanings in
|
||||||
|
// gabs paths, '~' needs to be encoded as '~0' and '.' needs to be encoded as
|
||||||
|
// '~1' when these characters appear in a reference key.
|
||||||
|
func DotPathToSlice(path string) []string {
|
||||||
|
hierarchy := strings.Split(path, ".")
|
||||||
|
for i, v := range hierarchy {
|
||||||
|
v = strings.Replace(v, "~1", ".", -1)
|
||||||
|
v = strings.Replace(v, "~0", "~", -1)
|
||||||
|
hierarchy[i] = v
|
||||||
|
}
|
||||||
|
return hierarchy
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Container references a specific element within a wrapped structure.
|
||||||
|
type Container struct {
|
||||||
|
object interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data returns the underlying value of the target element in the wrapped
|
||||||
|
// structure.
|
||||||
|
func (g *Container) Data() interface{} {
|
||||||
|
if g == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return g.object
|
||||||
|
}
|
||||||
|
func (g *Container) ToString() string {
|
||||||
|
if g == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
//return g.object.(string)
|
||||||
|
return C.C到文本(g.object)
|
||||||
|
}
|
||||||
|
func (g *Container) ToInt() int {
|
||||||
|
if g == nil {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
int, _ := strconv.Atoi(g.object.(string))
|
||||||
|
return int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Container) GetString(key string) string {
|
||||||
|
if g.ExistsP(key) {
|
||||||
|
value := g.Path(key).Data()
|
||||||
|
return C.C到文本(value)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
func (g *Container) GetArrayAllData(s string) []*Container {
|
||||||
|
objects := g.Path(s).Children()
|
||||||
|
return objects
|
||||||
|
}
|
||||||
|
func (g *Container) GetArrayCount(s string) int {
|
||||||
|
int, _ := g.ArrayCountP(s)
|
||||||
|
return int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Container) E取文本(key string) string {
|
||||||
|
return g.GetString(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Container) GetInt(key string) int64 {
|
||||||
|
if g.ExistsP(key) {
|
||||||
|
value := g.Path(key).Data().(int64)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
func (g *Container) E取值(key string) int64 {
|
||||||
|
return g.GetInt(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Container) GetBool(key string) bool {
|
||||||
|
if g.ExistsP(key) {
|
||||||
|
value := g.Path(key).Data().(bool)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
func (g *Container) E取逻辑值(key string) bool {
|
||||||
|
return g.GetBool(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func (g *Container) searchStrict(allowWildcard bool, hierarchy ...string) (*Container, error) {
|
||||||
|
object := g.Data()
|
||||||
|
for target := 0; target < len(hierarchy); target++ {
|
||||||
|
pathSeg := hierarchy[target]
|
||||||
|
if mmap, ok := object.(map[string]interface{}); ok {
|
||||||
|
object, ok = mmap[pathSeg]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to resolve path segment '%v': key '%v' was not found", target, pathSeg)
|
||||||
|
}
|
||||||
|
} else if marray, ok := object.([]interface{}); ok {
|
||||||
|
if allowWildcard && pathSeg == "*" {
|
||||||
|
tmpArray := []interface{}{}
|
||||||
|
for _, val := range marray {
|
||||||
|
if (target + 1) >= len(hierarchy) {
|
||||||
|
tmpArray = append(tmpArray, val)
|
||||||
|
} else if res := Wrap(val).Search(hierarchy[target+1:]...); res != nil {
|
||||||
|
tmpArray = append(tmpArray, res.Data())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(tmpArray) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return &Container{tmpArray}, nil
|
||||||
|
}
|
||||||
|
index, err := strconv.Atoi(pathSeg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to resolve path segment '%v': found array but segment value '%v' could not be parsed into array index: %v", target, pathSeg, err)
|
||||||
|
}
|
||||||
|
if len(marray) <= index {
|
||||||
|
return nil, fmt.Errorf("failed to resolve path segment '%v': found array but index '%v' exceeded target array size of '%v'", target, pathSeg, len(marray))
|
||||||
|
}
|
||||||
|
object = marray[index]
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("failed to resolve path segment '%v': field '%v' was not found", target, pathSeg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &Container{object}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search attempts to find and return an object within the wrapped structure by
|
||||||
|
// following a provided hierarchy of field names to locate the target.
|
||||||
|
//
|
||||||
|
// If the search encounters an array then the next hierarchy field name must be
|
||||||
|
// either a an integer which is interpreted as the index of the target, or the
|
||||||
|
// character '*', in which case all elements are searched with the remaining
|
||||||
|
// search hierarchy and the results returned within an array.
|
||||||
|
func (g *Container) Search(hierarchy ...string) *Container {
|
||||||
|
c, _ := g.searchStrict(true, hierarchy...)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path searches the wrapped structure following a path in dot notation,
|
||||||
|
// segments of this path are searched according to the same rules as Search.
|
||||||
|
//
|
||||||
|
// Because the characters '~' (%x7E) and '.' (%x2E) have special meanings in
|
||||||
|
// gabs paths, '~' needs to be encoded as '~0' and '.' needs to be encoded as
|
||||||
|
// '~1' when these characters appear in a reference key.
|
||||||
|
func (g *Container) Path(path string) *Container {
|
||||||
|
return g.Search(DotPathToSlice(path)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONPointer parses a JSON pointer path (https://tools.ietf.org/html/rfc6901)
|
||||||
|
// and either returns a *gabs.Container containing the result or an error if the
|
||||||
|
// referenced item could not be found.
|
||||||
|
//
|
||||||
|
// Because the characters '~' (%x7E) and '/' (%x2F) have special meanings in
|
||||||
|
// gabs paths, '~' needs to be encoded as '~0' and '/' needs to be encoded as
|
||||||
|
// '~1' when these characters appear in a reference key.
|
||||||
|
func (g *Container) JSONPointer(path string) (*Container, error) {
|
||||||
|
hierarchy, err := JSONPointerToSlice(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return g.searchStrict(false, hierarchy...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// S is a shorthand alias for Search.
|
||||||
|
func (g *Container) S(hierarchy ...string) *Container {
|
||||||
|
return g.Search(hierarchy...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exists checks whether a field exists within the hierarchy.
|
||||||
|
func (g *Container) Exists(hierarchy ...string) bool {
|
||||||
|
return g.Search(hierarchy...) != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExistsP checks whether a dot notation path exists.
|
||||||
|
func (g *Container) ExistsP(path string) bool {
|
||||||
|
return g.Exists(DotPathToSlice(path)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index attempts to find and return an element within a JSON array by an index.
|
||||||
|
func (g *Container) Index(index int) *Container {
|
||||||
|
if array, ok := g.Data().([]interface{}); ok {
|
||||||
|
if index >= len(array) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &Container{array[index]}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Children returns a slice of all children of an array element. This also works
|
||||||
|
// for objects, however, the children returned for an object will be in a random
|
||||||
|
// order and you lose the names of the returned objects this way. If the
|
||||||
|
// underlying container value isn't an array or map nil is returned.
|
||||||
|
func (g *Container) Children() []*Container {
|
||||||
|
if array, ok := g.Data().([]interface{}); ok {
|
||||||
|
children := make([]*Container, len(array))
|
||||||
|
for i := 0; i < len(array); i++ {
|
||||||
|
children[i] = &Container{array[i]}
|
||||||
|
}
|
||||||
|
return children
|
||||||
|
}
|
||||||
|
if mmap, ok := g.Data().(map[string]interface{}); ok {
|
||||||
|
children := []*Container{}
|
||||||
|
for _, obj := range mmap {
|
||||||
|
children = append(children, &Container{obj})
|
||||||
|
}
|
||||||
|
return children
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChildrenMap returns a map of all the children of an object element. IF the
|
||||||
|
// underlying value isn't a object then an empty map is returned.
|
||||||
|
func (g *Container) ChildrenMap() map[string]*Container {
|
||||||
|
if mmap, ok := g.Data().(map[string]interface{}); ok {
|
||||||
|
children := make(map[string]*Container, len(mmap))
|
||||||
|
for name, obj := range mmap {
|
||||||
|
children[name] = &Container{obj}
|
||||||
|
}
|
||||||
|
return children
|
||||||
|
}
|
||||||
|
return map[string]*Container{}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Set attempts to set the value of a field located by a hierarchy of field
|
||||||
|
// names. If the search encounters an array then the next hierarchy field name
|
||||||
|
// is interpreted as an integer index of an existing element, or the character
|
||||||
|
// '-', which indicates a new element appended to the end of the array.
|
||||||
|
//
|
||||||
|
// Any parts of the hierarchy that do not exist will be constructed as objects.
|
||||||
|
// This includes parts that could be interpreted as array indexes.
|
||||||
|
//
|
||||||
|
// Returns a container of the new value or an error.
|
||||||
|
func (g *Container) Set(value interface{}, hierarchy ...string) (*Container, error) {
|
||||||
|
if g == nil {
|
||||||
|
return nil, errors.New("failed to resolve path, container is nil")
|
||||||
|
}
|
||||||
|
if len(hierarchy) == 0 {
|
||||||
|
g.object = value
|
||||||
|
return g, nil
|
||||||
|
}
|
||||||
|
if g.object == nil {
|
||||||
|
g.object = map[string]interface{}{}
|
||||||
|
}
|
||||||
|
object := g.object
|
||||||
|
|
||||||
|
for target := 0; target < len(hierarchy); target++ {
|
||||||
|
pathSeg := hierarchy[target]
|
||||||
|
if mmap, ok := object.(map[string]interface{}); ok {
|
||||||
|
if target == len(hierarchy)-1 {
|
||||||
|
object = value
|
||||||
|
mmap[pathSeg] = object
|
||||||
|
} else if object = mmap[pathSeg]; object == nil {
|
||||||
|
mmap[pathSeg] = map[string]interface{}{}
|
||||||
|
object = mmap[pathSeg]
|
||||||
|
}
|
||||||
|
} else if marray, ok := object.([]interface{}); ok {
|
||||||
|
if pathSeg == "-" {
|
||||||
|
if target < 1 {
|
||||||
|
return nil, errors.New("unable to append new array index at root of path")
|
||||||
|
}
|
||||||
|
if target == len(hierarchy)-1 {
|
||||||
|
object = value
|
||||||
|
} else {
|
||||||
|
object = map[string]interface{}{}
|
||||||
|
}
|
||||||
|
marray = append(marray, object)
|
||||||
|
if _, err := g.Set(marray, hierarchy[:target]...); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
index, err := strconv.Atoi(pathSeg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to resolve path segment '%v': found array but segment value '%v' could not be parsed into array index: %v", target, pathSeg, err)
|
||||||
|
}
|
||||||
|
if len(marray) <= index {
|
||||||
|
return nil, fmt.Errorf("failed to resolve path segment '%v': found array but index '%v' exceeded target array size of '%v'", target, pathSeg, len(marray))
|
||||||
|
}
|
||||||
|
if target == len(hierarchy)-1 {
|
||||||
|
object = value
|
||||||
|
marray[index] = object
|
||||||
|
} else if object = marray[index]; object == nil {
|
||||||
|
return nil, fmt.Errorf("failed to resolve path segment '%v': field '%v' was not found", target, pathSeg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, ErrPathCollision
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &Container{object}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetP sets the value of a field at a path using dot notation, any parts
|
||||||
|
// of the path that do not exist will be constructed, and if a collision occurs
|
||||||
|
// with a non object type whilst iterating the path an error is returned.
|
||||||
|
func (g *Container) SetP(value interface{}, path string) (*Container, error) {
|
||||||
|
return g.Set(value, DotPathToSlice(path)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetIndex attempts to set a value of an array element based on an index.
|
||||||
|
func (g *Container) SetIndex(value interface{}, index int) (*Container, error) {
|
||||||
|
if array, ok := g.Data().([]interface{}); ok {
|
||||||
|
if index >= len(array) {
|
||||||
|
return nil, ErrOutOfBounds
|
||||||
|
}
|
||||||
|
array[index] = value
|
||||||
|
return &Container{array[index]}, nil
|
||||||
|
}
|
||||||
|
return nil, ErrNotArray
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetJSONPointer parses a JSON pointer path
|
||||||
|
// (https://tools.ietf.org/html/rfc6901) and sets the leaf to a value. Returns
|
||||||
|
// an error if the pointer could not be resolved due to missing fields.
|
||||||
|
func (g *Container) SetJSONPointer(value interface{}, path string) (*Container, error) {
|
||||||
|
hierarchy, err := JSONPointerToSlice(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return g.Set(value, hierarchy...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Object creates a new JSON object at a target path. Returns an error if the
|
||||||
|
// path contains a collision with a non object type.
|
||||||
|
func (g *Container) Object(hierarchy ...string) (*Container, error) {
|
||||||
|
return g.Set(map[string]interface{}{}, hierarchy...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObjectP creates a new JSON object at a target path using dot notation.
|
||||||
|
// Returns an error if the path contains a collision with a non object type.
|
||||||
|
func (g *Container) ObjectP(path string) (*Container, error) {
|
||||||
|
return g.Object(DotPathToSlice(path)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObjectI creates a new JSON object at an array index. Returns an error if the
|
||||||
|
// object is not an array or the index is out of bounds.
|
||||||
|
func (g *Container) ObjectI(index int) (*Container, error) {
|
||||||
|
return g.SetIndex(map[string]interface{}{}, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Array creates a new JSON array at a path. Returns an error if the path
|
||||||
|
// contains a collision with a non object type.
|
||||||
|
func (g *Container) Array(hierarchy ...string) (*Container, error) {
|
||||||
|
return g.Set([]interface{}{}, hierarchy...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayP creates a new JSON array at a path using dot notation. Returns an
|
||||||
|
// error if the path contains a collision with a non object type.
|
||||||
|
func (g *Container) ArrayP(path string) (*Container, error) {
|
||||||
|
return g.Array(DotPathToSlice(path)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayI creates a new JSON array within an array at an index. Returns an error
|
||||||
|
// if the element is not an array or the index is out of bounds.
|
||||||
|
func (g *Container) ArrayI(index int) (*Container, error) {
|
||||||
|
return g.SetIndex([]interface{}{}, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayOfSize creates a new JSON array of a particular size at a path. Returns
|
||||||
|
// an error if the path contains a collision with a non object type.
|
||||||
|
func (g *Container) ArrayOfSize(size int, hierarchy ...string) (*Container, error) {
|
||||||
|
a := make([]interface{}, size)
|
||||||
|
return g.Set(a, hierarchy...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayOfSizeP creates a new JSON array of a particular size at a path using
|
||||||
|
// dot notation. Returns an error if the path contains a collision with a non
|
||||||
|
// object type.
|
||||||
|
func (g *Container) ArrayOfSizeP(size int, path string) (*Container, error) {
|
||||||
|
return g.ArrayOfSize(size, DotPathToSlice(path)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayOfSizeI create a new JSON array of a particular size within an array at
|
||||||
|
// an index. Returns an error if the element is not an array or the index is out
|
||||||
|
// of bounds.
|
||||||
|
func (g *Container) ArrayOfSizeI(size, index int) (*Container, error) {
|
||||||
|
a := make([]interface{}, size)
|
||||||
|
return g.SetIndex(a, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete an element at a path, an error is returned if the element does not
|
||||||
|
// exist or is not an object. In order to remove an array element please use
|
||||||
|
// ArrayRemove.
|
||||||
|
func (g *Container) Delete(hierarchy ...string) error {
|
||||||
|
if g == nil || g.object == nil {
|
||||||
|
return ErrNotObj
|
||||||
|
}
|
||||||
|
if len(hierarchy) == 0 {
|
||||||
|
return ErrInvalidQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
object := g.object
|
||||||
|
target := hierarchy[len(hierarchy)-1]
|
||||||
|
if len(hierarchy) > 1 {
|
||||||
|
object = g.Search(hierarchy[:len(hierarchy)-1]...).Data()
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj, ok := object.(map[string]interface{}); ok {
|
||||||
|
if _, ok = obj[target]; !ok {
|
||||||
|
return ErrNotFound
|
||||||
|
}
|
||||||
|
delete(obj, target)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if array, ok := object.([]interface{}); ok {
|
||||||
|
if len(hierarchy) < 2 {
|
||||||
|
return errors.New("unable to delete array index at root of path")
|
||||||
|
}
|
||||||
|
index, err := strconv.Atoi(target)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse array index '%v': %v", target, err)
|
||||||
|
}
|
||||||
|
if index >= len(array) {
|
||||||
|
return ErrOutOfBounds
|
||||||
|
}
|
||||||
|
array = append(array[:index], array[index+1:]...)
|
||||||
|
g.Set(array, hierarchy[:len(hierarchy)-1]...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ErrNotObjOrArray
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteP deletes an element at a path using dot notation, an error is returned
|
||||||
|
// if the element does not exist.
|
||||||
|
func (g *Container) DeleteP(path string) error {
|
||||||
|
return g.Delete(DotPathToSlice(path)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MergeFn merges two objects using a provided function to resolve collisions.
|
||||||
|
//
|
||||||
|
// The collision function receives two interface{} arguments, destination (the
|
||||||
|
// original object) and source (the object being merged into the destination).
|
||||||
|
// Which ever value is returned becomes the new value in the destination object
|
||||||
|
// at the location of the collision.
|
||||||
|
func (g *Container) MergeFn(source *Container, collisionFn func(destination, source interface{}) interface{}) error {
|
||||||
|
var recursiveFnc func(map[string]interface{}, []string) error
|
||||||
|
recursiveFnc = func(mmap map[string]interface{}, path []string) error {
|
||||||
|
for key, value := range mmap {
|
||||||
|
newPath := append(path, key)
|
||||||
|
if g.Exists(newPath...) {
|
||||||
|
existingData := g.Search(newPath...).Data()
|
||||||
|
switch t := value.(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
switch existingVal := existingData.(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
if err := recursiveFnc(t, newPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if _, err := g.Set(collisionFn(existingVal, t), newPath...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if _, err := g.Set(collisionFn(existingData, t), newPath...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// path doesn't exist. So set the value
|
||||||
|
if _, err := g.Set(value, newPath...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if mmap, ok := source.Data().(map[string]interface{}); ok {
|
||||||
|
return recursiveFnc(mmap, []string{})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge a source object into an existing destination object. When a collision
|
||||||
|
// is found within the merged structures (both a source and destination object
|
||||||
|
// contain the same non-object keys) the result will be an array containing both
|
||||||
|
// values, where values that are already arrays will be expanded into the
|
||||||
|
// resulting array.
|
||||||
|
//
|
||||||
|
// It is possible to merge structures will different collision behaviours with
|
||||||
|
// MergeFn.
|
||||||
|
func (g *Container) Merge(source *Container) error {
|
||||||
|
return g.MergeFn(source, func(dest, source interface{}) interface{} {
|
||||||
|
destArr, destIsArray := dest.([]interface{})
|
||||||
|
sourceArr, sourceIsArray := source.([]interface{})
|
||||||
|
if destIsArray {
|
||||||
|
if sourceIsArray {
|
||||||
|
return append(destArr, sourceArr...)
|
||||||
|
}
|
||||||
|
return append(destArr, source)
|
||||||
|
}
|
||||||
|
if sourceIsArray {
|
||||||
|
return append(append([]interface{}{}, dest), sourceArr...)
|
||||||
|
}
|
||||||
|
return []interface{}{dest, source}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/*
|
||||||
|
Array modification/search - Keeping these options simple right now, no need for
|
||||||
|
anything more complicated since you can just cast to []interface{}, modify and
|
||||||
|
then reassign with Set.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ArrayAppend attempts to append a value onto a JSON array at a path. If the
|
||||||
|
// target is not a JSON array then it will be converted into one, with its
|
||||||
|
// original contents set to the first element of the array.
|
||||||
|
func (g *Container) ArrayAppend(value interface{}, hierarchy ...string) error {
|
||||||
|
if array, ok := g.Search(hierarchy...).Data().([]interface{}); ok {
|
||||||
|
array = append(array, value)
|
||||||
|
_, err := g.Set(array, hierarchy...)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
newArray := []interface{}{}
|
||||||
|
if d := g.Search(hierarchy...).Data(); d != nil {
|
||||||
|
newArray = append(newArray, d)
|
||||||
|
}
|
||||||
|
newArray = append(newArray, value)
|
||||||
|
|
||||||
|
_, err := g.Set(newArray, hierarchy...)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayAppendP attempts to append a value onto a JSON array at a path using dot
|
||||||
|
// notation. If the target is not a JSON array then it will be converted into
|
||||||
|
// one, with its original contents set to the first element of the array.
|
||||||
|
func (g *Container) ArrayAppendP(value interface{}, path string) error {
|
||||||
|
return g.ArrayAppend(value, DotPathToSlice(path)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayConcat attempts to append a value onto a JSON array at a path. If the
|
||||||
|
// target is not a JSON array then it will be converted into one, with its
|
||||||
|
// original contents set to the first element of the array.
|
||||||
|
//
|
||||||
|
// ArrayConcat differs from ArrayAppend in that it will expand a value type
|
||||||
|
// []interface{} during the append operation, resulting in concatenation of each
|
||||||
|
// element, rather than append as a single element of []interface{}.
|
||||||
|
func (g *Container) ArrayConcat(value interface{}, hierarchy ...string) error {
|
||||||
|
var array []interface{}
|
||||||
|
if d := g.Search(hierarchy...).Data(); d != nil {
|
||||||
|
if targetArray, ok := d.([]interface{}); !ok {
|
||||||
|
// If the data exists, and it is not a slice of interface,
|
||||||
|
// append it as the first element of our new array.
|
||||||
|
array = append(array, d)
|
||||||
|
} else {
|
||||||
|
// If the data exists, and it is a slice of interface,
|
||||||
|
// assign it to our variable.
|
||||||
|
array = targetArray
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := value.(type) {
|
||||||
|
case []interface{}:
|
||||||
|
// If we have been given a slice of interface, expand it when appending.
|
||||||
|
array = append(array, v...)
|
||||||
|
default:
|
||||||
|
array = append(array, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := g.Set(array, hierarchy...)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayConcatP attempts to append a value onto a JSON array at a path using dot
|
||||||
|
// notation. If the target is not a JSON array then it will be converted into one,
|
||||||
|
// with its original contents set to the first element of the array.
|
||||||
|
//
|
||||||
|
// ArrayConcatP differs from ArrayAppendP in that it will expand a value type
|
||||||
|
// []interface{} during the append operation, resulting in concatenation of each
|
||||||
|
// element, rather than append as a single element of []interface{}.
|
||||||
|
func (g *Container) ArrayConcatP(value interface{}, path string) error {
|
||||||
|
return g.ArrayConcat(value, DotPathToSlice(path)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayRemove attempts to remove an element identified by an index from a JSON
|
||||||
|
// array at a path.
|
||||||
|
func (g *Container) ArrayRemove(index int, hierarchy ...string) error {
|
||||||
|
if index < 0 {
|
||||||
|
return ErrOutOfBounds
|
||||||
|
}
|
||||||
|
array, ok := g.Search(hierarchy...).Data().([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return ErrNotArray
|
||||||
|
}
|
||||||
|
if index < len(array) {
|
||||||
|
array = append(array[:index], array[index+1:]...)
|
||||||
|
} else {
|
||||||
|
return ErrOutOfBounds
|
||||||
|
}
|
||||||
|
_, err := g.Set(array, hierarchy...)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayRemoveP attempts to remove an element identified by an index from a JSON
|
||||||
|
// array at a path using dot notation.
|
||||||
|
func (g *Container) ArrayRemoveP(index int, path string) error {
|
||||||
|
return g.ArrayRemove(index, DotPathToSlice(path)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayElement attempts to access an element by an index from a JSON array at a
|
||||||
|
// path.
|
||||||
|
func (g *Container) ArrayElement(index int, hierarchy ...string) (*Container, error) {
|
||||||
|
if index < 0 {
|
||||||
|
return nil, ErrOutOfBounds
|
||||||
|
}
|
||||||
|
array, ok := g.Search(hierarchy...).Data().([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrNotArray
|
||||||
|
}
|
||||||
|
if index < len(array) {
|
||||||
|
return &Container{array[index]}, nil
|
||||||
|
}
|
||||||
|
return nil, ErrOutOfBounds
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayElementP attempts to access an element by an index from a JSON array at
|
||||||
|
// a path using dot notation.
|
||||||
|
func (g *Container) ArrayElementP(index int, path string) (*Container, error) {
|
||||||
|
return g.ArrayElement(index, DotPathToSlice(path)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayCount counts the number of elements in a JSON array at a path.
|
||||||
|
func (g *Container) ArrayCount(hierarchy ...string) (int, error) {
|
||||||
|
if array, ok := g.Search(hierarchy...).Data().([]interface{}); ok {
|
||||||
|
return len(array), nil
|
||||||
|
}
|
||||||
|
return 0, ErrNotArray
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayCountP counts the number of elements in a JSON array at a path using dot
|
||||||
|
// notation.
|
||||||
|
func (g *Container) ArrayCountP(path string) (int, error) {
|
||||||
|
return g.ArrayCount(DotPathToSlice(path)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Bytes marshals an element to a JSON []byte blob.
|
||||||
|
func (g *Container) Bytes() []byte {
|
||||||
|
if bytes, err := json.Marshal(g.Data()); err == nil {
|
||||||
|
return bytes
|
||||||
|
}
|
||||||
|
return []byte("null")
|
||||||
|
}
|
||||||
|
|
||||||
|
// BytesIndent marshals an element to a JSON []byte blob formatted with a prefix
|
||||||
|
// and indent string.
|
||||||
|
func (g *Container) BytesIndent(prefix string, indent string) []byte {
|
||||||
|
if g.object != nil {
|
||||||
|
if bytes, err := json.MarshalIndent(g.Data(), prefix, indent); err == nil {
|
||||||
|
return bytes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return []byte("null")
|
||||||
|
}
|
||||||
|
|
||||||
|
// String marshals an element to a JSON formatted string.
|
||||||
|
func (g *Container) String() string {
|
||||||
|
return string(g.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringIndent marshals an element to a JSON string formatted with a prefix and
|
||||||
|
// indent string.
|
||||||
|
func (g *Container) StringIndent(prefix string, indent string) string {
|
||||||
|
return string(g.BytesIndent(prefix, indent))
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeOpt is a functional option for the EncodeJSON method.
|
||||||
|
type EncodeOpt func(e *json.Encoder)
|
||||||
|
|
||||||
|
// EncodeOptHTMLEscape sets the encoder to escape the JSON for html.
|
||||||
|
func EncodeOptHTMLEscape(doEscape bool) EncodeOpt {
|
||||||
|
return func(e *json.Encoder) {
|
||||||
|
e.SetEscapeHTML(doEscape)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeOptIndent sets the encoder to indent the JSON output.
|
||||||
|
func EncodeOptIndent(prefix string, indent string) EncodeOpt {
|
||||||
|
return func(e *json.Encoder) {
|
||||||
|
e.SetIndent(prefix, indent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeJSON marshals an element to a JSON formatted []byte using a variant
|
||||||
|
// list of modifier functions for the encoder being used. Functions for
|
||||||
|
// modifying the output are prefixed with EncodeOpt, e.g. EncodeOptHTMLEscape.
|
||||||
|
func (g *Container) EncodeJSON(encodeOpts ...EncodeOpt) []byte {
|
||||||
|
var b bytes.Buffer
|
||||||
|
encoder := json.NewEncoder(&b)
|
||||||
|
encoder.SetEscapeHTML(false) // Do not escape by default.
|
||||||
|
for _, opt := range encodeOpts {
|
||||||
|
opt(encoder)
|
||||||
|
}
|
||||||
|
if err := encoder.Encode(g.object); err != nil {
|
||||||
|
return []byte("null")
|
||||||
|
}
|
||||||
|
result := b.Bytes()
|
||||||
|
if len(result) > 0 {
|
||||||
|
result = result[:len(result)-1]
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new gabs JSON object.
|
||||||
|
func New() *Container {
|
||||||
|
return &Container{map[string]interface{}{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap an already unmarshalled JSON object (or a new map[string]interface{})
|
||||||
|
// into a *Container.
|
||||||
|
func Wrap(root interface{}) *Container {
|
||||||
|
return &Container{root}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseJSON unmarshals a JSON byte slice into a *Container.
|
||||||
|
func ParseJSON(sample []byte) (*Container, error) {
|
||||||
|
var gabs Container
|
||||||
|
|
||||||
|
if err := json.Unmarshal(sample, &gabs.object); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &gabs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseJSONDecoder applies a json.Decoder to a *Container.
|
||||||
|
func ParseJSONDecoder(decoder *json.Decoder) (*Container, error) {
|
||||||
|
var gabs Container
|
||||||
|
|
||||||
|
if err := decoder.Decode(&gabs.object); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &gabs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseJSONFile reads a file and unmarshals the contents into a *Container.
|
||||||
|
func ParseJSONFile(path string) (*Container, error) {
|
||||||
|
if len(path) > 0 {
|
||||||
|
cBytes, err := ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
container, err := ParseJSON(cBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return container, nil
|
||||||
|
}
|
||||||
|
return nil, ErrInvalidPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseJSONBuffer reads a buffer and unmarshals the contents into a *Container.
|
||||||
|
func ParseJSONBuffer(buffer io.Reader) (*Container, error) {
|
||||||
|
var gabs Container
|
||||||
|
jsonDecoder := json.NewDecoder(buffer)
|
||||||
|
if err := jsonDecoder.Decode(&gabs.object); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &gabs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON returns the JSON encoding of this container. This allows
|
||||||
|
// structs which contain Container instances to be marshaled using
|
||||||
|
// json.Marshal().
|
||||||
|
func (g *Container) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(g.Data())
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
12
go.mod
Normal file
12
go.mod
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
module gabs
|
||||||
|
|
||||||
|
go 1.19
|
||||||
|
|
||||||
|
require gitlab.lxinyu.cn/lxinyu/go/c v0.0.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gogf/gf v1.16.8 // indirect
|
||||||
|
golang.org/x/text v0.3.7 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
replace gitlab.lxinyu.cn/lxinyu/go/c => C:\Users\Administrator\go\src\gitlab.lxinyu.cn\lxinyu\go\c
|
||||||
55
go.sum
Normal file
55
go.sum
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28 h1:LdXxtjzvZYhhUaonAaAKArG3pyC67kGL3YY+6hGG8G4=
|
||||||
|
github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc=
|
||||||
|
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
|
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||||
|
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
|
github.com/gogf/gf v1.16.8 h1:iVXUB+QPQIYDMMjMdDb6ZINF8xf5bWy54XxAo600zMs=
|
||||||
|
github.com/gogf/gf v1.16.8/go.mod h1:8Q/kw05nlVRp+4vv7XASBsMe9L1tsVKiGoeP2AHnlkk=
|
||||||
|
github.com/gomodule/redigo v1.8.5 h1:nRAxCa+SVsyjSBrtZmG/cqb6VbTmuRzpg/PoTFlpumc=
|
||||||
|
github.com/gomodule/redigo v1.8.5/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
|
||||||
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||||
|
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/grokify/html-strip-tags-go v0.0.1 h1:0fThFwLbW7P/kOiTBs03FsJSV9RM2M/Q/MOnCQxKMo0=
|
||||||
|
github.com/grokify/html-strip-tags-go v0.0.1/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78=
|
||||||
|
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||||
|
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
|
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||||
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
|
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||||
|
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
|
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||||
|
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
go.opentelemetry.io/otel v1.0.0 h1:qTTn6x71GVBvoafHK/yaRUmFzI4LcONZD0/kXxl5PHI=
|
||||||
|
go.opentelemetry.io/otel v1.0.0/go.mod h1:AjRVh9A5/5DE7S+mZtTR6t8vpKKryam+0lREnfmS4cg=
|
||||||
|
go.opentelemetry.io/otel/trace v1.0.0 h1:TSBr8GTEtKevYMG/2d21M989r5WJYVimhTHBKVEZuh4=
|
||||||
|
go.opentelemetry.io/otel/trace v1.0.0/go.mod h1:PXTWqayeFUlJV1YDNhsJYB184+IvAH814St6o6ajzIs=
|
||||||
|
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 h1:ADo5wSpq2gqaCGQWzk7S5vd//0iyyLeAratkEoG5dLE=
|
||||||
|
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
Loading…
Reference in New Issue
Block a user