mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-05-11 20:36:03 +02:00
Use refs in jsonschema userconfig generator
This makes it possible to use recursive structures in the user config.
This commit is contained in:
parent
62c6ba7d57
commit
30e9bf8a75
11 changed files with 1542 additions and 1795 deletions
|
@ -411,6 +411,11 @@ os:
|
||||||
# window is closed.
|
# window is closed.
|
||||||
editAtLineAndWait: ""
|
editAtLineAndWait: ""
|
||||||
|
|
||||||
|
# Whether lazygit suspends until an edit process returns
|
||||||
|
# Pointer to bool so that we can distinguish unset (nil) from false.
|
||||||
|
# We're naming this `editInTerminal` for backwards compatibility
|
||||||
|
editInTerminal: false
|
||||||
|
|
||||||
# For opening a directory in an editor
|
# For opening a directory in an editor
|
||||||
openDirInEditor: ""
|
openDirInEditor: ""
|
||||||
|
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -11,7 +11,6 @@ require (
|
||||||
github.com/gdamore/tcell/v2 v2.8.1
|
github.com/gdamore/tcell/v2 v2.8.1
|
||||||
github.com/go-errors/errors v1.5.1
|
github.com/go-errors/errors v1.5.1
|
||||||
github.com/gookit/color v1.4.2
|
github.com/gookit/color v1.4.2
|
||||||
github.com/iancoleman/orderedmap v0.3.0
|
|
||||||
github.com/imdario/mergo v0.3.11
|
github.com/imdario/mergo v0.3.11
|
||||||
github.com/integrii/flaggy v1.4.0
|
github.com/integrii/flaggy v1.4.0
|
||||||
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68
|
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -171,8 +171,6 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc=
|
|
||||||
github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE=
|
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||||
|
|
|
@ -7,41 +7,76 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/jesseduffield/lazycore/pkg/utils"
|
"github.com/jesseduffield/lazycore/pkg/utils"
|
||||||
"github.com/jesseduffield/lazygit/pkg/config"
|
"github.com/jesseduffield/lazygit/pkg/config"
|
||||||
"github.com/karimkhaleel/jsonschema"
|
"github.com/karimkhaleel/jsonschema"
|
||||||
|
"github.com/samber/lo"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetSchemaDir() string {
|
func GetSchemaDir() string {
|
||||||
return utils.GetLazyRootDirectory() + "/schema"
|
return utils.GetLazyRootDirectory() + "/schema"
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateSchema() {
|
func GenerateSchema() *jsonschema.Schema {
|
||||||
schema := customReflect(&config.UserConfig{})
|
schema := customReflect(&config.UserConfig{})
|
||||||
obj, _ := json.MarshalIndent(schema, "", " ")
|
obj, _ := json.MarshalIndent(schema, "", " ")
|
||||||
obj = append(obj, '\n')
|
obj = append(obj, '\n')
|
||||||
|
|
||||||
if err := os.WriteFile(GetSchemaDir()+"/config.json", obj, 0o644); err != nil {
|
if err := os.WriteFile(GetSchemaDir()+"/config.json", obj, 0o644); err != nil {
|
||||||
fmt.Println("Error writing to file:", err)
|
fmt.Println("Error writing to file:", err)
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSubSchema(rootSchema, parentSchema *jsonschema.Schema, key string) *jsonschema.Schema {
|
||||||
|
subSchema, found := parentSchema.Properties.Get(key)
|
||||||
|
if !found {
|
||||||
|
panic(fmt.Sprintf("Failed to find subSchema at %s on parent", key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// This means the schema is defined on the rootSchema's Definitions
|
||||||
|
if subSchema.Ref != "" {
|
||||||
|
key, _ = strings.CutPrefix(subSchema.Ref, "#/$defs/")
|
||||||
|
refSchema, ok := rootSchema.Definitions[key]
|
||||||
|
if !ok {
|
||||||
|
panic(fmt.Sprintf("Failed to find #/$defs/%s", key))
|
||||||
|
}
|
||||||
|
refSchema.Description = subSchema.Description
|
||||||
|
return refSchema
|
||||||
|
}
|
||||||
|
|
||||||
|
return subSchema
|
||||||
}
|
}
|
||||||
|
|
||||||
func customReflect(v *config.UserConfig) *jsonschema.Schema {
|
func customReflect(v *config.UserConfig) *jsonschema.Schema {
|
||||||
defaultConfig := config.GetDefaultConfig()
|
r := &jsonschema.Reflector{FieldNameTag: "yaml", RequiredFromJSONSchemaTags: true}
|
||||||
r := &jsonschema.Reflector{FieldNameTag: "yaml", RequiredFromJSONSchemaTags: true, DoNotReference: true}
|
|
||||||
if err := r.AddGoComments("github.com/jesseduffield/lazygit/pkg/config", "../config"); err != nil {
|
if err := r.AddGoComments("github.com/jesseduffield/lazygit/pkg/config", "../config"); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
schema := r.Reflect(v)
|
schema := r.Reflect(v)
|
||||||
|
defaultConfig := config.GetDefaultConfig()
|
||||||
|
userConfigSchema := schema.Definitions["UserConfig"]
|
||||||
|
|
||||||
setDefaultVals(defaultConfig, schema)
|
defaultValue := reflect.ValueOf(defaultConfig).Elem()
|
||||||
|
|
||||||
|
yamlToFieldNames := lo.Invert(userConfigSchema.OriginalPropertiesMapping)
|
||||||
|
|
||||||
|
for pair := userConfigSchema.Properties.Oldest(); pair != nil; pair = pair.Next() {
|
||||||
|
yamlName := pair.Key
|
||||||
|
fieldName := yamlToFieldNames[yamlName]
|
||||||
|
|
||||||
|
subSchema := getSubSchema(schema, userConfigSchema, yamlName)
|
||||||
|
|
||||||
|
setDefaultVals(schema, subSchema, defaultValue.FieldByName(fieldName).Interface())
|
||||||
|
}
|
||||||
|
|
||||||
return schema
|
return schema
|
||||||
}
|
}
|
||||||
|
|
||||||
func setDefaultVals(defaults any, schema *jsonschema.Schema) {
|
func setDefaultVals(rootSchema, schema *jsonschema.Schema, defaults any) {
|
||||||
t := reflect.TypeOf(defaults)
|
t := reflect.TypeOf(defaults)
|
||||||
v := reflect.ValueOf(defaults)
|
v := reflect.ValueOf(defaults)
|
||||||
|
|
||||||
|
@ -50,6 +85,24 @@ func setDefaultVals(defaults any, schema *jsonschema.Schema) {
|
||||||
v = v.Elem()
|
v = v.Elem()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
k := t.Kind()
|
||||||
|
_ = k
|
||||||
|
|
||||||
|
switch t.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
schema.Default = v.Bool()
|
||||||
|
case reflect.Int:
|
||||||
|
schema.Default = v.Int()
|
||||||
|
case reflect.String:
|
||||||
|
schema.Default = v.String()
|
||||||
|
default:
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Kind() != reflect.Struct {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
for i := 0; i < t.NumField(); i++ {
|
for i := 0; i < t.NumField(); i++ {
|
||||||
value := v.Field(i).Interface()
|
value := v.Field(i).Interface()
|
||||||
parentKey := t.Field(i).Name
|
parentKey := t.Field(i).Name
|
||||||
|
@ -59,13 +112,10 @@ func setDefaultVals(defaults any, schema *jsonschema.Schema) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
subSchema, ok := schema.Properties.Get(key)
|
subSchema := getSubSchema(rootSchema, schema, key)
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if isStruct(value) {
|
if isStruct(value) {
|
||||||
setDefaultVals(value, subSchema)
|
setDefaultVals(rootSchema, subSchema, value)
|
||||||
} else if !isZeroValue(value) {
|
} else if !isZeroValue(value) {
|
||||||
subSchema.Default = value
|
subSchema.Default = value
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,13 @@ package jsonschema
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/iancoleman/orderedmap"
|
|
||||||
"github.com/jesseduffield/lazycore/pkg/utils"
|
"github.com/jesseduffield/lazycore/pkg/utils"
|
||||||
|
"github.com/karimkhaleel/jsonschema"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
@ -106,16 +105,7 @@ func (n *Node) MarshalYAML() (interface{}, error) {
|
||||||
setComment(&keyNode, n.Description)
|
setComment(&keyNode, n.Description)
|
||||||
}
|
}
|
||||||
|
|
||||||
if n.Default != nil {
|
if len(n.Children) > 0 {
|
||||||
valueNode := yaml.Node{
|
|
||||||
Kind: yaml.ScalarNode,
|
|
||||||
}
|
|
||||||
err := valueNode.Encode(n.Default)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
node.Content = append(node.Content, &keyNode, &valueNode)
|
|
||||||
} else if len(n.Children) > 0 {
|
|
||||||
childrenNode := yaml.Node{
|
childrenNode := yaml.Node{
|
||||||
Kind: yaml.MappingNode,
|
Kind: yaml.MappingNode,
|
||||||
}
|
}
|
||||||
|
@ -136,62 +126,20 @@ func (n *Node) MarshalYAML() (interface{}, error) {
|
||||||
childrenNode.Content = append(childrenNode.Content, childYaml.(*yaml.Node).Content...)
|
childrenNode.Content = append(childrenNode.Content, childYaml.(*yaml.Node).Content...)
|
||||||
}
|
}
|
||||||
node.Content = append(node.Content, &keyNode, &childrenNode)
|
node.Content = append(node.Content, &keyNode, &childrenNode)
|
||||||
|
} else {
|
||||||
|
valueNode := yaml.Node{
|
||||||
|
Kind: yaml.ScalarNode,
|
||||||
|
}
|
||||||
|
err := valueNode.Encode(n.Default)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
node.Content = append(node.Content, &keyNode, &valueNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &node, nil
|
return &node, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDescription(v *orderedmap.OrderedMap) string {
|
|
||||||
description, ok := v.Get("description")
|
|
||||||
if !ok {
|
|
||||||
description = ""
|
|
||||||
}
|
|
||||||
return description.(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDefault(v *orderedmap.OrderedMap) (error, any) {
|
|
||||||
defaultValue, ok := v.Get("default")
|
|
||||||
if ok {
|
|
||||||
return nil, defaultValue
|
|
||||||
}
|
|
||||||
|
|
||||||
dataType, ok := v.Get("type")
|
|
||||||
if ok {
|
|
||||||
dataTypeString := dataType.(string)
|
|
||||||
if dataTypeString == "string" {
|
|
||||||
return nil, ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors.New("Failed to get default value"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseNode(parent *Node, name string, value *orderedmap.OrderedMap) {
|
|
||||||
description := getDescription(value)
|
|
||||||
err, defaultValue := getDefault(value)
|
|
||||||
if err == nil {
|
|
||||||
leaf := &Node{Name: name, Description: description, Default: defaultValue}
|
|
||||||
parent.Children = append(parent.Children, leaf)
|
|
||||||
}
|
|
||||||
|
|
||||||
properties, ok := value.Get("properties")
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
orderedProperties := properties.(orderedmap.OrderedMap)
|
|
||||||
|
|
||||||
node := &Node{Name: name, Description: description}
|
|
||||||
parent.Children = append(parent.Children, node)
|
|
||||||
|
|
||||||
keys := orderedProperties.Keys()
|
|
||||||
for _, name := range keys {
|
|
||||||
value, _ := orderedProperties.Get(name)
|
|
||||||
typedValue := value.(orderedmap.OrderedMap)
|
|
||||||
parseNode(node, name, &typedValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeToConfigDocs(config []byte) error {
|
func writeToConfigDocs(config []byte) error {
|
||||||
configPath := utils.GetLazyRootDirectory() + "/docs/Config.md"
|
configPath := utils.GetLazyRootDirectory() + "/docs/Config.md"
|
||||||
markdown, err := os.ReadFile(configPath)
|
markdown, err := os.ReadFile(configPath)
|
||||||
|
@ -222,31 +170,12 @@ func writeToConfigDocs(config []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateConfigDocs() {
|
func GenerateConfigDocs(schema *jsonschema.Schema) {
|
||||||
content, err := os.ReadFile(GetSchemaDir() + "/config.json")
|
rootNode := &Node{
|
||||||
if err != nil {
|
Children: make([]*Node, 0),
|
||||||
panic("Error reading config.json")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
schema := orderedmap.New()
|
recurseOverSchema(schema, schema.Definitions["UserConfig"], rootNode)
|
||||||
|
|
||||||
err = json.Unmarshal(content, &schema)
|
|
||||||
if err != nil {
|
|
||||||
panic("Failed to unmarshal config.json")
|
|
||||||
}
|
|
||||||
|
|
||||||
root, ok := schema.Get("properties")
|
|
||||||
if !ok {
|
|
||||||
panic("properties key not found in schema")
|
|
||||||
}
|
|
||||||
orderedRoot := root.(orderedmap.OrderedMap)
|
|
||||||
|
|
||||||
rootNode := Node{}
|
|
||||||
for _, name := range orderedRoot.Keys() {
|
|
||||||
value, _ := orderedRoot.Get(name)
|
|
||||||
typedValue := value.(orderedmap.OrderedMap)
|
|
||||||
parseNode(&rootNode, name, &typedValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
encoder := yaml.NewEncoder(&buffer)
|
encoder := yaml.NewEncoder(&buffer)
|
||||||
|
@ -262,8 +191,51 @@ func GenerateConfigDocs() {
|
||||||
|
|
||||||
config := prepareMarshalledConfig(buffer)
|
config := prepareMarshalledConfig(buffer)
|
||||||
|
|
||||||
err = writeToConfigDocs(config)
|
err := writeToConfigDocs(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func recurseOverSchema(rootSchema, schema *jsonschema.Schema, parent *Node) {
|
||||||
|
if schema == nil || schema.Properties == nil || schema.Properties.Len() == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for pair := schema.Properties.Oldest(); pair != nil; pair = pair.Next() {
|
||||||
|
subSchema := getSubSchema(rootSchema, schema, pair.Key)
|
||||||
|
|
||||||
|
// Skip empty objects
|
||||||
|
if subSchema.Type == "object" && subSchema.Properties == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip empty arrays
|
||||||
|
if isZeroValue(subSchema.Default) && subSchema.Type == "array" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
node := Node{
|
||||||
|
Name: pair.Key,
|
||||||
|
Description: subSchema.Description,
|
||||||
|
Default: getZeroValue(subSchema.Default, subSchema.Type),
|
||||||
|
}
|
||||||
|
parent.Children = append(parent.Children, &node)
|
||||||
|
recurseOverSchema(rootSchema, subSchema, &node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getZeroValue(val any, t string) any {
|
||||||
|
if !isZeroValue(val) {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t {
|
||||||
|
case "string":
|
||||||
|
return ""
|
||||||
|
case "boolean":
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,6 @@ import (
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
fmt.Printf("Generating jsonschema in %s...\n", jsonschema.GetSchemaDir())
|
fmt.Printf("Generating jsonschema in %s...\n", jsonschema.GetSchemaDir())
|
||||||
jsonschema.GenerateSchema()
|
schema := jsonschema.GenerateSchema()
|
||||||
jsonschema.GenerateConfigDocs()
|
jsonschema.GenerateConfigDocs(schema)
|
||||||
}
|
}
|
||||||
|
|
2736
schema/config.json
2736
schema/config.json
File diff suppressed because it is too large
Load diff
21
vendor/github.com/iancoleman/orderedmap/LICENSE
generated
vendored
21
vendor/github.com/iancoleman/orderedmap/LICENSE
generated
vendored
|
@ -1,21 +0,0 @@
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2017 Ian Coleman
|
|
||||||
|
|
||||||
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.
|
|
266
vendor/github.com/iancoleman/orderedmap/orderedmap.go
generated
vendored
266
vendor/github.com/iancoleman/orderedmap/orderedmap.go
generated
vendored
|
@ -1,266 +0,0 @@
|
||||||
package orderedmap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"sort"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Pair struct {
|
|
||||||
key string
|
|
||||||
value interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (kv *Pair) Key() string {
|
|
||||||
return kv.key
|
|
||||||
}
|
|
||||||
|
|
||||||
func (kv *Pair) Value() interface{} {
|
|
||||||
return kv.value
|
|
||||||
}
|
|
||||||
|
|
||||||
type ByPair struct {
|
|
||||||
Pairs []*Pair
|
|
||||||
LessFunc func(a *Pair, j *Pair) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a ByPair) Len() int { return len(a.Pairs) }
|
|
||||||
func (a ByPair) Swap(i, j int) { a.Pairs[i], a.Pairs[j] = a.Pairs[j], a.Pairs[i] }
|
|
||||||
func (a ByPair) Less(i, j int) bool { return a.LessFunc(a.Pairs[i], a.Pairs[j]) }
|
|
||||||
|
|
||||||
type OrderedMap struct {
|
|
||||||
keys []string
|
|
||||||
values map[string]interface{}
|
|
||||||
escapeHTML bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func New() *OrderedMap {
|
|
||||||
o := OrderedMap{}
|
|
||||||
o.keys = []string{}
|
|
||||||
o.values = map[string]interface{}{}
|
|
||||||
o.escapeHTML = true
|
|
||||||
return &o
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OrderedMap) SetEscapeHTML(on bool) {
|
|
||||||
o.escapeHTML = on
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OrderedMap) Get(key string) (interface{}, bool) {
|
|
||||||
val, exists := o.values[key]
|
|
||||||
return val, exists
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OrderedMap) Set(key string, value interface{}) {
|
|
||||||
_, exists := o.values[key]
|
|
||||||
if !exists {
|
|
||||||
o.keys = append(o.keys, key)
|
|
||||||
}
|
|
||||||
o.values[key] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OrderedMap) Delete(key string) {
|
|
||||||
// check key is in use
|
|
||||||
_, ok := o.values[key]
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// remove from keys
|
|
||||||
for i, k := range o.keys {
|
|
||||||
if k == key {
|
|
||||||
o.keys = append(o.keys[:i], o.keys[i+1:]...)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// remove from values
|
|
||||||
delete(o.values, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OrderedMap) Keys() []string {
|
|
||||||
return o.keys
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OrderedMap) Values() map[string]interface{} {
|
|
||||||
return o.values
|
|
||||||
}
|
|
||||||
|
|
||||||
// SortKeys Sort the map keys using your sort func
|
|
||||||
func (o *OrderedMap) SortKeys(sortFunc func(keys []string)) {
|
|
||||||
sortFunc(o.keys)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort Sort the map using your sort func
|
|
||||||
func (o *OrderedMap) Sort(lessFunc func(a *Pair, b *Pair) bool) {
|
|
||||||
pairs := make([]*Pair, len(o.keys))
|
|
||||||
for i, key := range o.keys {
|
|
||||||
pairs[i] = &Pair{key, o.values[key]}
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(ByPair{pairs, lessFunc})
|
|
||||||
|
|
||||||
for i, pair := range pairs {
|
|
||||||
o.keys[i] = pair.key
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OrderedMap) UnmarshalJSON(b []byte) error {
|
|
||||||
if o.values == nil {
|
|
||||||
o.values = map[string]interface{}{}
|
|
||||||
}
|
|
||||||
err := json.Unmarshal(b, &o.values)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dec := json.NewDecoder(bytes.NewReader(b))
|
|
||||||
if _, err = dec.Token(); err != nil { // skip '{'
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
o.keys = make([]string, 0, len(o.values))
|
|
||||||
return decodeOrderedMap(dec, o)
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeOrderedMap(dec *json.Decoder, o *OrderedMap) error {
|
|
||||||
hasKey := make(map[string]bool, len(o.values))
|
|
||||||
for {
|
|
||||||
token, err := dec.Token()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if delim, ok := token.(json.Delim); ok && delim == '}' {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
key := token.(string)
|
|
||||||
if hasKey[key] {
|
|
||||||
// duplicate key
|
|
||||||
for j, k := range o.keys {
|
|
||||||
if k == key {
|
|
||||||
copy(o.keys[j:], o.keys[j+1:])
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
o.keys[len(o.keys)-1] = key
|
|
||||||
} else {
|
|
||||||
hasKey[key] = true
|
|
||||||
o.keys = append(o.keys, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
token, err = dec.Token()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if delim, ok := token.(json.Delim); ok {
|
|
||||||
switch delim {
|
|
||||||
case '{':
|
|
||||||
if values, ok := o.values[key].(map[string]interface{}); ok {
|
|
||||||
newMap := OrderedMap{
|
|
||||||
keys: make([]string, 0, len(values)),
|
|
||||||
values: values,
|
|
||||||
escapeHTML: o.escapeHTML,
|
|
||||||
}
|
|
||||||
if err = decodeOrderedMap(dec, &newMap); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
o.values[key] = newMap
|
|
||||||
} else if oldMap, ok := o.values[key].(OrderedMap); ok {
|
|
||||||
newMap := OrderedMap{
|
|
||||||
keys: make([]string, 0, len(oldMap.values)),
|
|
||||||
values: oldMap.values,
|
|
||||||
escapeHTML: o.escapeHTML,
|
|
||||||
}
|
|
||||||
if err = decodeOrderedMap(dec, &newMap); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
o.values[key] = newMap
|
|
||||||
} else if err = decodeOrderedMap(dec, &OrderedMap{}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case '[':
|
|
||||||
if values, ok := o.values[key].([]interface{}); ok {
|
|
||||||
if err = decodeSlice(dec, values, o.escapeHTML); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else if err = decodeSlice(dec, []interface{}{}, o.escapeHTML); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeSlice(dec *json.Decoder, s []interface{}, escapeHTML bool) error {
|
|
||||||
for index := 0; ; index++ {
|
|
||||||
token, err := dec.Token()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if delim, ok := token.(json.Delim); ok {
|
|
||||||
switch delim {
|
|
||||||
case '{':
|
|
||||||
if index < len(s) {
|
|
||||||
if values, ok := s[index].(map[string]interface{}); ok {
|
|
||||||
newMap := OrderedMap{
|
|
||||||
keys: make([]string, 0, len(values)),
|
|
||||||
values: values,
|
|
||||||
escapeHTML: escapeHTML,
|
|
||||||
}
|
|
||||||
if err = decodeOrderedMap(dec, &newMap); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s[index] = newMap
|
|
||||||
} else if oldMap, ok := s[index].(OrderedMap); ok {
|
|
||||||
newMap := OrderedMap{
|
|
||||||
keys: make([]string, 0, len(oldMap.values)),
|
|
||||||
values: oldMap.values,
|
|
||||||
escapeHTML: escapeHTML,
|
|
||||||
}
|
|
||||||
if err = decodeOrderedMap(dec, &newMap); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s[index] = newMap
|
|
||||||
} else if err = decodeOrderedMap(dec, &OrderedMap{}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else if err = decodeOrderedMap(dec, &OrderedMap{}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case '[':
|
|
||||||
if index < len(s) {
|
|
||||||
if values, ok := s[index].([]interface{}); ok {
|
|
||||||
if err = decodeSlice(dec, values, escapeHTML); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else if err = decodeSlice(dec, []interface{}{}, escapeHTML); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else if err = decodeSlice(dec, []interface{}{}, escapeHTML); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case ']':
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o OrderedMap) MarshalJSON() ([]byte, error) {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
buf.WriteByte('{')
|
|
||||||
encoder := json.NewEncoder(&buf)
|
|
||||||
encoder.SetEscapeHTML(o.escapeHTML)
|
|
||||||
for i, k := range o.keys {
|
|
||||||
if i > 0 {
|
|
||||||
buf.WriteByte(',')
|
|
||||||
}
|
|
||||||
// add key
|
|
||||||
if err := encoder.Encode(k); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
buf.WriteByte(':')
|
|
||||||
// add value
|
|
||||||
if err := encoder.Encode(o.values[k]); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buf.WriteByte('}')
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
|
81
vendor/github.com/iancoleman/orderedmap/readme.md
generated
vendored
81
vendor/github.com/iancoleman/orderedmap/readme.md
generated
vendored
|
@ -1,81 +0,0 @@
|
||||||
# orderedmap
|
|
||||||
|
|
||||||
[](https://travis-ci.com/iancoleman/orderedmap)
|
|
||||||
|
|
||||||
A golang data type equivalent to python's collections.OrderedDict
|
|
||||||
|
|
||||||
Retains order of keys in maps
|
|
||||||
|
|
||||||
Can be JSON serialized / deserialized
|
|
||||||
|
|
||||||
# Usage
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"github.com/iancoleman/orderedmap"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
|
|
||||||
// use New() instead of o := map[string]interface{}{}
|
|
||||||
o := orderedmap.New()
|
|
||||||
|
|
||||||
// use SetEscapeHTML() to whether escape problematic HTML characters or not, defaults is true
|
|
||||||
o.SetEscapeHTML(false)
|
|
||||||
|
|
||||||
// use Set instead of o["a"] = 1
|
|
||||||
o.Set("a", 1)
|
|
||||||
|
|
||||||
// add some value with special characters
|
|
||||||
o.Set("b", "\\.<>[]{}_-")
|
|
||||||
|
|
||||||
// use Get instead of i, ok := o["a"]
|
|
||||||
val, ok := o.Get("a")
|
|
||||||
|
|
||||||
// use Keys instead of for k, v := range o
|
|
||||||
keys := o.Keys()
|
|
||||||
for _, k := range keys {
|
|
||||||
v, _ := o.Get(k)
|
|
||||||
}
|
|
||||||
|
|
||||||
// use o.Delete instead of delete(o, key)
|
|
||||||
o.Delete("a")
|
|
||||||
|
|
||||||
// serialize to a json string using encoding/json
|
|
||||||
bytes, err := json.Marshal(o)
|
|
||||||
prettyBytes, err := json.MarshalIndent(o, "", " ")
|
|
||||||
|
|
||||||
// deserialize a json string using encoding/json
|
|
||||||
// all maps (including nested maps) will be parsed as orderedmaps
|
|
||||||
s := `{"a": 1}`
|
|
||||||
err := json.Unmarshal([]byte(s), &o)
|
|
||||||
|
|
||||||
// sort the keys
|
|
||||||
o.SortKeys(sort.Strings)
|
|
||||||
|
|
||||||
// sort by Pair
|
|
||||||
o.Sort(func(a *orderedmap.Pair, b *orderedmap.Pair) bool {
|
|
||||||
return a.Value().(float64) < b.Value().(float64)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
# Caveats
|
|
||||||
|
|
||||||
* OrderedMap only takes strings for the key, as per [the JSON spec](http://json.org/).
|
|
||||||
|
|
||||||
# Tests
|
|
||||||
|
|
||||||
```
|
|
||||||
go test
|
|
||||||
```
|
|
||||||
|
|
||||||
# Alternatives
|
|
||||||
|
|
||||||
None of the alternatives offer JSON serialization.
|
|
||||||
|
|
||||||
* [cevaris/ordered_map](https://github.com/cevaris/ordered_map)
|
|
||||||
* [mantyr/iterator](https://github.com/mantyr/iterator)
|
|
3
vendor/modules.txt
vendored
3
vendor/modules.txt
vendored
|
@ -109,9 +109,6 @@ github.com/gobwas/glob/util/strings
|
||||||
# github.com/gookit/color v1.4.2
|
# github.com/gookit/color v1.4.2
|
||||||
## explicit; go 1.12
|
## explicit; go 1.12
|
||||||
github.com/gookit/color
|
github.com/gookit/color
|
||||||
# github.com/iancoleman/orderedmap v0.3.0
|
|
||||||
## explicit; go 1.16
|
|
||||||
github.com/iancoleman/orderedmap
|
|
||||||
# github.com/imdario/mergo v0.3.11
|
# github.com/imdario/mergo v0.3.11
|
||||||
## explicit; go 1.13
|
## explicit; go 1.13
|
||||||
github.com/imdario/mergo
|
github.com/imdario/mergo
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue