summaryrefslogtreecommitdiff
path: root/nixos/shared/alloy-nix-config/alloy_nix_config.go
blob: 4b6eb63d053be2c162089101378eb8d9b6e10119 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package main

import (
	"encoding/json"
	"fmt"
	"maps"
	"os"
	"slices"
	"strconv"
	"strings"
)

func main() {
	if len(os.Args) != 3 {
		fmt.Fprintf(os.Stderr, "usage: %s <json_path> <out_path>\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)
	}
}