-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathstruct.go
171 lines (137 loc) · 3.4 KB
/
struct.go
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
package main
import (
"bufio"
"encoding/json"
"errors"
"flag"
"fmt"
"os"
"strings"
)
const (
defaultFieldSeparator = " "
defaultOutput = "string"
fieldsUsage = "comma-separated list of fields corresponding to the input data's fields\n\n" +
"Example\n\n" +
"$ echo \"foo1 bar1\" | struct -fields foo,bar\n" +
"$ foo:foo1 bar:bar1\n"
outputUsage = "the desired output format: json or string"
separatorUsage = "the character that separates fields in the input data"
skipHeadUsage = "the number of lines to skip from the beginning of the input"
)
var (
fieldsFlag fields // a comma-separated list of fields provided by the user
outFlag string // the format (either string or json) in which to structure data
sepFlag string // a string on which to split lines of input
skipHead int // the number of lines to skip from the head
)
func init() {
flagset := flag.CommandLine
flagset.Var(&fieldsFlag, "fields", fieldsUsage)
flagset.StringVar(&sepFlag, "separator", defaultFieldSeparator, separatorUsage)
flagset.StringVar(&outFlag, "output", defaultOutput, outputUsage)
flagset.IntVar(&skipHead, "skip-head", 0, skipHeadUsage)
flagset.Usage = func() {
fmt.Println("struct creates structured output from unstructured input")
flagset.PrintDefaults()
}
flagset.Parse(os.Args[1:])
if len(fieldsFlag) == 0 {
flagset.Usage()
os.Exit(0)
}
}
var sb strings.Builder
var jsonMap = make(map[string]string)
func main() {
fieldsProvided := len(fieldsFlag)
scanner := bufio.NewScanner(os.Stdin)
var i, line int
var field, in, v string
// perform a line-wise scan over stdin until EOF
for scanner.Scan() {
line++
if line <= skipHead {
continue
}
in = scanner.Text()
// if the separator string is overridden, split on it instead of the default of splitting on space
var inputFields []string
if sepFlag != defaultFieldSeparator {
inputFields = strings.Split(in, sepFlag)
} else {
inputFields = strings.Fields(in)
}
// for every field to be retained from the input, build a representation of it in the output
for i, v = range inputFields {
if i < fieldsProvided {
field = fieldsFlag[i]
} else {
field = ""
}
if err := buildOutput(field, v); err != nil {
fmt.Println(err.Error())
return
}
}
fmt.Println(out())
}
}
func out() (out string) {
switch outFlag {
case "string":
out = sb.String()
sb.Reset()
case "json":
o, _ := json.Marshal(jsonMap)
out = string(o)
for k := range jsonMap {
delete(jsonMap, k)
}
default:
out = ""
}
return
}
func buildOutput(field, value string) (err error) {
switch outFlag {
case "string":
buildString(field, value)
case "json":
buildJSON(field, value)
default:
err = fmt.Errorf("invalid -output option: %s", outFlag)
return
}
return
}
func buildString(field, value string) {
if field != "" {
sb.WriteString(field)
sb.WriteString(":")
sb.WriteString(value)
sb.WriteString(defaultFieldSeparator)
} else {
sb.WriteString(value)
sb.WriteString(defaultFieldSeparator)
}
}
func buildJSON(field, value string) {
if field == "" {
return
}
jsonMap[field] = value
}
type fields []string
func (f *fields) String() string {
return fmt.Sprint(*f)
}
func (f *fields) Set(value string) error {
if len(*f) > 0 {
return errors.New("fields switch already set")
}
for _, field := range strings.Split(value, ",") {
*f = append(*f, field)
}
return nil
}