summaryrefslogtreecommitdiffstats
path: root/pkg/jsonschema/generate_config_docs.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/jsonschema/generate_config_docs.go')
-rw-r--r--pkg/jsonschema/generate_config_docs.go269
1 files changed, 269 insertions, 0 deletions
diff --git a/pkg/jsonschema/generate_config_docs.go b/pkg/jsonschema/generate_config_docs.go
new file mode 100644
index 000000000..441cc55e7
--- /dev/null
+++ b/pkg/jsonschema/generate_config_docs.go
@@ -0,0 +1,269 @@
+package jsonschema
+
+import (
+ "bytes"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "os"
+ "strings"
+
+ "github.com/iancoleman/orderedmap"
+ "github.com/jesseduffield/lazycore/pkg/utils"
+ "github.com/samber/lo"
+
+ "gopkg.in/yaml.v3"
+)
+
+type Node struct {
+ Name string
+ Description string
+ Default any
+ Children []*Node
+}
+
+const (
+ IndentLevel = 2
+ DocumentationCommentStart = "<!-- START CONFIG YAML: AUTOMATICALLY GENERATED with `go generate ./..., DO NOT UPDATE MANUALLY -->\n"
+ DocumentationCommentEnd = "<!-- END CONFIG YAML -->"
+ DocumentationCommentStartLen = len(DocumentationCommentStart)
+)
+
+func insertBlankLines(buffer bytes.Buffer) bytes.Buffer {
+ lines := strings.Split(strings.TrimRight(buffer.String(), "\n"), "\n")
+
+ var newBuffer bytes.Buffer
+
+ previousIndent := -1
+ wasComment := false
+
+ for _, line := range lines {
+ trimmedLine := strings.TrimLeft(line, " ")
+ indent := len(line) - len(trimmedLine)
+ isComment := strings.HasPrefix(trimmedLine, "#")
+ if isComment && !wasComment && indent <= previousIndent {
+ newBuffer.WriteString("\n")
+ }
+ newBuffer.WriteString(line)
+ newBuffer.WriteString("\n")
+ previousIndent = indent
+ wasComment = isComment
+ }
+
+ return newBuffer
+}
+
+func prepareMarshalledConfig(buffer bytes.Buffer) []byte {
+ buffer = insertBlankLines(buffer)
+
+ // Remove all `---` lines
+ lines := strings.Split(strings.TrimRight(buffer.String(), "\n"), "\n")
+
+ var newBuffer bytes.Buffer
+
+ for _, line := range lines {
+ if strings.TrimSpace(line) != "---" {
+ newBuffer.WriteString(line)
+ newBuffer.WriteString("\n")
+ }
+ }
+
+ config := newBuffer.Bytes()
+
+ // Add markdown yaml block tag
+ config = append([]byte("```yaml\n"), config...)
+ config = append(config, []byte("```\n")...)
+
+ return config
+}
+
+func setComment(yamlNode *yaml.Node, description string) {
+ // Workaround for the way yaml formats the HeadComment if it contains
+ // blank lines: it renders these without a leading "#", but we want a
+ // leading "#" even on blank lines. However, yaml respects it if the
+ // HeadComment already contains a leading "#", so we prefix all lines
+ // (including blank ones) with "#".
+ yamlNode.HeadComment = strings.Join(
+ lo.Map(strings.Split(description, "\n"), func(s string, _ int) string {
+ if s == "" {
+ return "#" // avoid trailing space on blank lines
+ }
+ return "# " + s
+ }),
+ "\n")
+}
+
+func (n *Node) MarshalYAML() (interface{}, error) {
+ node := yaml.Node{
+ Kind: yaml.MappingNode,
+ }
+
+ keyNode := yaml.Node{
+ Kind: yaml.ScalarNode,
+ Value: n.Name,
+ }
+ if n.Description != "" {
+ 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 {
+ childrenNode := yaml.Node{
+ Kind: yaml.MappingNode,
+ }
+ for _, child := range n.Children {
+ childYaml, err := child.MarshalYAML()
+ if err != nil {
+ return nil, err
+ }
+
+ childKey := yaml.Node{
+ Kind: yaml.ScalarNode,
+ Value: child.Name,
+ }
+ if child.Description != "" {
+ setComment(&childKey, child.Description)
+ }
+ childYaml = childYaml.(*yaml.Node)
+ childrenNode.Content = append(childrenNode.Content, childYaml.(*yaml.Node).Content...)
+ }
+ node.Content = append(node.Content, &keyNode, &childrenNode)
+ }
+
+ 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)
+ if err != nil {
+ return fmt.Errorf("Error reading Config.md file %w", err)
+ }
+
+ startConfigSectionIndex := bytes.Index(markdown, []byte(DocumentationCommentStart))
+ if startConfigSectionIndex == -1 {
+ return errors.New("Default config starting comment not found")
+ }
+
+ endConfigSectionIndex := bytes.Index(markdown[startConfigSectionIndex+DocumentationCommentStartLen:], []byte(DocumentationCommentEnd))
+ if endConfigSectionIndex == -1 {
+ return errors.New("Default config closing comment not found")
+ }
+
+ endConfigSectionIndex = endConfigSectionIndex + startConfigSectionIndex + DocumentationCommentStartLen
+
+ newMarkdown := make([]byte, 0, len(markdown)-endConfigSectionIndex+startConfigSectionIndex+len(config))
+ newMarkdown = append(newMarkdown, markdown[:startConfigSectionIndex+DocumentationCommentStartLen]...)
+ newMarkdown = append(newMarkdown, config...)
+ newMarkdown = append(newMarkdown, markdown[endConfigSectionIndex:]...)
+
+ if err := os.WriteFile(configPath, newMarkdown, 0o644); err != nil {
+ return fmt.Errorf("Error writing to file %w", err)
+ }
+ return nil
+}
+
+func GenerateConfigDocs() {
+ content, err := os.ReadFile(GetSchemaDir() + "/config.json")
+ if err != nil {
+ panic("Error reading config.json")
+ }
+
+ 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)
+ }
+
+ var buffer bytes.Buffer
+ encoder := yaml.NewEncoder(&buffer)
+ encoder.SetIndent(IndentLevel)
+
+ for _, child := range rootNode.Children {
+ err := encoder.Encode(child)
+ if err != nil {
+ panic("Failed to Marshal document")
+ }
+ }
+ encoder.Close()
+
+ config := prepareMarshalledConfig(buffer)
+
+ err = writeToConfigDocs(config)
+ if err != nil {
+ panic(err)
+ }
+}