Skip to content

Commit adcdbb2

Browse files
Merge pull request #5 from marcelocarlos/support-using-subtrees
Add support for using JSON/YAML subtrees instead of the entire contents
2 parents 74c3d1a + dd49727 commit adcdbb2

File tree

3 files changed

+105
-69
lines changed

3 files changed

+105
-69
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ echo "v3: {{ .key2.first.key3 }}" | datasubst --yaml-data examples/basic-data.ya
4242
# Using stdin - env
4343
echo "{{ .TEST1 }} {{ .TEST2 }}" | TEST1="hello" TEST2="world" datasubst --env-data
4444

45+
# Specifying JSON subtrees to use (available for JSON and YAML)
46+
echo "{{ .first.key3 }}" | datasubst --json-data examples/basic-data.json --subtree .key2
47+
# Specifying YAML subtrees to use (available for JSON and YAML)
48+
datasubst --json-data examples/basic-data.json --subtree .key2 -i examples/basic-input-subtree.txt
49+
4550
# Using additional options, such -s (strict mode) and -d (change delimiters)
4651
echo "(( .TEST ))" | TEST="hi" datasubst --env-data -d '((:))' -s
4752
```

examples/basic-input-subtree.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
key3: {{ .first.key3 }}
2+
key3: {{ .second.key3 }}
3+
key3-dynamic: {{ (index . "first").key3 }}

main.go

Lines changed: 97 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ const usage = `Usage:
2121
Options:
2222
-j, --json-data DATA_INPUT Input data source in JSON format.
2323
-y, --yaml-data DATA_INPUT Input data source in YAML format.
24+
-t, --subtree JSON and YAML only, use a subtree of the data source instead of the full contents
2425
-e, --env-data Input data source comes from environment variables.
25-
-i, --input INPUT Input template file in go template format.
26+
-i, --input INPUT Input template file or directory containig template(s) in go template format.
2627
-o, --output OUTPUT Write the output to the file at OUTPUT.
2728
-s, --strict Strict mode (causes an error if a key is missing)
2829
-d, --delimiters Set the delimiters used in the templates in the format <left>:<right> (default: '{{:}}')
@@ -35,10 +36,95 @@ Examples:
3536
$ datasubst --input examples/basic-input.txt --json-data examples/basic-data.json
3637
$ echo "v3: {{ .key2.first.key3 }}" | datasubst --yaml-data examples/basic-data.yaml
3738
$ echo "{{ .TEST1 }} {{ .TEST2 }}" | TEST1="hello" TEST2="world" datasubst --env-data
38-
$ echo "(( .TEST ))" | TEST="hi" datasubst --env-data -d '((:))'`
39+
$ echo "(( .TEST ))" | TEST="hi" datasubst --env-data -d '((:))'
40+
$ echo "v3: {{ .first.key3 }}" | datasubst --yaml-data examples/basic-data.yaml --subtree .key2`
3941

4042
var Version string
4143

44+
var (
45+
inputFile, outputFile, jsonDataFile, yamlDataFile, delimiters, subtree string
46+
envFlag, strictFlag, helpFlag, versionFlag bool
47+
)
48+
49+
func main() {
50+
log.SetFlags(0)
51+
parseArgs()
52+
53+
// Read input
54+
in := os.Stdin
55+
if inputFile != "" && inputFile != "-" {
56+
f, err := os.Open(inputFile)
57+
if err != nil {
58+
log.Fatalf("Error opening input file: %v\n", err)
59+
}
60+
defer f.Close()
61+
in = f
62+
}
63+
tplStr, err := ioutil.ReadAll(in)
64+
if err != nil {
65+
log.Fatalf("Error reading input file: %v\n", err)
66+
}
67+
68+
// Read and Parse data file
69+
var data interface{}
70+
if jsonDataFile != "" {
71+
data, err = parseJSON(jsonDataFile)
72+
if subtree != "" {
73+
data = getSubTree(data, subtree)
74+
}
75+
} else if yamlDataFile != "" {
76+
data, err = parseYAML(yamlDataFile)
77+
if subtree != "" {
78+
data = getSubTree(data, subtree)
79+
}
80+
} else {
81+
data, err = parseEnv()
82+
}
83+
if err != nil {
84+
log.Fatalf("Error opening data file: %v\n", err)
85+
}
86+
87+
// Prepare Template
88+
tpl := template.New("template")
89+
if strictFlag {
90+
tpl.Option("missingkey=error")
91+
}
92+
if delimiters != "" {
93+
if strings.Count(delimiters, ":") != 1 || delimiters[len(delimiters)-1:] == ":" || delimiters[0:1] == ":" {
94+
log.Fatal("Error: invalid delimiter format. Must be '<left>:<right>' and ':'")
95+
}
96+
d := strings.Split(delimiters, ":")
97+
tpl.Delims(d[0], d[1])
98+
}
99+
tpl, err = tpl.Parse(string(tplStr))
100+
if err != nil {
101+
log.Fatalf("Error parsing template: %v\n", err)
102+
}
103+
104+
// Render
105+
out := os.Stdout
106+
if outputFile != "" && outputFile != "-" {
107+
out, err = os.Create(outputFile)
108+
if err != nil {
109+
log.Fatalf("Error creating output file: %v\n", err)
110+
}
111+
defer out.Close()
112+
}
113+
err = tpl.Execute(out, data)
114+
if err != nil {
115+
log.Fatalf("Error rendering template: %v\n", err)
116+
}
117+
}
118+
119+
func getSubTree(data interface{}, substree string) interface{} {
120+
st := strings.Split(subtree, ".")[1:]
121+
for _, k := range st {
122+
v := data.(map[string]interface{})
123+
data = v[k]
124+
}
125+
return data
126+
}
127+
42128
func parseYAML(yamlDataFile string) (interface{}, error) {
43129
var data interface{}
44130
dataFile, err := os.Open(filepath.Clean(yamlDataFile))
@@ -86,22 +172,18 @@ func countTrue(b ...bool) int {
86172
return n
87173
}
88174

89-
func main() {
90-
log.SetFlags(0)
175+
func parseArgs() {
91176
flag.Usage = func() { fmt.Fprintf(os.Stderr, "%s\n", usage) }
92177
if len(os.Args) == 1 {
93178
log.Fatalf("%s\n", usage)
94179
}
95180

96-
var (
97-
inputFile, outputFile, jsonDataFile, yamlDataFile, delimiters string
98-
envFlag, strictFlag, helpFlag, versionFlag bool
99-
)
100-
101-
flag.StringVar(&inputFile, "input", "", "input template file in go template format")
102-
flag.StringVar(&inputFile, "i", "", "input template file in go template format")
181+
flag.StringVar(&inputFile, "input", "", "input template file or directory containig template(s) in go template format")
182+
flag.StringVar(&inputFile, "i", "", "input template file or directory containig template(s) in go template format")
103183
flag.StringVar(&jsonDataFile, "json-data", "", "input data source in JSON format")
104184
flag.StringVar(&jsonDataFile, "j", "", "input data source in JSON format")
185+
flag.StringVar(&subtree, "subtree", "", "subtree to be used (e.g. .my_key.my_subkey)")
186+
flag.StringVar(&subtree, "t", "", "subtree to be used (e.g. .my_key.my_subkey)")
105187
flag.BoolVar(&envFlag, "env-data", false, "input data source comes from environment variables")
106188
flag.BoolVar(&envFlag, "e", false, "input data source comes from environment variables")
107189
flag.StringVar(&outputFile, "output", "", "write the output to the file at OUTPUT")
@@ -119,76 +201,22 @@ func main() {
119201
if versionFlag {
120202
if Version != "" {
121203
fmt.Println(Version)
122-
return
204+
os.Exit(0)
123205
}
124206
if buildInfo, ok := debug.ReadBuildInfo(); ok {
125207
fmt.Println(buildInfo.Main.Version)
126-
return
208+
os.Exit(0)
127209
}
128210
fmt.Println("(unknown)")
129-
return
211+
os.Exit(0)
130212
}
131213

132214
if helpFlag {
133215
fmt.Println(usage)
134-
return
216+
os.Exit(0)
135217
}
136218

137219
if countTrue(jsonDataFile != "", yamlDataFile != "", envFlag) != 1 {
138220
log.Fatal("Error: please specify --json-data, --yaml-data or --env-data")
139221
}
140-
// Read input
141-
in := os.Stdin
142-
if inputFile != "" && inputFile != "-" {
143-
f, err := os.Open(inputFile)
144-
if err != nil {
145-
log.Fatalf("Error opening input file: %v\n", err)
146-
}
147-
defer f.Close()
148-
in = f
149-
}
150-
tplStr, err := ioutil.ReadAll(in)
151-
if err != nil {
152-
log.Fatalf("Error reading input file: %v\n", err)
153-
}
154-
// Read and Parse data file
155-
var data interface{}
156-
if jsonDataFile != "" {
157-
data, err = parseJSON(jsonDataFile)
158-
} else if yamlDataFile != "" {
159-
data, err = parseYAML(yamlDataFile)
160-
} else {
161-
data, err = parseEnv()
162-
}
163-
if err != nil {
164-
log.Fatalf("Error opening data file: %v\n", err)
165-
}
166-
tpl := template.New("template")
167-
if strictFlag {
168-
tpl.Option("missingkey=error")
169-
}
170-
if delimiters != "" {
171-
if strings.Count(delimiters, ":") != 1 || delimiters[len(delimiters)-1:] == ":" || delimiters[0:1] == ":" {
172-
log.Fatal("Error: invalid delimiter format. Must be '<left>:<right>' and ':'")
173-
}
174-
d := strings.Split(delimiters, ":")
175-
tpl.Delims(d[0], d[1])
176-
}
177-
tpl, err = tpl.Parse(string(tplStr))
178-
if err != nil {
179-
log.Fatalf("Error parsing template: %v\n", err)
180-
}
181-
// Render
182-
out := os.Stdout
183-
if outputFile != "" && outputFile != "-" {
184-
out, err = os.Create(outputFile)
185-
if err != nil {
186-
log.Fatalf("Error creating output file: %v\n", err)
187-
}
188-
defer out.Close()
189-
}
190-
err = tpl.Execute(out, data)
191-
if err != nil {
192-
log.Fatalf("Error rendering template: %v\n", err)
193-
}
194222
}

0 commit comments

Comments
 (0)