Skip to content

Commit 95aaf2b

Browse files
committed
Use refs in jsonschema userconfig generator
1 parent 861e77d commit 95aaf2b

File tree

5 files changed

+1599
-1445
lines changed

5 files changed

+1599
-1445
lines changed

docs/Config.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,11 @@ os:
411411
# window is closed.
412412
editAtLineAndWait: ""
413413

414+
# Whether lazygit suspends until an edit process returns
415+
# Pointer to bool so that we can distinguish unset (nil) from false.
416+
# We're naming this `editInTerminal` for backwards compatibility
417+
editInTerminal: false
418+
414419
# For opening a directory in an editor
415420
openDirInEditor: ""
416421

pkg/jsonschema/generate.go

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,41 +7,73 @@ import (
77
"fmt"
88
"os"
99
"reflect"
10+
"strings"
1011

1112
"github.com/jesseduffield/lazycore/pkg/utils"
1213
"github.com/jesseduffield/lazygit/pkg/config"
1314
"github.com/karimkhaleel/jsonschema"
15+
"github.com/samber/lo"
1416
)
1517

1618
func GetSchemaDir() string {
1719
return utils.GetLazyRootDirectory() + "/schema"
1820
}
1921

20-
func GenerateSchema() {
22+
func GenerateSchema() *jsonschema.Schema {
2123
schema := customReflect(&config.UserConfig{})
2224
obj, _ := json.MarshalIndent(schema, "", " ")
2325
obj = append(obj, '\n')
2426

2527
if err := os.WriteFile(GetSchemaDir()+"/config.json", obj, 0o644); err != nil {
2628
fmt.Println("Error writing to file:", err)
27-
return
29+
return nil
2830
}
31+
return schema
32+
}
33+
34+
func getSubSchema(rootSchema, parentSchema *jsonschema.Schema, key string) *jsonschema.Schema {
35+
subSchema, found := parentSchema.Properties.Get(key)
36+
if !found {
37+
panic(fmt.Sprintf("Failed to find subSchema at %s on parent", key))
38+
}
39+
40+
// This means the schema is defined on the rootSchema's Definitions
41+
if subSchema.Ref != "" {
42+
key, _ = strings.CutPrefix(subSchema.Ref, "#/$defs/")
43+
refSchema := rootSchema.Definitions[key]
44+
refSchema.Description = subSchema.Description
45+
return refSchema
46+
}
47+
48+
return subSchema
2949
}
3050

3151
func customReflect(v *config.UserConfig) *jsonschema.Schema {
32-
defaultConfig := config.GetDefaultConfig()
33-
r := &jsonschema.Reflector{FieldNameTag: "yaml", RequiredFromJSONSchemaTags: true, DoNotReference: true}
52+
r := &jsonschema.Reflector{FieldNameTag: "yaml", RequiredFromJSONSchemaTags: true}
3453
if err := r.AddGoComments("github.com/jesseduffield/lazygit/pkg/config", "../config"); err != nil {
3554
panic(err)
3655
}
3756
schema := r.Reflect(v)
57+
defaultConfig := config.GetDefaultConfig()
58+
userConfigSchema := schema.Definitions["UserConfig"]
59+
60+
defaultValue := reflect.ValueOf(defaultConfig).Elem()
61+
62+
yamlToFieldNames := lo.Invert(userConfigSchema.OriginalPropertiesMapping)
63+
64+
for pair := userConfigSchema.Properties.Oldest(); pair != nil; pair = pair.Next() {
65+
yamlName := pair.Key
66+
fieldName := yamlToFieldNames[yamlName]
3867

39-
setDefaultVals(defaultConfig, schema)
68+
subSchema := getSubSchema(schema, userConfigSchema, yamlName)
69+
70+
setDefaultVals(schema, subSchema, defaultValue.FieldByName(fieldName).Interface())
71+
}
4072

4173
return schema
4274
}
4375

44-
func setDefaultVals(defaults any, schema *jsonschema.Schema) {
76+
func setDefaultVals(rootSchema, schema *jsonschema.Schema, defaults any) {
4577
t := reflect.TypeOf(defaults)
4678
v := reflect.ValueOf(defaults)
4779

@@ -50,6 +82,24 @@ func setDefaultVals(defaults any, schema *jsonschema.Schema) {
5082
v = v.Elem()
5183
}
5284

85+
k := t.Kind()
86+
_ = k
87+
88+
switch t.Kind() {
89+
case reflect.Bool:
90+
schema.Default = v.Bool()
91+
case reflect.Int:
92+
schema.Default = v.Int()
93+
case reflect.String:
94+
schema.Default = v.String()
95+
default:
96+
// Do nothing
97+
}
98+
99+
if t.Kind() != reflect.Struct {
100+
return
101+
}
102+
53103
for i := 0; i < t.NumField(); i++ {
54104
value := v.Field(i).Interface()
55105
parentKey := t.Field(i).Name
@@ -59,13 +109,10 @@ func setDefaultVals(defaults any, schema *jsonschema.Schema) {
59109
continue
60110
}
61111

62-
subSchema, ok := schema.Properties.Get(key)
63-
if !ok {
64-
continue
65-
}
112+
subSchema := getSubSchema(rootSchema, schema, key)
66113

67114
if isStruct(value) {
68-
setDefaultVals(value, subSchema)
115+
setDefaultVals(rootSchema, subSchema, value)
69116
} else if !isZeroValue(value) {
70117
subSchema.Default = value
71118
}

pkg/jsonschema/generate_config_docs.go

Lines changed: 84 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@ package jsonschema
22

33
import (
44
"bytes"
5-
"encoding/json"
65
"errors"
76
"fmt"
87
"os"
98
"strings"
109

11-
"github.com/iancoleman/orderedmap"
1210
"github.com/jesseduffield/lazycore/pkg/utils"
11+
"github.com/karimkhaleel/jsonschema"
1312
"github.com/samber/lo"
1413

1514
"gopkg.in/yaml.v3"
@@ -106,16 +105,7 @@ func (n *Node) MarshalYAML() (interface{}, error) {
106105
setComment(&keyNode, n.Description)
107106
}
108107

109-
if n.Default != nil {
110-
valueNode := yaml.Node{
111-
Kind: yaml.ScalarNode,
112-
}
113-
err := valueNode.Encode(n.Default)
114-
if err != nil {
115-
return nil, err
116-
}
117-
node.Content = append(node.Content, &keyNode, &valueNode)
118-
} else if len(n.Children) > 0 {
108+
if len(n.Children) > 0 {
119109
childrenNode := yaml.Node{
120110
Kind: yaml.MappingNode,
121111
}
@@ -136,60 +126,18 @@ func (n *Node) MarshalYAML() (interface{}, error) {
136126
childrenNode.Content = append(childrenNode.Content, childYaml.(*yaml.Node).Content...)
137127
}
138128
node.Content = append(node.Content, &keyNode, &childrenNode)
139-
}
140-
141-
return &node, nil
142-
}
143-
144-
func getDescription(v *orderedmap.OrderedMap) string {
145-
description, ok := v.Get("description")
146-
if !ok {
147-
description = ""
148-
}
149-
return description.(string)
150-
}
151-
152-
func getDefault(v *orderedmap.OrderedMap) (error, any) {
153-
defaultValue, ok := v.Get("default")
154-
if ok {
155-
return nil, defaultValue
156-
}
157-
158-
dataType, ok := v.Get("type")
159-
if ok {
160-
dataTypeString := dataType.(string)
161-
if dataTypeString == "string" {
162-
return nil, ""
129+
} else {
130+
valueNode := yaml.Node{
131+
Kind: yaml.ScalarNode,
163132
}
133+
err := valueNode.Encode(n.Default)
134+
if err != nil {
135+
return nil, err
136+
}
137+
node.Content = append(node.Content, &keyNode, &valueNode)
164138
}
165139

166-
return errors.New("Failed to get default value"), nil
167-
}
168-
169-
func parseNode(parent *Node, name string, value *orderedmap.OrderedMap) {
170-
description := getDescription(value)
171-
err, defaultValue := getDefault(value)
172-
if err == nil {
173-
leaf := &Node{Name: name, Description: description, Default: defaultValue}
174-
parent.Children = append(parent.Children, leaf)
175-
}
176-
177-
properties, ok := value.Get("properties")
178-
if !ok {
179-
return
180-
}
181-
182-
orderedProperties := properties.(orderedmap.OrderedMap)
183-
184-
node := &Node{Name: name, Description: description}
185-
parent.Children = append(parent.Children, node)
186-
187-
keys := orderedProperties.Keys()
188-
for _, name := range keys {
189-
value, _ := orderedProperties.Get(name)
190-
typedValue := value.(orderedmap.OrderedMap)
191-
parseNode(node, name, &typedValue)
192-
}
140+
return &node, nil
193141
}
194142

195143
func writeToConfigDocs(config []byte) error {
@@ -222,30 +170,37 @@ func writeToConfigDocs(config []byte) error {
222170
return nil
223171
}
224172

225-
func GenerateConfigDocs() {
226-
content, err := os.ReadFile(GetSchemaDir() + "/config.json")
227-
if err != nil {
228-
panic("Error reading config.json")
173+
func GenerateConfigDocs(schema *jsonschema.Schema) {
174+
rootNode := &Node{
175+
Children: make([]*Node, 0),
229176
}
230177

231-
schema := orderedmap.New()
178+
userConfigSchema := schema.Definitions["UserConfig"]
179+
for pair := userConfigSchema.Properties.Oldest(); pair != nil; pair = pair.Next() {
180+
yamlName := pair.Key
181+
subSchema := getSubSchema(schema, userConfigSchema, yamlName)
232182

233-
err = json.Unmarshal(content, &schema)
234-
if err != nil {
235-
panic("Failed to unmarshal config.json")
236-
}
183+
if subSchema.Type == "array" {
184+
_ = subSchema
185+
}
237186

238-
root, ok := schema.Get("properties")
239-
if !ok {
240-
panic("properties key not found in schema")
241-
}
242-
orderedRoot := root.(orderedmap.OrderedMap)
187+
// Skip empty objects
188+
if subSchema.Type == "object" && subSchema.Properties == nil {
189+
continue
190+
}
191+
192+
// Skip empty arrays
193+
if isZeroValue(subSchema.Default) && subSchema.Type == "array" {
194+
continue
195+
}
243196

244-
rootNode := Node{}
245-
for _, name := range orderedRoot.Keys() {
246-
value, _ := orderedRoot.Get(name)
247-
typedValue := value.(orderedmap.OrderedMap)
248-
parseNode(&rootNode, name, &typedValue)
197+
node := Node{
198+
Name: pair.Key,
199+
Description: subSchema.Description,
200+
Default: getZeroValue(subSchema.Default, subSchema.Type),
201+
}
202+
recurseOverSchema(schema, subSchema, &node)
203+
rootNode.Children = append(rootNode.Children, &node)
249204
}
250205

251206
var buffer bytes.Buffer
@@ -262,8 +217,54 @@ func GenerateConfigDocs() {
262217

263218
config := prepareMarshalledConfig(buffer)
264219

265-
err = writeToConfigDocs(config)
220+
err := writeToConfigDocs(config)
266221
if err != nil {
267222
panic(err)
268223
}
269224
}
225+
226+
func recurseOverSchema(rootSchema, schema *jsonschema.Schema, parent *Node) {
227+
if schema == nil || schema.Properties == nil || schema.Properties.Len() == 0 {
228+
return
229+
}
230+
231+
for pair := schema.Properties.Oldest(); pair != nil; pair = pair.Next() {
232+
if pair.Key == "confirmOnQuit" {
233+
_ = pair
234+
}
235+
subSchema := getSubSchema(rootSchema, schema, pair.Key)
236+
237+
// Skip empty objects
238+
if subSchema.Type == "object" && subSchema.Properties == nil {
239+
continue
240+
}
241+
242+
// Skip empty arrays
243+
if isZeroValue(subSchema.Default) && subSchema.Type == "array" {
244+
continue
245+
}
246+
247+
node := Node{
248+
Name: pair.Key,
249+
Description: subSchema.Description,
250+
Default: getZeroValue(subSchema.Default, subSchema.Type),
251+
}
252+
parent.Children = append(parent.Children, &node)
253+
recurseOverSchema(rootSchema, subSchema, &node)
254+
}
255+
}
256+
257+
func getZeroValue(val any, t string) any {
258+
if !isZeroValue(val) {
259+
return val
260+
}
261+
262+
switch t {
263+
case "string":
264+
return ""
265+
case "boolean":
266+
return false
267+
default:
268+
return nil
269+
}
270+
}

pkg/jsonschema/generator.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ import (
1010

1111
func main() {
1212
fmt.Printf("Generating jsonschema in %s...\n", jsonschema.GetSchemaDir())
13-
jsonschema.GenerateSchema()
14-
jsonschema.GenerateConfigDocs()
13+
schema := jsonschema.GenerateSchema()
14+
jsonschema.GenerateConfigDocs(schema)
1515
}

0 commit comments

Comments
 (0)