• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

astronomer / astro-cli / d8c087d9-3e34-4a26-9912-4e9425408752

04 Feb 2026 08:31PM UTC coverage: 32.799% (+0.005%) from 32.794%
d8c087d9-3e34-4a26-9912-4e9425408752

push

circleci

jeremybeard
Fix linting

18 of 95 new or added lines in 9 files covered. (18.95%)

5 existing lines in 1 file now uncovered.

21211 of 64670 relevant lines covered (32.8%)

8.33 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

73.96
/cmd/api/output.go
1
package api
2

3
import (
4
        "bytes"
5
        "encoding/json"
6
        "fmt"
7
        "io"
8
        "os"
9
        "strings"
10
        "text/template"
11

12
        "github.com/fatih/color"
13
        "github.com/itchyny/gojq"
14
        "github.com/mattn/go-isatty"
15
)
16

17
// ANSI color codes for JSON syntax highlighting
18
const (
19
        colorDelim  = "1;37" // bright white
20
        colorKey    = "1;34" // bright blue
21
        colorNull   = "36"   // cyan
22
        colorString = "32"   // green
23
        colorBool   = "33"   // yellow
24
        colorNumber = "35"   // magenta
25
)
26

27
// OutputOptions configures how output is processed.
28
type OutputOptions struct {
29
        FilterOutput string // jq filter expression
30
        Template     string // Go template
31
        ColorEnabled bool   // whether to colorize output
32
        Indent       string // indentation for JSON
33
}
34

35
// processOutput processes the response body based on output options.
36
func processOutput(body []byte, out io.Writer, opts OutputOptions) error {
3✔
37
        // jq filtering takes precedence
3✔
38
        if opts.FilterOutput != "" {
4✔
39
                return evaluateJQ(body, out, opts.FilterOutput, opts.ColorEnabled)
1✔
40
        }
1✔
41

42
        // Go template formatting
43
        if opts.Template != "" {
3✔
44
                return evaluateTemplate(body, out, opts.Template)
1✔
45
        }
1✔
46

47
        // Default: pretty-print JSON
48
        return writeColorizedJSON(out, body, opts.ColorEnabled, opts.Indent)
1✔
49
}
50

51
// evaluateJQ evaluates a jq expression against the input JSON.
52
func evaluateJQ(input []byte, out io.Writer, expr string, colorize bool) error {
9✔
53
        query, err := gojq.Parse(expr)
9✔
54
        if err != nil {
10✔
55
                return fmt.Errorf("parsing jq expression: %w", err)
1✔
56
        }
1✔
57

58
        var data interface{}
8✔
59
        if err := json.Unmarshal(input, &data); err != nil {
9✔
60
                return fmt.Errorf("parsing JSON: %w", err)
1✔
61
        }
1✔
62

63
        iter := query.Run(data)
7✔
64
        for {
25✔
65
                v, ok := iter.Next()
18✔
66
                if !ok {
25✔
67
                        break
7✔
68
                }
69
                if err, isErr := v.(error); isErr {
11✔
70
                        return fmt.Errorf("jq error: %w", err)
×
71
                }
×
72

73
                // Format output
74
                output, err := formatValue(v, colorize)
11✔
75
                if err != nil {
11✔
76
                        return err
×
77
                }
×
78
                fmt.Fprintln(out, output)
11✔
79
        }
80

81
        return nil
7✔
82
}
83

84
// formatValue formats a single value for output.
85
func formatValue(v interface{}, colorize bool) (string, error) {
11✔
86
        switch val := v.(type) {
11✔
87
        case string:
7✔
88
                return val, nil
7✔
89
        case nil:
×
90
                if colorize {
×
91
                        return fmt.Sprintf("\x1b[%sm%s\x1b[m", colorNull, "null"), nil
×
92
                }
×
93
                return "null", nil
×
94
        default:
4✔
95
                // For complex types, marshal to JSON
4✔
96
                b, err := json.MarshalIndent(v, "", "  ")
4✔
97
                if err != nil {
4✔
98
                        return "", err
×
99
                }
×
100
                if colorize {
4✔
101
                        var buf bytes.Buffer
×
102
                        if err := writeColorizedJSON(&buf, b, true, "  "); err != nil {
×
103
                                return string(b), nil
×
104
                        }
×
105
                        return buf.String(), nil
×
106
                }
107
                return string(b), nil
4✔
108
        }
109
}
110

111
// evaluateTemplate evaluates a Go template against the input JSON.
112
func evaluateTemplate(input []byte, out io.Writer, tmplStr string) error {
6✔
113
        var data interface{}
6✔
114
        if err := json.Unmarshal(input, &data); err != nil {
7✔
115
                return fmt.Errorf("parsing JSON: %w", err)
1✔
116
        }
1✔
117

118
        tmpl, err := template.New("output").Funcs(templateFuncs()).Parse(tmplStr)
5✔
119
        if err != nil {
6✔
120
                return fmt.Errorf("parsing template: %w", err)
1✔
121
        }
1✔
122

123
        return tmpl.Execute(out, data)
4✔
124
}
125

126
// templateFuncs returns custom template functions.
127
func templateFuncs() template.FuncMap {
6✔
128
        return template.FuncMap{
6✔
129
                "join": strings.Join,
6✔
130
                "color": func(c string, s interface{}) string {
6✔
131
                        switch c {
×
132
                        case "red":
×
133
                                return color.RedString("%v", s)
×
134
                        case "green":
×
135
                                return color.GreenString("%v", s)
×
136
                        case "yellow":
×
137
                                return color.YellowString("%v", s)
×
138
                        case "blue":
×
139
                                return color.BlueString("%v", s)
×
140
                        case "magenta":
×
141
                                return color.MagentaString("%v", s)
×
142
                        case "cyan":
×
143
                                return color.CyanString("%v", s)
×
144
                        case "white":
×
145
                                return color.WhiteString("%v", s)
×
146
                        default:
×
147
                                return fmt.Sprintf("%v", s)
×
148
                        }
149
                },
150
                "pluck": func(key string, items interface{}) []interface{} {
1✔
151
                        var result []interface{}
1✔
152
                        if arr, ok := items.([]interface{}); ok {
2✔
153
                                for _, item := range arr {
3✔
154
                                        if m, ok := item.(map[string]interface{}); ok {
4✔
155
                                                if v, exists := m[key]; exists {
4✔
156
                                                        result = append(result, v)
2✔
157
                                                }
2✔
158
                                        }
159
                                }
160
                        }
161
                        return result
1✔
162
                },
163
        }
164
}
165

166
// writeColorizedJSON writes colorized JSON to the output.
167
//
168
//nolint:gocognit // Complex but necessary for proper JSON colorization
169
func writeColorizedJSON(w io.Writer, data []byte, colorize bool, indent string) error {
3✔
170
        if !colorize {
5✔
171
                // Pretty print without colors
2✔
172
                var out bytes.Buffer
2✔
173
                if err := json.Indent(&out, data, "", indent); err != nil {
2✔
174
                        // If indentation fails, write raw
×
175
                        _, err = w.Write(data)
×
176
                        return err
×
177
                }
×
178
                _, err := w.Write(out.Bytes())
2✔
179
                if err == nil {
4✔
180
                        fmt.Fprintln(w) // Add newline
2✔
181
                }
2✔
182
                return err
2✔
183
        }
184

185
        dec := json.NewDecoder(bytes.NewReader(data))
1✔
186
        dec.UseNumber()
1✔
187

1✔
188
        var idx int
1✔
189
        var stack []json.Delim
1✔
190

1✔
191
        for {
12✔
192
                t, err := dec.Token()
11✔
193
                if err == io.EOF {
12✔
194
                        break
1✔
195
                }
196
                if err != nil {
10✔
197
                        return err
×
198
                }
×
199

200
                switch tt := t.(type) {
10✔
201
                case json.Delim:
2✔
202
                        switch tt {
2✔
203
                        case '{', '[':
1✔
204
                                stack = append(stack, tt)
1✔
205
                                idx = 0
1✔
206
                                fmt.Fprintf(w, "\x1b[%sm%s\x1b[m", colorDelim, string(tt))
1✔
207
                                if dec.More() {
2✔
208
                                        fmt.Fprint(w, "\n", strings.Repeat(indent, len(stack)))
1✔
209
                                }
1✔
210
                                continue
1✔
211
                        case '}', ']':
1✔
212
                                stack = stack[:len(stack)-1]
1✔
213
                                idx = 0
1✔
214
                                fmt.Fprintf(w, "\x1b[%sm%s\x1b[m", colorDelim, string(tt))
1✔
215
                        }
216
                default:
8✔
217
                        b, err := marshalJSON(tt)
8✔
218
                        if err != nil {
8✔
219
                                return err
×
220
                        }
×
221

222
                        isKey := len(stack) > 0 && stack[len(stack)-1] == '{' && idx%2 == 0
8✔
223
                        idx++
8✔
224

8✔
225
                        var clr string
8✔
226
                        switch {
8✔
227
                        case isKey:
4✔
228
                                clr = colorKey
4✔
229
                        case tt == nil:
1✔
230
                                clr = colorNull
1✔
231
                        default:
3✔
232
                                switch t.(type) {
3✔
233
                                case string:
1✔
234
                                        clr = colorString
1✔
235
                                case bool:
1✔
236
                                        clr = colorBool
1✔
237
                                case json.Number:
1✔
238
                                        clr = colorNumber
1✔
239
                                }
240
                        }
241

242
                        if clr == "" {
8✔
NEW
243
                                _, _ = w.Write(b)
×
244
                        } else {
8✔
245
                                fmt.Fprintf(w, "\x1b[%sm%s\x1b[m", clr, b)
8✔
246
                        }
8✔
247

248
                        if isKey {
12✔
249
                                fmt.Fprintf(w, "\x1b[%sm:\x1b[m ", colorDelim)
4✔
250
                                continue
4✔
251
                        }
252
                }
253

254
                switch {
5✔
255
                case dec.More():
3✔
256
                        fmt.Fprintf(w, "\x1b[%sm,\x1b[m\n%s", colorDelim, strings.Repeat(indent, len(stack)))
3✔
257
                case len(stack) > 0:
1✔
258
                        fmt.Fprint(w, "\n", strings.Repeat(indent, len(stack)-1))
1✔
259
                default:
1✔
260
                        fmt.Fprint(w, "\n")
1✔
261
                }
262
        }
263

264
        return nil
1✔
265
}
266

267
// marshalJSON marshals a value to JSON without HTML escaping.
268
func marshalJSON(v interface{}) ([]byte, error) {
8✔
269
        buf := bytes.Buffer{}
8✔
270
        enc := json.NewEncoder(&buf)
8✔
271
        enc.SetEscapeHTML(false)
8✔
272
        if err := enc.Encode(v); err != nil {
8✔
273
                return nil, err
×
274
        }
×
275
        bb := buf.Bytes()
8✔
276
        // Trim trailing newline added by encoder
8✔
277
        if len(bb) > 0 && bb[len(bb)-1] == '\n' {
16✔
278
                return bb[:len(bb)-1], nil
8✔
279
        }
8✔
280
        return bb, nil
×
281
}
282

283
// isColorEnabled returns true if color output should be enabled.
284
func isColorEnabled(out io.Writer) bool {
×
285
        if f, ok := out.(*os.File); ok {
×
286
                return isatty.IsTerminal(f.Fd()) || isatty.IsCygwinTerminal(f.Fd())
×
287
        }
×
288
        return false
×
289
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc