Skip to content

Commit 9eedf51

Browse files
authored
Merge pull request #3998 from jandubois/yq-options
Add `--yq` option to `limactl list` and `limactl info`
2 parents 75b4c3e + 02a2be1 commit 9eedf51

File tree

5 files changed

+147
-17
lines changed

5 files changed

+147
-17
lines changed

cmd/limactl/info.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@ import (
77
"encoding/json"
88
"fmt"
99

10+
"github.com/mikefarah/yq/v4/pkg/yqlib"
1011
"github.com/spf13/cobra"
1112

1213
"github.com/lima-vm/lima/v2/pkg/limainfo"
14+
"github.com/lima-vm/lima/v2/pkg/uiutil"
15+
"github.com/lima-vm/lima/v2/pkg/yqutil"
1316
)
1417

1518
func newInfoCommand() *cobra.Command {
@@ -20,11 +23,19 @@ func newInfoCommand() *cobra.Command {
2023
RunE: infoAction,
2124
GroupID: advancedCommand,
2225
}
26+
infoCommand.Flags().String("yq", ".", "Apply yq expression to output")
27+
2328
return infoCommand
2429
}
2530

2631
func infoAction(cmd *cobra.Command, _ []string) error {
2732
ctx := cmd.Context()
33+
34+
yq, err := cmd.Flags().GetString("yq")
35+
if err != nil {
36+
return err
37+
}
38+
2839
info, err := limainfo.New(ctx)
2940
if err != nil {
3041
return err
@@ -33,6 +44,14 @@ func infoAction(cmd *cobra.Command, _ []string) error {
3344
if err != nil {
3445
return err
3546
}
36-
_, err = fmt.Fprintln(cmd.OutOrStdout(), string(j))
47+
48+
encoderPrefs := yqlib.ConfiguredJSONPreferences.Copy()
49+
encoderPrefs.Indent = 4
50+
encoderPrefs.ColorsEnabled = uiutil.OutputIsTTY(cmd.OutOrStdout())
51+
encoder := yqlib.NewJSONEncoder(encoderPrefs)
52+
str, err := yqutil.EvaluateExpressionWithEncoder(yq, string(j), encoder)
53+
if err == nil {
54+
_, err = fmt.Fprint(cmd.OutOrStdout(), str)
55+
}
3756
return err
3857
}

cmd/limactl/list.go

Lines changed: 88 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,23 @@
44
package main
55

66
import (
7+
"bufio"
8+
"bytes"
79
"errors"
810
"fmt"
9-
"os"
1011
"reflect"
1112
"sort"
1213
"strings"
1314

1415
"github.com/cheggaaa/pb/v3/termutil"
15-
"github.com/mattn/go-isatty"
16+
"github.com/mikefarah/yq/v4/pkg/yqlib"
1617
"github.com/sirupsen/logrus"
1718
"github.com/spf13/cobra"
1819

1920
"github.com/lima-vm/lima/v2/pkg/limatype"
2021
"github.com/lima-vm/lima/v2/pkg/store"
22+
"github.com/lima-vm/lima/v2/pkg/uiutil"
23+
"github.com/lima-vm/lima/v2/pkg/yqutil"
2124
)
2225

2326
func fieldNames() []string {
@@ -64,6 +67,7 @@ The following legacy flags continue to function:
6467
listCommand.Flags().Bool("json", false, "JSONify output")
6568
listCommand.Flags().BoolP("quiet", "q", false, "Only show names")
6669
listCommand.Flags().Bool("all-fields", false, "Show all fields")
70+
listCommand.Flags().String("yq", "", "Apply yq expression to each instance")
6771

6872
return listCommand
6973
}
@@ -109,6 +113,10 @@ func listAction(cmd *cobra.Command, args []string) error {
109113
if err != nil {
110114
return err
111115
}
116+
yq, err := cmd.Flags().GetString("yq")
117+
if err != nil {
118+
return err
119+
}
112120

113121
if jsonFormat {
114122
format = "json"
@@ -121,6 +129,14 @@ func listAction(cmd *cobra.Command, args []string) error {
121129
if listFields && cmd.Flags().Changed("format") {
122130
return errors.New("option --list-fields conflicts with option --format")
123131
}
132+
if yq != "" {
133+
if cmd.Flags().Changed("format") && format != "json" && format != "yaml" {
134+
return errors.New("option --yq only works with --format json or yaml")
135+
}
136+
if listFields {
137+
return errors.New("option --list-fields conflicts with option --yq")
138+
}
139+
}
124140

125141
if quiet && format != "table" {
126142
return errors.New("option --quiet can only be used with '--format table'")
@@ -194,16 +210,80 @@ func listAction(cmd *cobra.Command, args []string) error {
194210
}
195211

196212
options := store.PrintOptions{AllFields: allFields}
197-
out := cmd.OutOrStdout()
198-
if out == os.Stdout {
199-
if isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd()) {
200-
if w, err := termutil.TerminalWidth(); err == nil {
201-
options.TerminalWidth = w
213+
isTTY := uiutil.OutputIsTTY(cmd.OutOrStdout())
214+
if isTTY {
215+
if w, err := termutil.TerminalWidth(); err == nil {
216+
options.TerminalWidth = w
217+
}
218+
}
219+
// --yq implies --format json unless --format yaml has been explicitly specified
220+
if yq != "" && !cmd.Flags().Changed("format") {
221+
format = "json"
222+
}
223+
// Always pipe JSON and YAML through yq to colorize it if isTTY
224+
if yq == "" && (format == "json" || format == "yaml") {
225+
yq = "."
226+
}
227+
228+
if yq == "" {
229+
err = store.PrintInstances(cmd.OutOrStdout(), instances, format, &options)
230+
if err == nil && unmatchedInstances {
231+
return unmatchedInstancesError{}
232+
}
233+
return err
234+
}
235+
236+
buf := new(bytes.Buffer)
237+
err = store.PrintInstances(buf, instances, format, &options)
238+
if err != nil {
239+
return err
240+
}
241+
242+
if format == "json" {
243+
encoderPrefs := yqlib.ConfiguredJSONPreferences.Copy()
244+
if isTTY {
245+
// Using non-0 indent means the instance will be printed over multiple lines,
246+
// so is no longer in JSON Lines format. This is a compromise for readability.
247+
encoderPrefs.Indent = 4
248+
encoderPrefs.ColorsEnabled = true
249+
} else {
250+
encoderPrefs.Indent = 0
251+
encoderPrefs.ColorsEnabled = false
252+
}
253+
encoder := yqlib.NewJSONEncoder(encoderPrefs)
254+
255+
// Each line contains the JSON object for one Lima instance.
256+
scanner := bufio.NewScanner(buf)
257+
for scanner.Scan() {
258+
var str string
259+
if str, err = yqutil.EvaluateExpressionWithEncoder(yq, scanner.Text(), encoder); err != nil {
260+
return err
202261
}
262+
if _, err = fmt.Fprint(cmd.OutOrStdout(), str); err != nil {
263+
return err
264+
}
265+
}
266+
err = scanner.Err()
267+
if err == nil && unmatchedInstances {
268+
return unmatchedInstancesError{}
203269
}
270+
return err
204271
}
205272

206-
err = store.PrintInstances(out, instances, format, &options)
273+
var str string
274+
if isTTY {
275+
// This branch is trading the better formatting from yamlfmt for colorizing from yqlib.
276+
if str, err = yqutil.EvaluateExpressionPlain(yq, buf.String(), true); err != nil {
277+
return err
278+
}
279+
} else {
280+
var res []byte
281+
if res, err = yqutil.EvaluateExpression(yq, buf.Bytes()); err != nil {
282+
return err
283+
}
284+
str = string(res)
285+
}
286+
_, err = fmt.Fprint(cmd.OutOrStdout(), str)
207287
if err == nil && unmatchedInstances {
208288
return unmatchedInstancesError{}
209289
}

cmd/limactl/template.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"github.com/lima-vm/lima/v2/pkg/limatmpl"
1818
"github.com/lima-vm/lima/v2/pkg/limatype/dirnames"
1919
"github.com/lima-vm/lima/v2/pkg/limayaml"
20+
"github.com/lima-vm/lima/v2/pkg/uiutil"
2021
"github.com/lima-vm/lima/v2/pkg/yqutil"
2122
)
2223

@@ -148,6 +149,15 @@ func templateCopyAction(cmd *cobra.Command, args []string) error {
148149
return err
149150
}
150151
}
152+
if target == "-" && uiutil.OutputIsTTY(cmd.OutOrStdout()) {
153+
// run the output through YQ to colorize it
154+
out, err := yqutil.EvaluateExpressionPlain(".", string(tmpl.Bytes), true)
155+
if err == nil {
156+
_, err = fmt.Fprint(cmd.OutOrStdout(), out)
157+
}
158+
return err
159+
}
160+
151161
writer := cmd.OutOrStdout()
152162
if target != "-" {
153163
file, err := os.OpenFile(target, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
@@ -200,7 +210,8 @@ func templateYQAction(cmd *cobra.Command, args []string) error {
200210
if err := fillDefaults(ctx, tmpl); err != nil {
201211
return err
202212
}
203-
out, err := yqutil.EvaluateExpressionPlain(expr, string(tmpl.Bytes))
213+
colorsEnabled := uiutil.OutputIsTTY(cmd.OutOrStdout())
214+
out, err := yqutil.EvaluateExpressionPlain(expr, string(tmpl.Bytes), colorsEnabled)
204215
if err == nil {
205216
_, err = fmt.Fprint(cmd.OutOrStdout(), out)
206217
}

pkg/uiutil/uiutil.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@
44
package uiutil
55

66
import (
7+
"io"
8+
"os"
9+
710
"github.com/AlecAivazis/survey/v2"
811
"github.com/AlecAivazis/survey/v2/terminal"
12+
"github.com/mattn/go-isatty"
913
)
1014

1115
var InterruptErr = terminal.InterruptErr
@@ -36,3 +40,14 @@ func Select(message string, options []string) (int, error) {
3640
}
3741
return ans, nil
3842
}
43+
44+
// OutputIsTTY returns true if writer is going to stdout, and stdout is a terminal device,
45+
// not a regular file, stream, or pipe etc.
46+
func OutputIsTTY(writer io.Writer) bool {
47+
// This setting is needed so we can write integration tests for the TTY output.
48+
// It is probably not useful otherwise.
49+
if os.Getenv("_LIMA_OUTPUT_IS_TTY") != "" {
50+
return true
51+
}
52+
return writer == os.Stdout && (isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd()))
53+
}

pkg/yqutil/yqutil.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ func ValidateContent(content []byte) error {
3939
return err
4040
}
4141

42-
// EvaluateExpressionPlain evaluates the yq expression and returns the yq result.
43-
func EvaluateExpressionPlain(expression, content string) (string, error) {
42+
// EvaluateExpressionWithEncoder evaluates the yq expression and returns the yq result using a custom encoder.
43+
func EvaluateExpressionWithEncoder(expression, content string, encoder yqlib.Encoder) (string, error) {
4444
if expression == "" {
4545
return content, nil
4646
}
@@ -50,10 +50,6 @@ func EvaluateExpressionPlain(expression, content string) (string, error) {
5050
logging.SetBackend(backend)
5151
yqlib.InitExpressionParser()
5252

53-
encoderPrefs := yqlib.ConfiguredYamlPreferences.Copy()
54-
encoderPrefs.Indent = 2
55-
encoderPrefs.ColorsEnabled = false
56-
encoder := yqlib.NewYamlEncoder(encoderPrefs)
5753
decoder := yqlib.NewYamlDecoder(yqlib.ConfiguredYamlPreferences)
5854
out, err := yqlib.NewStringEvaluator().EvaluateAll(expression, content, encoder, decoder)
5955
if err != nil {
@@ -82,6 +78,15 @@ func EvaluateExpressionPlain(expression, content string) (string, error) {
8278
return out, nil
8379
}
8480

81+
// EvaluateExpressionPlain evaluates the yq expression and returns the yq result.
82+
func EvaluateExpressionPlain(expression, content string, colorsEnabled bool) (string, error) {
83+
encoderPrefs := yqlib.ConfiguredYamlPreferences.Copy()
84+
encoderPrefs.Indent = 2
85+
encoderPrefs.ColorsEnabled = colorsEnabled
86+
encoder := yqlib.NewYamlEncoder(encoderPrefs)
87+
return EvaluateExpressionWithEncoder(expression, content, encoder)
88+
}
89+
8590
// EvaluateExpression evaluates the yq expression and returns the output formatted with yamlfmt.
8691
func EvaluateExpression(expression string, content []byte) ([]byte, error) {
8792
if expression == "" {
@@ -101,7 +106,7 @@ func EvaluateExpression(expression string, content []byte) ([]byte, error) {
101106
return nil, err
102107
}
103108

104-
out, err := EvaluateExpressionPlain(expression, string(contentModified))
109+
out, err := EvaluateExpressionPlain(expression, string(contentModified), false)
105110
if err != nil {
106111
return nil, err
107112
}

0 commit comments

Comments
 (0)