]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/go: allow users to specify required fields in JSON output
authorMichael Matloob <matloob@golang.org>
Wed, 26 Jan 2022 20:31:20 +0000 (15:31 -0500)
committerMichael Matloob <matloob@golang.org>
Wed, 2 Mar 2022 18:04:10 +0000 (18:04 +0000)
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>

src/cmd/go/alldocs.go
src/cmd/go/internal/list/list.go
src/cmd/go/testdata/script/list_json_fields.txt [new file with mode: 0644]

index 63e7900e02f473877912fdf4c3aa02c928d78554..2bd2fb6fbc9bede1a05c88dc6cd9ac034d850957 100644 (file)
 // 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
index 8be921193525cf78c6cd1296f62d13badf1ee432..9cebb934bfcb08439d5e92e07e35a04124995fa5 100644 (file)
@@ -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(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 (file)
index 0000000..58c9efa
--- /dev/null
@@ -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"
+       ]
+}