aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMichael Matloob <matloob@golang.org>2022-01-26 15:31:20 -0500
committerMichael Matloob <matloob@golang.org>2022-03-02 18:04:10 +0000
commitec4687f337465b719efdeef72b357fa0b05879bb (patch)
tree3d9dbe020902f8931c33057761efd2863a5c56dd /src
parent6c6a8fb702fcf3c2d23dc68976a831b2bdcad03e (diff)
downloadgo-ec4687f337465b719efdeef72b357fa0b05879bb.tar.xz
cmd/go: allow users to specify required fields in JSON output
For #29666 Change-Id: Ibae3d75bb2c19571c8d473cb47d6c4b3a880bba8 Reviewed-on: https://go-review.googlesource.com/c/go/+/381035 Trust: Michael Matloob <matloob@golang.org> Run-TryBot: Michael Matloob <matloob@golang.org> Reviewed-by: Bryan Mills <bcmills@google.com> TryBot-Result: Gopher Robot <gobot@golang.org>
Diffstat (limited to 'src')
-rw-r--r--src/cmd/go/alldocs.go5
-rw-r--r--src/cmd/go/internal/list/list.go98
-rw-r--r--src/cmd/go/testdata/script/list_json_fields.txt52
3 files changed, 137 insertions, 18 deletions
diff --git a/src/cmd/go/alldocs.go b/src/cmd/go/alldocs.go
index 63e7900e02..2bd2fb6fbc 100644
--- a/src/cmd/go/alldocs.go
+++ b/src/cmd/go/alldocs.go
@@ -869,7 +869,10 @@
// for the go/build package's Context type.
//
// The -json flag causes the package data to be printed in JSON format
-// instead of using the template format.
+// instead of using the template format. The JSON flag can optionally be
+// provided with a set of comma-separated required field names to be output.
+// If so, those required fields will always appear in JSON output, but
+// others may be omitted to save work in computing the JSON struct.
//
// The -compiled flag causes list to set CompiledGoFiles to the Go source
// files presented to the compiler. Typically this means that it repeats
diff --git a/src/cmd/go/internal/list/list.go b/src/cmd/go/internal/list/list.go
index 8be9211935..9cebb934bf 100644
--- a/src/cmd/go/internal/list/list.go
+++ b/src/cmd/go/internal/list/list.go
@@ -13,7 +13,9 @@ import (
"fmt"
"io"
"os"
+ "reflect"
"sort"
+ "strconv"
"strings"
"text/template"
@@ -157,7 +159,10 @@ For more information about the meaning of these fields see the documentation
for the go/build package's Context type.
The -json flag causes the package data to be printed in JSON format
-instead of using the template format.
+instead of using the template format. The JSON flag can optionally be
+provided with a set of comma-separated required field names to be output.
+If so, those required fields will always appear in JSON output, but
+others may be omitted to save work in computing the JSON struct.
The -compiled flag causes list to set CompiledGoFiles to the Go source
files presented to the compiler. Typically this means that it repeats
@@ -316,29 +321,79 @@ For more about modules, see https://golang.org/ref/mod.
func init() {
CmdList.Run = runList // break init cycle
work.AddBuildFlags(CmdList, work.DefaultBuildFlags)
+ CmdList.Flag.Var(&listJsonFields, "json", "")
}
var (
- listCompiled = CmdList.Flag.Bool("compiled", false, "")
- listDeps = CmdList.Flag.Bool("deps", false, "")
- listE = CmdList.Flag.Bool("e", false, "")
- listExport = CmdList.Flag.Bool("export", false, "")
- listFmt = CmdList.Flag.String("f", "", "")
- listFind = CmdList.Flag.Bool("find", false, "")
- listJson = CmdList.Flag.Bool("json", false, "")
- listM = CmdList.Flag.Bool("m", false, "")
- listRetracted = CmdList.Flag.Bool("retracted", false, "")
- listTest = CmdList.Flag.Bool("test", false, "")
- listU = CmdList.Flag.Bool("u", false, "")
- listVersions = CmdList.Flag.Bool("versions", false, "")
+ listCompiled = CmdList.Flag.Bool("compiled", false, "")
+ listDeps = CmdList.Flag.Bool("deps", false, "")
+ listE = CmdList.Flag.Bool("e", false, "")
+ listExport = CmdList.Flag.Bool("export", false, "")
+ listFmt = CmdList.Flag.String("f", "", "")
+ listFind = CmdList.Flag.Bool("find", false, "")
+ listJson bool
+ listJsonFields jsonFlag // If not empty, only output these fields.
+ listM = CmdList.Flag.Bool("m", false, "")
+ listRetracted = CmdList.Flag.Bool("retracted", false, "")
+ listTest = CmdList.Flag.Bool("test", false, "")
+ listU = CmdList.Flag.Bool("u", false, "")
+ listVersions = CmdList.Flag.Bool("versions", false, "")
)
+// A StringsFlag is a command-line flag that interprets its argument
+// as a space-separated list of possibly-quoted strings.
+type jsonFlag map[string]bool
+
+func (v *jsonFlag) Set(s string) error {
+ if v, err := strconv.ParseBool(s); err == nil {
+ listJson = v
+ return nil
+ }
+ listJson = true
+ if *v == nil {
+ *v = make(map[string]bool)
+ }
+ for _, f := range strings.Split(s, ",") {
+ (*v)[f] = true
+ }
+ return nil
+}
+
+func (v *jsonFlag) String() string {
+ var fields []string
+ for f := range *v {
+ fields = append(fields, f)
+ }
+ sort.Strings(fields)
+ return strings.Join(fields, ",")
+}
+
+func (v *jsonFlag) IsBoolFlag() bool {
+ return true
+}
+
+func (v *jsonFlag) needAll() bool {
+ return len(*v) == 0
+}
+
+func (v *jsonFlag) needAny(fields ...string) bool {
+ if v.needAll() {
+ return true
+ }
+ for _, f := range fields {
+ if (*v)[f] {
+ return true
+ }
+ }
+ return false
+}
+
var nl = []byte{'\n'}
func runList(ctx context.Context, cmd *base.Command, args []string) {
modload.InitWorkfile()
- if *listFmt != "" && *listJson == true {
+ if *listFmt != "" && listJson == true {
base.Fatalf("go list -f cannot be used with -json")
}
@@ -357,9 +412,18 @@ func runList(ctx context.Context, cmd *base.Command, args []string) {
}
}
- var do func(any)
- if *listJson {
+ var do func(x any)
+ if listJson {
do = func(x any) {
+ if !listJsonFields.needAll() {
+ v := reflect.ValueOf(x).Elem() // do is always called with a non-nil pointer.
+ // Clear all non-requested fields.
+ for i := 0; i < v.NumField(); i++ {
+ if !listJsonFields.needAny(v.Type().Field(i).Name) {
+ v.Field(i).Set(reflect.Zero(v.Type().Field(i).Type))
+ }
+ }
+ }
b, err := json.MarshalIndent(x, "", "\t")
if err != nil {
out.Flush()
@@ -589,7 +653,7 @@ func runList(ctx context.Context, cmd *base.Command, args []string) {
}
// Do we need to run a build to gather information?
- needStale := *listJson || strings.Contains(*listFmt, ".Stale")
+ needStale := (listJson && listJsonFields.needAny("Stale", "StaleReason")) || strings.Contains(*listFmt, ".Stale")
if needStale || *listExport || *listCompiled {
var b work.Builder
b.Init()
diff --git a/src/cmd/go/testdata/script/list_json_fields.txt b/src/cmd/go/testdata/script/list_json_fields.txt
new file mode 100644
index 0000000000..58c9efa162
--- /dev/null
+++ b/src/cmd/go/testdata/script/list_json_fields.txt
@@ -0,0 +1,52 @@
+# Test using -json flag to specify specific fields.
+
+# Test -json produces "full" output by looking for multiple fields present.
+go list -json .
+stdout '"Name": "a"'
+stdout '"Stale": true'
+# Same thing for -json=true
+go list -json=true .
+stdout '"Name": "a"'
+stdout '"Stale": true'
+
+# Test -json=false produces non-json output.
+go list -json=false
+cmp stdout want-non-json.txt
+
+# Test -json=<field> keeps only that field.
+go list -json=Name
+cmp stdout want-json-name.txt
+
+# Test -json=<field> with multiple fields.
+go list -json=ImportPath,Name,GoFiles,Imports
+cmp stdout want-json-multiple.txt
+
+-- go.mod --
+module example.com/a
+
+go 1.18
+-- a.go --
+package a
+
+import "fmt"
+
+func F() {
+ fmt.Println("hey there")
+}
+-- want-non-json.txt --
+example.com/a
+-- want-json-name.txt --
+{
+ "Name": "a"
+}
+-- want-json-multiple.txt --
+{
+ "ImportPath": "example.com/a",
+ "Name": "a",
+ "GoFiles": [
+ "a.go"
+ ],
+ "Imports": [
+ "fmt"
+ ]
+}