Skip to content
This repository was archived by the owner on Jan 14, 2026. It is now read-only.

Commit 29578ea

Browse files
committed
Add dot formatter
1 parent 4584140 commit 29578ea

3 files changed

Lines changed: 161 additions & 0 deletions

File tree

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ Please parses data structures in the above formats and can output them as:
146146

147147
* bash `declare` syntax
148148
* YAML
149+
* dot (graphviz)
149150

150151
If you're not familiar with associative arrays in bash or how `declare` works, it's worth reading the following:
151152

@@ -195,3 +196,7 @@ Making use of the bash-declare output format
195196

196197
$ echo '{"json": ["array", "values"]}' | please parse json | (declare -A data=$(cat -); echo ${data[1]})
197198
values
199+
200+
Generating a graph from some json (you need graphviz installed)
201+
202+
$ echo '{"vars": ["foo", "bar", "baz"], "cake": {"is_lie": true}}' | please parse -o dot | dot -Tpng > graph.png

cmd/parse.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ func init() {
4949
formatters = map[string]func(interface{}, string) string{
5050
"bash": formatter.Bash,
5151
"yaml": formatter.Yaml,
52+
"dot": formatter.Dot,
5253
}
5354
}
5455

formatter/dot.go

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package formatter
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"os"
7+
"reflect"
8+
"strconv"
9+
"strings"
10+
)
11+
12+
type node struct {
13+
name string
14+
label string
15+
}
16+
17+
type link struct {
18+
left string
19+
right string
20+
}
21+
22+
func wrap(in string) string {
23+
out := strings.Replace(in, "\\", "\\\\", -1)
24+
out = strings.Replace(out, "\"", "\\\"", -1)
25+
out = strings.Replace(out, "\n", "\\n", -1)
26+
out = fmt.Sprintf("\"%s\"", out)
27+
28+
return out
29+
}
30+
31+
func Dot(in interface{}, path string) (out string) {
32+
nodes, links := flatten(in, path, "root")
33+
34+
nodes = append(nodes, node{
35+
name: "root",
36+
label: "[Root]",
37+
})
38+
39+
var buf bytes.Buffer
40+
41+
for _, node := range nodes {
42+
buf.WriteString(fmt.Sprintf("%s [label=%s];\n", wrap(node.name), wrap(node.label)))
43+
}
44+
45+
for _, link := range links {
46+
buf.WriteString(fmt.Sprintf("%s -- %s;\n", wrap(link.left), wrap(link.right)))
47+
}
48+
49+
return fmt.Sprintf("graph{\n%s}", buf.String())
50+
}
51+
52+
func flatten(in interface{}, path string, current_path string) ([]node, []link) {
53+
var nodes []node
54+
var links []link
55+
56+
if in == nil {
57+
return nodes, links
58+
}
59+
60+
val := reflect.ValueOf(in)
61+
62+
split_path := strings.SplitN(path, ".", 2)
63+
64+
this_path := split_path[0]
65+
var next_path string
66+
67+
if len(split_path) > 1 {
68+
next_path = split_path[1]
69+
}
70+
71+
switch val.Kind() {
72+
case reflect.Map:
73+
vv := in.(map[string]interface{})
74+
75+
if this_path != "" {
76+
if _, ok := vv[this_path]; !ok {
77+
fmt.Fprintf(os.Stderr, "Key does not exist: %s\n", this_path)
78+
os.Exit(1)
79+
}
80+
81+
return flatten(vv[this_path], next_path, this_path)
82+
}
83+
84+
for key, value := range vv {
85+
target := current_path + "-" + key
86+
87+
nodes = append(nodes, node{
88+
name: target,
89+
label: key,
90+
})
91+
92+
if current_path != "" {
93+
links = append(links, link{
94+
left: current_path,
95+
right: target,
96+
})
97+
}
98+
99+
new_nodes, new_links := flatten(value, path, target)
100+
101+
nodes = append(nodes, new_nodes...)
102+
links = append(links, new_links...)
103+
}
104+
105+
return nodes, links
106+
case reflect.Array, reflect.Slice:
107+
if this_path != "" {
108+
index, err := strconv.Atoi(this_path)
109+
110+
if err != nil || index < 0 || index >= val.Len() {
111+
fmt.Fprintf(os.Stderr, "Key does not exist: %s\n", this_path)
112+
os.Exit(1)
113+
}
114+
115+
return flatten(val.Index(index).Interface(), next_path, this_path)
116+
}
117+
118+
for index := 0; index < val.Len(); index++ {
119+
value := val.Index(index).Interface()
120+
121+
target := current_path + "-" + fmt.Sprint(index)
122+
123+
nodes = append(nodes, node{
124+
name: target,
125+
label: fmt.Sprintf("[%d]", index),
126+
})
127+
128+
if current_path != "" {
129+
links = append(links, link{
130+
left: current_path,
131+
right: target,
132+
})
133+
}
134+
135+
new_nodes, new_links := flatten(value, path, target)
136+
137+
nodes = append(nodes, new_nodes...)
138+
links = append(links, new_links...)
139+
}
140+
141+
return nodes, links
142+
default:
143+
if this_path != "" {
144+
fmt.Fprintf(os.Stderr, "Key does not exist: %s\n", this_path)
145+
os.Exit(1)
146+
}
147+
148+
links = append(links, link{
149+
left: current_path,
150+
right: fmt.Sprint(in),
151+
})
152+
153+
return nodes, links
154+
}
155+
}

0 commit comments

Comments
 (0)