package main import ( "encoding/json" "fmt" "maps" "os" "slices" "strconv" "strings" ) func main() { if len(os.Args) != 3 { fmt.Fprintf(os.Stderr, "usage: %s \n", os.Args[0]) os.Exit(1) } jsonPath := os.Args[1] outPath := os.Args[2] jsonData, err := os.ReadFile(jsonPath) if err != nil { fmt.Fprintf(os.Stderr, "error reading file %s: %v\n", jsonPath, err) os.Exit(1) } // It would be nice to preserve the order of blocks ... except that we can't // because Nix already doesn't preserve the order of attribute sets. var config map[string]any if err := json.Unmarshal(jsonData, &config); err != nil { fmt.Fprintf(os.Stderr, "error parsing JSON: %v\n", err) os.Exit(1) } result := formatConfig(config) if err := os.WriteFile(outPath, []byte(result), 0644); err != nil { fmt.Fprintf(os.Stderr, "error writing file %s: %v\n", outPath, err) os.Exit(1) } } func formatConfig(config map[string]any) string { var s strings.Builder for _, blockName := range slices.Sorted(maps.Keys(config)) { labels := config[blockName] if labelsMap, ok := labels.(map[string]any); ok { for label, block := range labelsMap { if blockMap, ok := block.(map[string]any); ok { s.WriteString(formatBlock(blockName, label, blockMap, 0)) } } } } return s.String() } func formatBlock(blockName string, label string, block map[string]any, indent int) string { var s strings.Builder s.WriteString(strings.Repeat(" ", indent)) s.WriteString(blockName) if label != "" { s.WriteString(fmt.Sprintf(` %s`, strconv.Quote(label))) } s.WriteString(" {\n") var blocks []any if blocksValue, exists := block["blocks"]; exists { if blocksList, ok := blocksValue.([]any); ok { blocks = blocksList } delete(block, "blocks") } for _, key := range slices.Sorted(maps.Keys(block)) { s.WriteString(strings.Repeat(" ", indent+1)) s.WriteString(fmt.Sprintf("%s = %s\n", key, formatValue(block[key]))) } for _, blockItem := range blocks { if blockMap, ok := blockItem.(map[string]any); ok { var name string if nameValue, exists := blockMap["name"]; exists { if nameStr, ok := nameValue.(string); ok { name = nameStr } delete(blockMap, "name") } s.WriteString(formatBlock(name, "", blockMap, indent+1)) } } s.WriteString(strings.Repeat(" ", indent)) s.WriteString("}\n") return s.String() } func formatValue(value any) string { switch v := value.(type) { case string: return strconv.Quote(v) case map[string]any: if ref, exists := v["$ref"]; exists { if refStr, ok := ref.(string); ok { return refStr } } var parts []string for _, name := range slices.Sorted(maps.Keys(v)) { parts = append(parts, fmt.Sprintf("%s=%s,", name, formatValue(v[name]))) } return "{" + strings.Join(parts, " ") + "}" case []any: var parts []string for _, item := range v { parts = append(parts, formatValue(item)) } return "[" + strings.Join(parts, ", ") + "]" default: return fmt.Sprintf("%v", v) } }