-
Notifications
You must be signed in to change notification settings - Fork 73
/
pipe.go
153 lines (128 loc) · 4.39 KB
/
pipe.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
// Package pipe provides a simple API to read and retrieve values
// from stdin to use in Cobra commands. Public API consists of
// GetInput, which reads stdin, Exists, which checks for value
// existence, and Get for retrieving existing values.
package pipe
import (
"bufio"
"errors"
"fmt"
"io"
"os"
"strings"
"github.com/tidwall/gjson"
"github.com/newrelic/newrelic-cli/internal/utils"
)
// Created Interface and struct to surround io.Reader for easy mocking
type pipeReader interface {
ReadPipe() (string, error)
}
type stdinPipeReader struct {
input io.Reader
}
func (spr stdinPipeReader) ReadPipe() (string, error) {
text := ""
var err error
scanner := bufio.NewScanner(spr.input)
for scanner.Scan() {
text = fmt.Sprintf("%s%s ", text, strings.TrimSpace(scanner.Text()))
}
if scanErr := scanner.Err(); scanErr != nil {
err = scanErr
}
return strings.TrimSpace(text), err
}
func jsonToFilteredMap(r string, selectors []string) ([]map[string]string, error) {
text := r
if !gjson.Valid(text) {
return nil, errors.New("invalid JSON received by stdin")
}
// always start with an array of values
if strings.HasPrefix(text, "{") {
text = fmt.Sprintf("[ %s ]", text)
}
// returns []gjson.Result
jsonArray := gjson.Parse(text).Array()
resultsArray := make([]map[string]string, len(jsonArray))
for index, resultObj := range jsonArray {
resultMap := make(map[string]string)
for _, selector := range selectors {
if value := resultObj.Get(selector); value.Exists() {
// Convert every value to a string
resultMap[selector] = value.String()
}
}
resultsArray[index] = resultMap
}
return resultsArray, nil
}
func readStdin(pipe pipeReader, selectorList []string) ([]map[string]string, error) {
jsonString, pipeErr := pipe.ReadPipe()
if pipeErr != nil {
return nil, pipeErr
}
filteredMap, mapErr := jsonToFilteredMap(jsonString, selectorList)
if mapErr != nil {
return nil, mapErr
}
return filteredMap, nil
}
// getPipeInputInnerFunc is the function returned with arguments by
// getPipeInputFactory that becomes GetInput. Private to package, main
// function to be tested.
func getPipeInputInnerFunc(pipe pipeReader, pipeInputExists bool, acceptedPipeInput []string) map[string][]string {
if pipeInputExists {
pipeInputMap := map[string][]string{}
inputArray, err := readStdin(pipe, acceptedPipeInput)
if err != nil {
utils.LogIfError(err)
return map[string][]string{}
}
for _, key := range acceptedPipeInput {
var collectedItemsForKey []string
for _, value := range inputArray {
collectedItemsForKey = append(collectedItemsForKey, value[key])
}
pipeInputMap[key] = collectedItemsForKey
}
return pipeInputMap
}
return map[string][]string{}
}
// getPipeInputFactory is a factory method to create GetInput. Allows for
// extensive testing options using dependency injection. Only ever called
// once on the first import of the package. Private to the package.
func getPipeInputFactory(pipe pipeReader, predicate func() bool) func([]string) {
return func(acceptedPipeInput []string) {
if pipeInput == nil {
pipeInput = getPipeInputInnerFunc(pipe, predicate(), acceptedPipeInput)
}
}
}
var pipeInput map[string][]string
// GetInput takes a slice of gjson selectors (https://github.com/tidwall/gjson/blob/master/SYNTAX.md)
// as an argument. When ran once at the top the init function, GetInput
// stores those desired json values from stdin. The existence of and values
// of those stdin json keys can then be retrieved using the public Exists and
// Get methods, respectively.
var GetInput = getPipeInputFactory(stdinPipeReader{input: os.Stdin}, utils.StdinExists)
// Get is the only API provided to retrieve values from stdin json. Get
// is designed to be used in the cobra command itself, when any required
// value fed into stdin is needed. If stdin is empty or the value specified
// does not exist, Get will return nil for the value and false for the ok
// check.
func Get(inputKey string) ([]string, bool) {
if pipeInput == nil {
return nil, false
}
value, ok := pipeInput[inputKey]
return value, ok
}
// Exists is meant to be used in the init function, after GetInput is called,
// to determine if a required Cobra flag needs to be declared. If the inputKey
// exists in the Pipe module, then you can skip the required flag declaration
// and used the piped in values instead.
func Exists(inputKey string) bool {
_, ok := Get(inputKey)
return ok
}