-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcmd.go
More file actions
92 lines (84 loc) · 2.65 KB
/
cmd.go
File metadata and controls
92 lines (84 loc) · 2.65 KB
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
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"os"
"os/exec"
"strings"
)
func runCommand(dir string, args ...string) error {
cmd := exec.Command(args[0], args[1:]...)
cmd.Dir = dir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("command %q failed: %w", args, err)
}
return nil
}
func runContextCommand(ctx context.Context, dir string, args ...string) error {
cmd := exec.CommandContext(ctx, args[0], args[1:]...)
cmd.Dir = dir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("command %q failed: %w", args, err)
}
return nil
}
// runCommandWithOutput runs a command and unmarshal the output into a specified type.
// It is useful for commands that return JSON output.
// If command execution fails, we still attempt to parse the output as JSON,
// and return the parsed result along with the error.
//
// Example usage:
//
// type MyOutput struct {
// Field1 string `json:"field1"`
// Field2 int `json:"field2"`
// }
//
// output, err := runCommandWithOutput[MyOutput](workDir, "mycommand", "arg1", "arg2"); err != nil {
// fmt.Println("Error:", err)
// }
//
// Example output from mycommand might be:
//
// {"field1": "value1", "field2": 42}
//
// The output will be parsed into the MyOutput struct.
// Example return value:
//
// &MyOutput{Field1: "value1", Field2: 42}
func runCommandWithOutput[T any](dir string, args ...string) (result T, err error) {
cmd := exec.Command(args[0], args[1:]...)
cmd.Dir = dir
cmd.Env = os.Environ() // inherit environment variables
cmd.Stderr = os.Stderr
out, err := cmd.Output()
// check if the output is valid JSON, even if the command fails.
if !json.Valid(out) {
// if the output is not valid JSON, check if individual lines are
// valid JSON; if so, create a JSON array from the combined lines.
// this is useful for commands that output multiple JSON objects, one per line.
jsonLines := []string{"["}
for line := range bytes.Lines(out) {
if len(line) == 0 || !json.Valid(line) {
continue // skip empty lines or lines that are not valid JSON
}
jsonLines = append(jsonLines, string(line)+",")
}
lastLine := len(jsonLines) - 1
jsonLines[lastLine] = strings.TrimSuffix(jsonLines[lastLine], ",") // remove trailing comma from last line
jsonLines = append(jsonLines, "]")
out = []byte(strings.Join(jsonLines, "\n"))
}
// unmarshal the output into the specified type
if err := json.Unmarshal(out, &result); err != nil {
return result, fmt.Errorf("failed to unmarshal output: %w", err)
}
// return both the result and the command error, if any
return result, err
}