From 0d7315249d20bf9856605068074a7b6c6bcce835 Mon Sep 17 00:00:00 2001 From: Wim Date: Sun, 6 Dec 2020 23:16:02 +0100 Subject: Update vendor (#1330) --- vendor/github.com/Jeffail/gabs/gabs.go | 344 +++++++++++++++++++++++---------- 1 file changed, 245 insertions(+), 99 deletions(-) (limited to 'vendor/github.com/Jeffail/gabs/gabs.go') diff --git a/vendor/github.com/Jeffail/gabs/gabs.go b/vendor/github.com/Jeffail/gabs/gabs.go index a21a79d7..011c4c39 100644 --- a/vendor/github.com/Jeffail/gabs/gabs.go +++ b/vendor/github.com/Jeffail/gabs/gabs.go @@ -20,58 +20,85 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -// Package gabs implements a simplified wrapper around creating and parsing JSON. +// Package gabs implements a simplified wrapper around creating and parsing +// unknown or dynamic JSON. package gabs import ( "bytes" "encoding/json" "errors" + "fmt" "io" "io/ioutil" + "strconv" "strings" ) -//-------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------ var ( - // ErrOutOfBounds - Index out of bounds. + // ErrOutOfBounds indicates an index was out of bounds. ErrOutOfBounds = errors.New("out of bounds") - // ErrNotObjOrArray - The target is not an object or array type. + // 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 - The target is not an object type. + // ErrNotObj is returned when a target is not an object but needs to be for + // the intended operation. ErrNotObj = errors.New("not an object") - // ErrNotArray - The target is not an array type. + // 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 - Creating a path failed because an element collided with an existing value. + // 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 - The input value was not a map[string]interface{}. + // ErrInvalidInputObj is returned when the input value was not a + // map[string]interface{}. ErrInvalidInputObj = errors.New("invalid input object") - // ErrInvalidInputText - The input data could not be parsed. + // ErrInvalidInputText is returned when the input data could not be parsed. ErrInvalidInputText = errors.New("input text could not be parsed") - // ErrInvalidPath - The filepath was not valid. + // ErrInvalidPath is returned when the filepath was not valid. ErrInvalidPath = errors.New("invalid file path") - // ErrInvalidBuffer - The input buffer contained an invalid JSON string + // ErrInvalidBuffer is returned when the input buffer contained an invalid + // JSON string. ErrInvalidBuffer = errors.New("input buffer contained invalid JSON") ) -//-------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------ -// Container - an internal structure that holds a reference to the core interface map of the parsed -// json. Use this container to move context. +func resolveJSONPointerHierarchy(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 +} + +//------------------------------------------------------------------------------ + +// Container references a specific element within a JSON structure. type Container struct { object interface{} } -// Data - Return the contained data as an interface{}. +// Data returns the underlying interface{} of the target element in the JSON +// structure. func (g *Container) Data() interface{} { if g == nil { return nil @@ -79,17 +106,18 @@ func (g *Container) Data() interface{} { return g.object } -//-------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------ -// Path - Search for a value using dot notation. +// Path searches the JSON structure following a path in dot notation. func (g *Container) Path(path string) *Container { return g.Search(strings.Split(path, ".")...) } -// Search - Attempt to find and return an object within the JSON structure by specifying the -// hierarchy of field names to locate the target. If the search encounters an array and has not -// reached the end target then it will iterate each object of the array for the target and return -// all of the results in a JSON array. +// Search attempts to find and return an object within the JSON structure by +// following a provided hierarchy of field names to locate the target. If the +// search encounters an array and has not reached the end target then it will +// iterate each object of the array for the target and return all of the results +// in a JSON array. func (g *Container) Search(hierarchy ...string) *Container { var object interface{} @@ -120,22 +148,55 @@ func (g *Container) Search(hierarchy ...string) *Container { return &Container{object} } -// S - Shorthand method, does the same thing as Search. +// 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. +func (g *Container) JSONPointer(path string) (*Container, error) { + hierarchy, err := resolveJSONPointerHierarchy(path) + if err != nil { + return nil, err + } + + 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 JSON pointer: index '%v' value '%v' was not found", target, pathSeg) + } + } else if marray, ok := object.([]interface{}); ok { + index, err := strconv.Atoi(pathSeg) + if err != nil { + return nil, fmt.Errorf("failed to resolve JSON pointer: could not parse index '%v' value '%v' into array index: %v", target, pathSeg, err) + } + if len(marray) <= index { + return nil, fmt.Errorf("failed to resolve JSON pointer: index '%v' value '%v' exceeded target array size of '%v'", target, pathSeg, len(marray)) + } + object = marray[index] + } else { + return &Container{nil}, fmt.Errorf("failed to resolve JSON pointer: index '%v' field '%v' was not found", target, pathSeg) + } + } + return &Container{object}, nil +} + +// S is a shorthand alias for Search. func (g *Container) S(hierarchy ...string) *Container { return g.Search(hierarchy...) } -// Exists - Checks whether a path exists. +// Exists checks whether a path exists. func (g *Container) Exists(hierarchy ...string) bool { return g.Search(hierarchy...) != nil } -// ExistsP - Checks whether a dot notation path exists. +// ExistsP checks whether a dot notation path exists. func (g *Container) ExistsP(path string) bool { return g.Exists(strings.Split(path, ".")...) } -// Index - Attempt to find and return an object within a JSON array by index. +// 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) { @@ -146,9 +207,9 @@ func (g *Container) Index(index int) *Container { return &Container{nil} } -// Children - Return a slice of all the children of the array. This also works for objects, however, -// the children returned for an object will NOT be in order and you lose the names of the returned -// objects this way. +// 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. func (g *Container) Children() ([]*Container, error) { if array, ok := g.Data().([]interface{}); ok { children := make([]*Container, len(array)) @@ -167,7 +228,7 @@ func (g *Container) Children() ([]*Container, error) { return nil, ErrNotObjOrArray } -// ChildrenMap - Return a map of all the children of an object. +// ChildrenMap returns a map of all the children of an object element. func (g *Container) ChildrenMap() (map[string]*Container, error) { if mmap, ok := g.Data().(map[string]interface{}); ok { children := map[string]*Container{} @@ -179,11 +240,11 @@ func (g *Container) ChildrenMap() (map[string]*Container, error) { return nil, ErrNotObj } -//-------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------ -// Set - Set the value of a field at a JSON path, 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. +// Set the value of a field at a JSON path, 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) Set(value interface{}, path ...string) (*Container, error) { if len(path) == 0 { g.object = value @@ -209,12 +270,14 @@ func (g *Container) Set(value interface{}, path ...string) (*Container, error) { return &Container{object}, nil } -// SetP - Does the same as Set, but using a dot notation JSON path. +// SetP sets the value of a field at a JSON 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, strings.Split(path, ".")...) } -// SetIndex - Set a value of an array element based on the index. +// 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) { @@ -226,60 +289,112 @@ func (g *Container) SetIndex(value interface{}, index int) (*Container, error) { return &Container{nil}, ErrNotArray } -// Object - Create a new JSON object at a path. Returns an error if the path contains a collision -// with a non object type. +// 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) error { + hierarchy, err := resolveJSONPointerHierarchy(path) + if err != nil { + return err + } + + if len(hierarchy) == 0 { + g.object = value + return nil + } + + 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 { + return fmt.Errorf("failed to resolve JSON pointer: index '%v' value '%v' was not found", target, pathSeg) + } + } else if marray, ok := object.([]interface{}); ok { + index, err := strconv.Atoi(pathSeg) + if err != nil { + return fmt.Errorf("failed to resolve JSON pointer: could not parse index '%v' value '%v' into array index: %v", target, pathSeg, err) + } + if len(marray) <= index { + return fmt.Errorf("failed to resolve JSON pointer: index '%v' value '%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 fmt.Errorf("failed to resolve JSON pointer: index '%v' value '%v' was not found", target, pathSeg) + } + } else { + return fmt.Errorf("failed to resolve JSON pointer: index '%v' value '%v' was not found", target, pathSeg) + } + } + return nil +} + +// 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(path ...string) (*Container, error) { return g.Set(map[string]interface{}{}, path...) } -// ObjectP - Does the same as Object, but using a dot notation JSON path. +// 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(strings.Split(path, ".")...) } -// ObjectI - Create 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. +// 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 - Create a new JSON array at a path. Returns an error if the path contains a collision with -// a non object type. +// 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(path ...string) (*Container, error) { return g.Set([]interface{}{}, path...) } -// ArrayP - Does the same as Array, but using a dot notation JSON path. +// 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(strings.Split(path, ".")...) } -// ArrayI - Create a new JSON array at an array index. Returns an error if the object is not an -// array or the index is out of bounds. +// 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 - Create 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. +// 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, path ...string) (*Container, error) { a := make([]interface{}, size) return g.Set(a, path...) } -// ArrayOfSizeP - Does the same as ArrayOfSize, but using a dot notation JSON path. +// 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, strings.Split(path, ".")...) } -// ArrayOfSizeI - Create a new JSON array of a particular size at an array index. Returns an error -// if the object is not an array or the index is out of bounds. +// 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 - Delete an element at a JSON path, an error is returned if the element does not exist. +// Delete an element at a path, an error is returned if the element does not +// exist. func (g *Container) Delete(path ...string) error { var object interface{} @@ -304,45 +419,40 @@ func (g *Container) Delete(path ...string) error { return nil } -// DeleteP - Does the same as Delete, but using a dot notation JSON path. +// 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(strings.Split(path, ".")...) } -// Merge - Merges two gabs-containers -func (g *Container) Merge(toMerge *Container) error { +// 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...) { - target := g.Search(newPath...) + existingData := g.Search(newPath...).Data() switch t := value.(type) { case map[string]interface{}: - switch targetV := target.Data().(type) { + switch existingVal := existingData.(type) { case map[string]interface{}: if err := recursiveFnc(t, newPath); err != nil { return err } - case []interface{}: - g.Set(append(targetV, t), newPath...) default: - newSlice := append([]interface{}{}, targetV) - g.Set(append(newSlice, t), newPath...) - } - case []interface{}: - for _, valueOfSlice := range t { - if err := g.ArrayAppend(valueOfSlice, newPath...); err != nil { + if _, err := g.Set(collisionFn(existingVal, t), newPath...); err != nil { return err } } default: - switch targetV := target.Data().(type) { - case []interface{}: - g.Set(append(targetV, t), newPath...) - default: - newSlice := append([]interface{}{}, targetV) - g.Set(append(newSlice, t), newPath...) + if _, err := g.Set(collisionFn(existingData, t), newPath...); err != nil { + return err } } } else { @@ -354,21 +464,48 @@ func (g *Container) Merge(toMerge *Container) error { } return nil } - if mmap, ok := toMerge.Data().(map[string]interface{}); ok { + 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. +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 - Append a value onto a JSON array. If the target is not a JSON array then it will be -// converted into one, with its contents as the first element of the array. +// 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{}, path ...string) error { if array, ok := g.Search(path...).Data().([]interface{}); ok { array = append(array, value) @@ -386,12 +523,15 @@ func (g *Container) ArrayAppend(value interface{}, path ...string) error { return err } -// ArrayAppendP - Append a value onto a JSON array using a dot notation JSON path. +// 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, strings.Split(path, ".")...) } -// ArrayRemove - Remove an element from a JSON array. +// ArrayRemove attempts to remove an element identified by an index from a JSON +// array at a path. func (g *Container) ArrayRemove(index int, path ...string) error { if index < 0 { return ErrOutOfBounds @@ -409,12 +549,14 @@ func (g *Container) ArrayRemove(index int, path ...string) error { return err } -// ArrayRemoveP - Remove an element from a JSON array using a dot notation JSON path. +// 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, strings.Split(path, ".")...) } -// ArrayElement - Access an element from a JSON array. +// ArrayElement attempts to access an element by an index from a JSON array at a +// path. func (g *Container) ArrayElement(index int, path ...string) (*Container, error) { if index < 0 { return &Container{nil}, ErrOutOfBounds @@ -429,12 +571,13 @@ func (g *Container) ArrayElement(index int, path ...string) (*Container, error) return &Container{nil}, ErrOutOfBounds } -// ArrayElementP - Access an element from a JSON array using a dot notation JSON path. +// 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, strings.Split(path, ".")...) } -// ArrayCount - Count the number of elements in a JSON array. +// ArrayCount counts the number of elements in a JSON array at a path. func (g *Container) ArrayCount(path ...string) (int, error) { if array, ok := g.Search(path...).Data().([]interface{}); ok { return len(array), nil @@ -442,14 +585,15 @@ func (g *Container) ArrayCount(path ...string) (int, error) { return 0, ErrNotArray } -// ArrayCountP - Count the number of elements in a JSON array using a dot notation JSON path. +// 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(strings.Split(path, ".")...) } -//-------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------ -// Bytes - Converts the contained object back to a JSON []byte blob. +// Bytes marshals an element to a JSON []byte blob. func (g *Container) Bytes() []byte { if g.Data() != nil { if bytes, err := json.Marshal(g.object); err == nil { @@ -459,7 +603,8 @@ func (g *Container) Bytes() []byte { return []byte("{}") } -// BytesIndent - Converts the contained object to a JSON []byte blob formatted with prefix, indent. +// 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.object, prefix, indent); err == nil { @@ -469,12 +614,13 @@ func (g *Container) BytesIndent(prefix string, indent string) []byte { return []byte("{}") } -// String - Converts the contained object to a JSON formatted string. +// String marshals an element to a JSON formatted string. func (g *Container) String() string { return string(g.Bytes()) } -// StringIndent - Converts the contained object back to a JSON formatted string with prefix, indent. +// 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)) } @@ -496,10 +642,9 @@ func EncodeOptIndent(prefix string, indent string) EncodeOpt { } } -// EncodeJSON - Encodes the contained object back 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. +// 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) @@ -517,17 +662,18 @@ func (g *Container) EncodeJSON(encodeOpts ...EncodeOpt) []byte { return result } -// New - Create a new gabs JSON object. +// New creates a new gabs JSON object. func New() *Container { return &Container{map[string]interface{}{}} } -// Consume - Gobble up an already converted JSON object, or a fresh map[string]interface{} object. +// Consume an already unmarshalled JSON object (or a new map[string]interface{}) +// into a *Container. func Consume(root interface{}) (*Container, error) { return &Container{root}, nil } -// ParseJSON - Convert a string into a representation of the parsed JSON. +// ParseJSON unmarshals a JSON byte slice into a *Container. func ParseJSON(sample []byte) (*Container, error) { var gabs Container @@ -538,7 +684,7 @@ func ParseJSON(sample []byte) (*Container, error) { return &gabs, nil } -// ParseJSONDecoder - Convert a json.Decoder into a representation of the parsed JSON. +// ParseJSONDecoder applies a json.Decoder to a *Container. func ParseJSONDecoder(decoder *json.Decoder) (*Container, error) { var gabs Container @@ -549,7 +695,7 @@ func ParseJSONDecoder(decoder *json.Decoder) (*Container, error) { return &gabs, nil } -// ParseJSONFile - Read a file and convert into a representation of the parsed JSON. +// 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) @@ -567,7 +713,7 @@ func ParseJSONFile(path string) (*Container, error) { return nil, ErrInvalidPath } -// ParseJSONBuffer - Read the contents of a buffer into a representation of the parsed JSON. +// 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) @@ -578,4 +724,4 @@ func ParseJSONBuffer(buffer io.Reader) (*Container, error) { return &gabs, nil } -//-------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------ -- cgit v1.2.3