mirror of
				https://github.com/jesseduffield/lazygit.git
				synced 2025-10-30 23:57:43 +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:
		
				
					committed by
					
						 Stefan Haller
						Stefan Haller
					
				
			
			
				
	
			
			
			
						parent
						
							62c6ba7d57
						
					
				
				
					commit
					30e9bf8a75
				
			| @@ -411,6 +411,11 @@ os: | ||||
|   # window is closed. | ||||
|   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 | ||||
|   openDirInEditor: "" | ||||
|  | ||||
|   | ||||
							
								
								
									
										1
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								go.mod
									
									
									
									
									
								
							| @@ -11,7 +11,6 @@ require ( | ||||
| 	github.com/gdamore/tcell/v2 v2.8.1 | ||||
| 	github.com/go-errors/errors v1.5.1 | ||||
| 	github.com/gookit/color v1.4.2 | ||||
| 	github.com/iancoleman/orderedmap v0.3.0 | ||||
| 	github.com/imdario/mergo v0.3.11 | ||||
| 	github.com/integrii/flaggy v1.4.0 | ||||
| 	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/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= | ||||
| 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-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= | ||||
| github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= | ||||
|   | ||||
| @@ -7,41 +7,76 @@ import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/jesseduffield/lazycore/pkg/utils" | ||||
| 	"github.com/jesseduffield/lazygit/pkg/config" | ||||
| 	"github.com/karimkhaleel/jsonschema" | ||||
| 	"github.com/samber/lo" | ||||
| ) | ||||
|  | ||||
| func GetSchemaDir() string { | ||||
| 	return utils.GetLazyRootDirectory() + "/schema" | ||||
| } | ||||
|  | ||||
| func GenerateSchema() { | ||||
| func GenerateSchema() *jsonschema.Schema { | ||||
| 	schema := customReflect(&config.UserConfig{}) | ||||
| 	obj, _ := json.MarshalIndent(schema, "", "  ") | ||||
| 	obj = append(obj, '\n') | ||||
|  | ||||
| 	if err := os.WriteFile(GetSchemaDir()+"/config.json", obj, 0o644); err != nil { | ||||
| 		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 { | ||||
| 	defaultConfig := config.GetDefaultConfig() | ||||
| 	r := &jsonschema.Reflector{FieldNameTag: "yaml", RequiredFromJSONSchemaTags: true, DoNotReference: true} | ||||
| 	r := &jsonschema.Reflector{FieldNameTag: "yaml", RequiredFromJSONSchemaTags: true} | ||||
| 	if err := r.AddGoComments("github.com/jesseduffield/lazygit/pkg/config", "../config"); err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	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 | ||||
| } | ||||
|  | ||||
| func setDefaultVals(defaults any, schema *jsonschema.Schema) { | ||||
| func setDefaultVals(rootSchema, schema *jsonschema.Schema, defaults any) { | ||||
| 	t := reflect.TypeOf(defaults) | ||||
| 	v := reflect.ValueOf(defaults) | ||||
|  | ||||
| @@ -50,6 +85,24 @@ func setDefaultVals(defaults any, schema *jsonschema.Schema) { | ||||
| 		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++ { | ||||
| 		value := v.Field(i).Interface() | ||||
| 		parentKey := t.Field(i).Name | ||||
| @@ -59,13 +112,10 @@ func setDefaultVals(defaults any, schema *jsonschema.Schema) { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		subSchema, ok := schema.Properties.Get(key) | ||||
| 		if !ok { | ||||
| 			continue | ||||
| 		} | ||||
| 		subSchema := getSubSchema(rootSchema, schema, key) | ||||
|  | ||||
| 		if isStruct(value) { | ||||
| 			setDefaultVals(value, subSchema) | ||||
| 			setDefaultVals(rootSchema, subSchema, value) | ||||
| 		} else if !isZeroValue(value) { | ||||
| 			subSchema.Default = value | ||||
| 		} | ||||
|   | ||||
| @@ -2,14 +2,13 @@ package jsonschema | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/iancoleman/orderedmap" | ||||
| 	"github.com/jesseduffield/lazycore/pkg/utils" | ||||
| 	"github.com/karimkhaleel/jsonschema" | ||||
| 	"github.com/samber/lo" | ||||
|  | ||||
| 	"gopkg.in/yaml.v3" | ||||
| @@ -106,16 +105,7 @@ func (n *Node) MarshalYAML() (interface{}, error) { | ||||
| 		setComment(&keyNode, n.Description) | ||||
| 	} | ||||
|  | ||||
| 	if n.Default != nil { | ||||
| 		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 { | ||||
| 	if len(n.Children) > 0 { | ||||
| 		childrenNode := yaml.Node{ | ||||
| 			Kind: yaml.MappingNode, | ||||
| 		} | ||||
| @@ -136,62 +126,20 @@ func (n *Node) MarshalYAML() (interface{}, error) { | ||||
| 			childrenNode.Content = append(childrenNode.Content, childYaml.(*yaml.Node).Content...) | ||||
| 		} | ||||
| 		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 | ||||
| } | ||||
|  | ||||
| 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 { | ||||
| 	configPath := utils.GetLazyRootDirectory() + "/docs/Config.md" | ||||
| 	markdown, err := os.ReadFile(configPath) | ||||
| @@ -222,31 +170,12 @@ func writeToConfigDocs(config []byte) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func GenerateConfigDocs() { | ||||
| 	content, err := os.ReadFile(GetSchemaDir() + "/config.json") | ||||
| 	if err != nil { | ||||
| 		panic("Error reading config.json") | ||||
| func GenerateConfigDocs(schema *jsonschema.Schema) { | ||||
| 	rootNode := &Node{ | ||||
| 		Children: make([]*Node, 0), | ||||
| 	} | ||||
|  | ||||
| 	schema := orderedmap.New() | ||||
|  | ||||
| 	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) | ||||
| 	} | ||||
| 	recurseOverSchema(schema, schema.Definitions["UserConfig"], rootNode) | ||||
|  | ||||
| 	var buffer bytes.Buffer | ||||
| 	encoder := yaml.NewEncoder(&buffer) | ||||
| @@ -262,8 +191,51 @@ func GenerateConfigDocs() { | ||||
|  | ||||
| 	config := prepareMarshalledConfig(buffer) | ||||
|  | ||||
| 	err = writeToConfigDocs(config) | ||||
| 	err := writeToConfigDocs(config) | ||||
| 	if err != nil { | ||||
| 		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() { | ||||
| 	fmt.Printf("Generating jsonschema in %s...\n", jsonschema.GetSchemaDir()) | ||||
| 	jsonschema.GenerateSchema() | ||||
| 	jsonschema.GenerateConfigDocs() | ||||
| 	schema := jsonschema.GenerateSchema() | ||||
| 	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 | ||||
| ## explicit; go 1.12 | ||||
| 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 | ||||
| ## explicit; go 1.13 | ||||
| github.com/imdario/mergo | ||||
|   | ||||
		Reference in New Issue
	
	Block a user