forked from johnkerl/miller
-
Notifications
You must be signed in to change notification settings - Fork 0
/
util.go
231 lines (206 loc) · 5.26 KB
/
util.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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
package lib
import (
"fmt"
"os"
"sort"
"strconv"
"strings"
"unicode/utf8"
)
func BooleanXOR(a, b bool) bool {
return a != b
}
func BoolToInt(b bool) int64 {
if b == false {
return 0
} else {
return 1
}
}
func Plural(n int) string {
if n == 1 {
return ""
} else {
return "s"
}
}
// In Go as in all languages I'm aware of with a string-split, "a,b,c" splits
// on "," to ["a", "b", "c" and "a" splits to ["a"], both of which are fine --
// but "" splits to [""] when I wish it were []. This function does the latter.
func SplitString(input string, separator string) []string {
if input == "" {
return make([]string, 0)
} else {
return strings.Split(input, separator)
}
}
func StringListToSet(stringList []string) map[string]bool {
if stringList == nil {
return nil
}
stringSet := make(map[string]bool)
for _, s := range stringList {
stringSet[s] = true
}
return stringSet
}
func SortStrings(strings []string) {
// Go sort API: for ascending sort, return true if element i < element j.
sort.Slice(strings, func(i, j int) bool {
return strings[i] < strings[j]
})
}
func ReverseStringList(strings []string) {
n := len(strings)
i := 0
j := n - 1
for i < j {
temp := strings[i]
strings[i] = strings[j]
strings[j] = temp
i++
j--
}
}
func SortedStrings(strings []string) []string {
copy := make([]string, len(strings))
for i, s := range strings {
copy[i] = s
}
// Go sort API: for ascending sort, return true if element i < element j.
sort.Slice(copy, func(i, j int) bool {
return copy[i] < copy[j]
})
return copy
}
func IntMin2(a, b int64) int64 {
if a < b {
return a
} else {
return b
}
}
// TryIntFromString tries decimal, hex, octal, and binary.
func TryIntFromString(input string) (int64, bool) {
// Go's strconv parses "1_2" as 12; not OK for Miller syntax. (Also not valid JSON.)
for i := 0; i < len(input); i++ {
if input[i] == '_' {
return 0, false
}
}
// Following twos-complement formatting familiar from all manner of
// languages, including C which was Miller's original implementation
// language, we want to allow 0x00....00 through 0x7f....ff as positive
// 64-bit integers and 0x80....00 through 0xff....ff as negative ones. Go's
// signed-int parsing explicitly doesn't allow that, but we don't want Go
// semantics to dictate Miller semantics. So, we try signed-int parsing
// for 0x00....00 through 0x7f....ff, as well as positive or negative
// decimal. Failing that, we try unsigned-int parsing for 0x80....00
// through 0xff....ff.
i64, ierr := strconv.ParseInt(input, 0 /* check all*/, 64)
if ierr == nil {
return i64, true
}
u64, uerr := strconv.ParseUint(input, 0 /* check all*/, 64)
if uerr == nil {
return int64(u64), true
}
return 0, false
}
// TryIntFromStringWithBase allows the user to choose the base that's used,
// rather than inferring from 0x prefix, etc as TryIntFromString does.
func TryIntFromStringWithBase(input string, base int64) (int64, bool) {
// Go's strconv parses "1_2" as 12; not OK for Miller syntax. (Also not valid JSON.)
for i := 0; i < len(input); i++ {
if input[i] == '_' {
return 0, false
}
}
i64, ierr := strconv.ParseInt(input, int(base), 64)
if ierr == nil {
return i64, true
}
u64, uerr := strconv.ParseUint(input, int(base), 64)
if uerr == nil {
return int64(u64), true
}
return 0, false
}
func TryFloatFromString(input string) (float64, bool) {
// Go's strconv parses "1_2.3_4" as 12.34; not OK for Miller syntax. (Also not valid JSON.)
for i := 0; i < len(input); i++ {
if input[i] == '_' {
return 0, false
}
}
fval, err := strconv.ParseFloat(input, 64)
if err == nil {
return fval, true
} else {
return 0, false
}
}
func TryBoolFromBoolString(input string) (bool, bool) {
if input == "true" {
return true, true
} else if input == "false" {
return false, true
} else {
return false, false
}
}
// Go doesn't preserve insertion order in its arrays, so here we make an
// accessor for getting the keys in sorted order for the benefit of
// map-printers.
func GetArrayKeysSorted(input map[string]string) []string {
keys := make([]string, len(input))
i := 0
for key := range input {
keys[i] = key
i++
}
sort.Strings(keys)
return keys
}
// WriteTempFile places the contents string into a temp file, which the caller
// must remove.
func WriteTempFileOrDie(contents string) string {
// Use "" as first argument to os.CreateTemp to use default directory.
// Nominally "/tmp" or somesuch on all unix-like systems, but not for Windows.
handle, err := os.CreateTemp("", "mlr-temp")
if err != nil {
fmt.Printf("mlr: could not create temp file.\n")
os.Exit(1)
}
_, err = handle.WriteString(contents)
if err != nil {
fmt.Printf("mlr: could not populate temp file.\n")
os.Exit(1)
}
err = handle.Close()
if err != nil {
fmt.Printf("mlr: could not finish write of temp file.\n")
os.Exit(1)
}
return handle.Name()
}
func CopyStringArray(input []string) []string {
if input == nil {
return nil
}
output := make([]string, len(input))
copy(output, input)
return output
}
func StripEmpties(input []string) []string {
output := make([]string, 0, len(input))
for _, e := range input {
if e != "" {
output = append(output, e)
}
}
return output
}
func UTF8Strlen(s string) int64 {
return int64(utf8.RuneCountInString(s))
}