From fe60e7f80826838968507a680b72daf8bbb9ba42 Mon Sep 17 00:00:00 2001 From: Jesse Tatasciore Date: Mon, 24 Jan 2022 10:44:15 -0500 Subject: [PATCH] refactor: Move aquery and cquery to be there own verbs under pkg/aspect --- cmd/aspect/aquery/BUILD.bazel | 2 +- cmd/aspect/aquery/aquery.go | 2 +- cmd/aspect/cquery/BUILD.bazel | 2 +- cmd/aspect/cquery/cquery.go | 2 +- cmd/aspect/query/BUILD.bazel | 2 +- cmd/aspect/query/query.go | 2 +- pkg/aspect/{query => }/aquery/BUILD.bazel | 8 +- pkg/aspect/{query => }/aquery/aquery.go | 2 +- pkg/aspect/{query => }/aquery/aquery_test.go | 13 +- pkg/aspect/{query => }/cquery/BUILD.bazel | 8 +- pkg/aspect/{query => }/cquery/cquery.go | 2 +- pkg/aspect/{query => }/cquery/cquery_test.go | 6 +- pkg/aspect/query/BUILD.bazel | 22 +- pkg/aspect/query/query.go | 239 ++++++------------ pkg/aspect/query/query/BUILD.bazel | 31 --- pkg/aspect/query/query/query.go | 123 --------- pkg/aspect/query/{query => }/query_test.go | 6 +- pkg/aspect/query/shared/BUILD.bazel | 15 ++ .../query/{ => shared}/mock/BUILD.bazel | 4 +- pkg/aspect/query/{ => shared}/mock/doc.go | 0 pkg/aspect/query/shared/query.go | 220 ++++++++++++++++ 21 files changed, 355 insertions(+), 356 deletions(-) rename pkg/aspect/{query => }/aquery/BUILD.bazel (76%) rename pkg/aspect/{query => }/aquery/aquery.go (97%) rename pkg/aspect/{query => }/aquery/aquery_test.go (94%) rename pkg/aspect/{query => }/cquery/BUILD.bazel (76%) rename pkg/aspect/{query => }/cquery/cquery.go (97%) rename pkg/aspect/{query => }/cquery/cquery_test.go (97%) delete mode 100644 pkg/aspect/query/query/BUILD.bazel delete mode 100644 pkg/aspect/query/query/query.go rename pkg/aspect/query/{query => }/query_test.go (98%) create mode 100644 pkg/aspect/query/shared/BUILD.bazel rename pkg/aspect/query/{ => shared}/mock/BUILD.bazel (84%) rename pkg/aspect/query/{ => shared}/mock/doc.go (100%) create mode 100644 pkg/aspect/query/shared/query.go diff --git a/cmd/aspect/aquery/BUILD.bazel b/cmd/aspect/aquery/BUILD.bazel index ddb01af1d..f01c9713f 100644 --- a/cmd/aspect/aquery/BUILD.bazel +++ b/cmd/aspect/aquery/BUILD.bazel @@ -6,7 +6,7 @@ go_library( importpath = "aspect.build/cli/cmd/aspect/aquery", visibility = ["//visibility:public"], deps = [ - "//pkg/aspect/query/aquery", + "//pkg/aspect/aquery", "//pkg/bazel", "//pkg/ioutils", "@com_github_spf13_cobra//:cobra", diff --git a/cmd/aspect/aquery/aquery.go b/cmd/aspect/aquery/aquery.go index 7695e0506..0da3b0758 100644 --- a/cmd/aspect/aquery/aquery.go +++ b/cmd/aspect/aquery/aquery.go @@ -9,7 +9,7 @@ package aquery import ( "github.com/spf13/cobra" - "aspect.build/cli/pkg/aspect/query/aquery" + "aspect.build/cli/pkg/aspect/aquery" "aspect.build/cli/pkg/bazel" "aspect.build/cli/pkg/ioutils" ) diff --git a/cmd/aspect/cquery/BUILD.bazel b/cmd/aspect/cquery/BUILD.bazel index d3475486d..9c17d88ee 100644 --- a/cmd/aspect/cquery/BUILD.bazel +++ b/cmd/aspect/cquery/BUILD.bazel @@ -6,7 +6,7 @@ go_library( importpath = "aspect.build/cli/cmd/aspect/cquery", visibility = ["//visibility:public"], deps = [ - "//pkg/aspect/query/cquery", + "//pkg/aspect/cquery", "//pkg/bazel", "//pkg/ioutils", "@com_github_spf13_cobra//:cobra", diff --git a/cmd/aspect/cquery/cquery.go b/cmd/aspect/cquery/cquery.go index dd5232bbf..204683098 100644 --- a/cmd/aspect/cquery/cquery.go +++ b/cmd/aspect/cquery/cquery.go @@ -9,7 +9,7 @@ package cquery import ( "github.com/spf13/cobra" - "aspect.build/cli/pkg/aspect/query/cquery" + "aspect.build/cli/pkg/aspect/cquery" "aspect.build/cli/pkg/bazel" "aspect.build/cli/pkg/ioutils" ) diff --git a/cmd/aspect/query/BUILD.bazel b/cmd/aspect/query/BUILD.bazel index 9c9d54272..e09b9669a 100644 --- a/cmd/aspect/query/BUILD.bazel +++ b/cmd/aspect/query/BUILD.bazel @@ -6,7 +6,7 @@ go_library( importpath = "aspect.build/cli/cmd/aspect/query", visibility = ["//visibility:public"], deps = [ - "//pkg/aspect/query/query", + "//pkg/aspect/query", "//pkg/bazel", "//pkg/ioutils", "@com_github_spf13_cobra//:cobra", diff --git a/cmd/aspect/query/query.go b/cmd/aspect/query/query.go index f18d53b5a..472972c26 100644 --- a/cmd/aspect/query/query.go +++ b/cmd/aspect/query/query.go @@ -9,7 +9,7 @@ package query import ( "github.com/spf13/cobra" - "aspect.build/cli/pkg/aspect/query/query" + "aspect.build/cli/pkg/aspect/query" "aspect.build/cli/pkg/bazel" "aspect.build/cli/pkg/ioutils" ) diff --git a/pkg/aspect/query/aquery/BUILD.bazel b/pkg/aspect/aquery/BUILD.bazel similarity index 76% rename from pkg/aspect/query/aquery/BUILD.bazel rename to pkg/aspect/aquery/BUILD.bazel index 45a4c3203..1af7243af 100644 --- a/pkg/aspect/query/aquery/BUILD.bazel +++ b/pkg/aspect/aquery/BUILD.bazel @@ -3,10 +3,10 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "aquery", srcs = ["aquery.go"], - importpath = "aspect.build/cli/pkg/aspect/query/aquery", + importpath = "aspect.build/cli/pkg/aspect/aquery", visibility = ["//visibility:public"], deps = [ - "//pkg/aspect/query", + "//pkg/aspect/query/shared", "//pkg/bazel", "//pkg/ioutils", "@com_github_spf13_cobra//:cobra", @@ -18,8 +18,8 @@ go_test( srcs = ["aquery_test.go"], deps = [ ":aquery", - "//pkg/aspect/query", - "//pkg/aspect/query/mock", + "//pkg/aspect/query/shared", + "//pkg/aspect/query/shared/mock", "//pkg/bazel/mock", "//pkg/ioutils", "@com_github_golang_mock//gomock", diff --git a/pkg/aspect/query/aquery/aquery.go b/pkg/aspect/aquery/aquery.go similarity index 97% rename from pkg/aspect/query/aquery/aquery.go rename to pkg/aspect/aquery/aquery.go index 8bd1417f3..987cd85c8 100644 --- a/pkg/aspect/query/aquery/aquery.go +++ b/pkg/aspect/aquery/aquery.go @@ -9,7 +9,7 @@ package aquery import ( "github.com/spf13/cobra" - shared "aspect.build/cli/pkg/aspect/query" + "aspect.build/cli/pkg/aspect/query/shared" "aspect.build/cli/pkg/bazel" "aspect.build/cli/pkg/ioutils" ) diff --git a/pkg/aspect/query/aquery/aquery_test.go b/pkg/aspect/aquery/aquery_test.go similarity index 94% rename from pkg/aspect/query/aquery/aquery_test.go rename to pkg/aspect/aquery/aquery_test.go index 5b56d46b2..687b2f140 100644 --- a/pkg/aspect/query/aquery/aquery_test.go +++ b/pkg/aspect/aquery/aquery_test.go @@ -15,10 +15,9 @@ import ( . "github.com/onsi/gomega" "github.com/spf13/cobra" - "aspect.build/cli/pkg/aspect/query" - shared "aspect.build/cli/pkg/aspect/query" - "aspect.build/cli/pkg/aspect/query/aquery" - query_mock "aspect.build/cli/pkg/aspect/query/mock" + "aspect.build/cli/pkg/aspect/aquery" + "aspect.build/cli/pkg/aspect/query/shared" + query_mock "aspect.build/cli/pkg/aspect/query/shared/mock" bazel_mock "aspect.build/cli/pkg/bazel/mock" "aspect.build/cli/pkg/ioutils" ) @@ -185,15 +184,15 @@ func TestQuery(t *testing.T) { Streams: streams, Bzl: spawner, IsInteractive: true, - Prompt: func(label string) query.PromptRunner { + Prompt: func(label string) shared.PromptRunner { g.Expect(strings.Contains(label, "targettwo") || strings.Contains(label, "dependencytwo")).To(Equal(true)) return promptRunner }, - Select: func(presetNames []string) query.SelectRunner { + Select: func(presetNames []string) shared.SelectRunner { return selectRunner }, } - q.Presets = []*query.PresetQuery{ + q.Presets = []*shared.PresetQuery{ { Name: "why1", Description: "Determine why a target depends on another", diff --git a/pkg/aspect/query/cquery/BUILD.bazel b/pkg/aspect/cquery/BUILD.bazel similarity index 76% rename from pkg/aspect/query/cquery/BUILD.bazel rename to pkg/aspect/cquery/BUILD.bazel index 17d6ea969..29e02d6ae 100644 --- a/pkg/aspect/query/cquery/BUILD.bazel +++ b/pkg/aspect/cquery/BUILD.bazel @@ -3,10 +3,10 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "cquery", srcs = ["cquery.go"], - importpath = "aspect.build/cli/pkg/aspect/query/cquery", + importpath = "aspect.build/cli/pkg/aspect/cquery", visibility = ["//visibility:public"], deps = [ - "//pkg/aspect/query", + "//pkg/aspect/query/shared", "//pkg/bazel", "//pkg/ioutils", "@com_github_spf13_cobra//:cobra", @@ -18,8 +18,8 @@ go_test( srcs = ["cquery_test.go"], deps = [ ":cquery", - "//pkg/aspect/query", - "//pkg/aspect/query/mock", + "//pkg/aspect/query/shared", + "//pkg/aspect/query/shared/mock", "//pkg/bazel/mock", "//pkg/ioutils", "@com_github_golang_mock//gomock", diff --git a/pkg/aspect/query/cquery/cquery.go b/pkg/aspect/cquery/cquery.go similarity index 97% rename from pkg/aspect/query/cquery/cquery.go rename to pkg/aspect/cquery/cquery.go index 03d33b0f6..e8e8a3942 100644 --- a/pkg/aspect/query/cquery/cquery.go +++ b/pkg/aspect/cquery/cquery.go @@ -9,7 +9,7 @@ package cquery import ( "github.com/spf13/cobra" - shared "aspect.build/cli/pkg/aspect/query" + "aspect.build/cli/pkg/aspect/query/shared" "aspect.build/cli/pkg/bazel" "aspect.build/cli/pkg/ioutils" ) diff --git a/pkg/aspect/query/cquery/cquery_test.go b/pkg/aspect/cquery/cquery_test.go similarity index 97% rename from pkg/aspect/query/cquery/cquery_test.go rename to pkg/aspect/cquery/cquery_test.go index a0c4a5991..a38c0dea6 100644 --- a/pkg/aspect/query/cquery/cquery_test.go +++ b/pkg/aspect/cquery/cquery_test.go @@ -15,9 +15,9 @@ import ( . "github.com/onsi/gomega" "github.com/spf13/cobra" - shared "aspect.build/cli/pkg/aspect/query" - "aspect.build/cli/pkg/aspect/query/cquery" - query_mock "aspect.build/cli/pkg/aspect/query/mock" + "aspect.build/cli/pkg/aspect/cquery" + "aspect.build/cli/pkg/aspect/query/shared" + query_mock "aspect.build/cli/pkg/aspect/query/shared/mock" bazel_mock "aspect.build/cli/pkg/bazel/mock" "aspect.build/cli/pkg/ioutils" ) diff --git a/pkg/aspect/query/BUILD.bazel b/pkg/aspect/query/BUILD.bazel index 707e3ee01..de972667a 100644 --- a/pkg/aspect/query/BUILD.bazel +++ b/pkg/aspect/query/BUILD.bazel @@ -1,4 +1,4 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "query", @@ -6,10 +6,26 @@ go_library( importpath = "aspect.build/cli/pkg/aspect/query", visibility = ["//visibility:public"], deps = [ - "//pkg/aspecterrors", + "//pkg/aspect/query/shared", "//pkg/bazel", "//pkg/ioutils", - "@com_github_manifoldco_promptui//:promptui", "@com_github_spf13_cobra//:cobra", + "@com_github_spf13_viper//:viper", + ], +) + +go_test( + name = "query_test", + srcs = ["query_test.go"], + deps = [ + ":query", + "//pkg/aspect/query/shared", + "//pkg/aspect/query/shared/mock", + "//pkg/bazel/mock", + "//pkg/ioutils", + "@com_github_golang_mock//gomock", + "@com_github_onsi_gomega//:gomega", + "@com_github_spf13_cobra//:cobra", + "@com_github_spf13_viper//:viper", ], ) diff --git a/pkg/aspect/query/query.go b/pkg/aspect/query/query.go index 40ced0ccd..011df19d4 100644 --- a/pkg/aspect/query/query.go +++ b/pkg/aspect/query/query.go @@ -6,215 +6,118 @@ Not licensed for re-use. package query -/* -This package is meant to contain code that will be shared between the 3 query verbs. query, aquery and cquery -*/ - import ( "fmt" - "regexp" - "strings" - "github.com/manifoldco/promptui" "github.com/spf13/cobra" + "github.com/spf13/viper" - "aspect.build/cli/pkg/aspecterrors" + "aspect.build/cli/pkg/aspect/query/shared" "aspect.build/cli/pkg/bazel" "aspect.build/cli/pkg/ioutils" ) -type PresetQuery struct { - Name string - Description string - Query string - Verb string -} +const ( + useCQuery = "query.cquery.use" + useCQueryInquired = "query.cquery.inquired" + allowAllQueries = "query.all.allow" + allowAllQueriesInquired = "query.all.inquired" +) -type PromptRunner interface { - Run() (string, error) -} +type Query struct { + ioutils.Streams -func Prompt(label string) PromptRunner { - return &promptui.Prompt{ - Label: label, - } -} + Bzl bazel.Bazel + IsInteractive bool -type ConfirmationRunner interface { - Run() (string, error) -} + Presets []*shared.PresetQuery + Prefs viper.Viper -func Confirmation(question string) ConfirmationRunner { - return &promptui.Prompt{ - Label: question, - IsConfirm: true, - } + Prompt func(label string) shared.PromptRunner + Confirmation func(question string) shared.ConfirmationRunner + Select func(presetNames []string) shared.SelectRunner } -type SelectRunner interface { - Run() (int, string, error) -} - -func Select(presetNames []string) SelectRunner { - return &promptui.Select{ - Label: "Select a preset query", - Items: presetNames, +func New(streams ioutils.Streams, bzl bazel.Bazel, isInteractive bool) *Query { + // will potentially be updated during Run() if the user requests that query also show aquery and cquery predefined queries + presets := shared.GetPrecannedQueries("query") + + return &Query{ + Streams: streams, + Bzl: bzl, + IsInteractive: isInteractive, + Presets: presets, + Prompt: shared.Prompt, + Select: shared.Select, + Confirmation: shared.Confirmation, + Prefs: *viper.GetViper(), } } -func GetPrecannedQueries(verb string) []*PresetQuery { - // TODO: Queries should be loadable from the plugin config - // https://github.com/aspect-build/aspect-cli/issues/98 - presets := []*PresetQuery{ - { - Name: "why", - Description: "Determine why targetA depends on targetB", - Query: "somepath(?targetA, ?targetB)", - Verb: "query", - }, - { - Name: "deps", - Description: "Get the deps of a target", - Query: "deps(?target)", - Verb: "query", - }, - { - Name: "adeps", - Description: "Get the deps of a target", - Query: "deps(?target)", - Verb: "aquery", - }, - { - Name: "cdeps", - Description: "Get the deps of a target", - Query: "deps(?target)", - Verb: "cquery", - }, +func (q *Query) Run(cmd *cobra.Command, args []string) error { + err := q.checkConfig( + allowAllQueries, + allowAllQueriesInquired, + "Include predefined aquery's and cquery's when calling query", + ) + if err != nil { + return err } - switch verb { - case "query": - return filterPrecannedQueries("query", presets) - case "cquery": - return filterPrecannedQueries("cquery", presets) - case "aquery": - return filterPrecannedQueries("aquery", presets) + err = q.checkConfig( + useCQuery, + useCQueryInquired, + "Use cquery instead of query", + ) + if err != nil { + return err } - return presets -} + verb := "query" -func filterPrecannedQueries(verb string, presets []*PresetQuery) []*PresetQuery { - filteredPresets := []*PresetQuery{} - for _, presetQuery := range presets { - - if presetQuery.Verb == verb { - filteredPresets = append(filteredPresets, presetQuery) - } + if q.Prefs.GetBool(useCQuery) { + verb = "cquery" } - return filteredPresets -} - -func ProcessQueries(presets []*PresetQuery) (map[string]*PresetQuery, []string, error) { - processedPresets := make(map[string]*PresetQuery) - presetNames := make([]string, len(presets)) - for i, p := range presets { - if _, exists := processedPresets[p.Name]; exists { - err := fmt.Errorf("duplicated preset query name %q", p.Name) - return processedPresets, presetNames, err - } - processedPresets[p.Name] = p - presetNames[i] = fmt.Sprintf("%s: %s", p.Name, p.Description) + if q.Prefs.GetBool(allowAllQueries) { + q.Presets = shared.GetPrecannedQueries("") } - return processedPresets, presetNames, nil -} - -func RunQuery(bzl bazel.Bazel, verb string, query string) error { - bazelCmd := []string{ - verb, - query, + presets, presetNames, err := shared.ProcessQueries(q.Presets) + if err != nil { + return shared.GetPrettyError(cmd, err) } - if exitCode, err := bzl.Spawn(bazelCmd); exitCode != 0 { - err = &aspecterrors.ExitError{ - Err: err, - ExitCode: exitCode, - } - return fmt.Errorf("failed to run %q %q: %w", verb, query, err) - } + verb, query, runReplacements, err := shared.SelectQuery(verb, presets, q.Presets, presetNames, q.Streams, args, q.Select) - return nil -} - -func ReplacePlaceholders(query string, args []string, p func(label string) PromptRunner) (string, error) { - placeholders := regexp.MustCompile(`(\?[a-zA-Z]*)`).FindAllString(query, -1) - - if len(placeholders) == len(args)-1 { - for i, placeholder := range placeholders { - // todo.... Print out targetA was set to //foo and targetB was set to //bar - query = strings.ReplaceAll(query, placeholder, args[i+1]) - } - } else if len(placeholders) > 0 { - for _, placeholder := range placeholders { - label := fmt.Sprintf("Value for '%s'", strings.TrimPrefix(placeholder, "?")) - prompt := p(label) - val, err := prompt.Run() + if err != nil { + return shared.GetPrettyError(cmd, err) + } - if err != nil { - return "", err - } + if runReplacements { + query, err = shared.ReplacePlaceholders(query, args, q.Prompt) - query = strings.ReplaceAll(query, placeholder, val) + if err != nil { + return shared.GetPrettyError(cmd, err) } } - return query, nil + return shared.RunQuery(q.Bzl, verb, query) } -func SelectQuery( - verb string, - processedPresets map[string]*PresetQuery, - rawPresets []*PresetQuery, - presetNames []string, - streams ioutils.Streams, - args []string, - s func(presetNames []string) SelectRunner, -) (string, string, bool, error) { +func (q *Query) checkConfig(baseUseKey string, baseInquiredKey string, question string) error { + if !q.Prefs.GetBool(baseInquiredKey) { + q.Prefs.Set(baseInquiredKey, true) - var preset *PresetQuery - if len(args) == 0 { - selectQueryPrompt := s(presetNames) + // if user types in y or Y then err will be nil. Any other input will not result in nil + _, someErr := q.Confirmation(question).Run() - i, _, err := selectQueryPrompt.Run() + q.Prefs.Set(baseUseKey, someErr == nil) - if err != nil { - return verb, "", false, err - } - - preset = rawPresets[i] - } else { - maybeQueryOrPreset := args[0] - if value, ok := processedPresets[maybeQueryOrPreset]; ok { - // Treat this as the name of the preset query, so don't prompt for it. - fmt.Fprintf(streams.Stdout, "Preset query \"%s\" selected\n", value.Name) - fmt.Fprintf(streams.Stdout, "%s: %s\n", value.Name, value.Description) - preset = value - } else { - // Treat this as a raw query expression. - return verb, maybeQueryOrPreset, false, nil + if err := q.Prefs.WriteConfig(); err != nil { + return fmt.Errorf("failed to update config file: %w", err) } } - if preset == nil { - err := fmt.Errorf("unable to determine preset query") - return verb, "", false, err - } - - return preset.Verb, preset.Query, true, nil -} - -func GetPrettyError(cmd *cobra.Command, err error) error { - return fmt.Errorf("failed to run 'aspect %s': %w", cmd.Use, err) + return nil } diff --git a/pkg/aspect/query/query/BUILD.bazel b/pkg/aspect/query/query/BUILD.bazel deleted file mode 100644 index 4cdfa119b..000000000 --- a/pkg/aspect/query/query/BUILD.bazel +++ /dev/null @@ -1,31 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") - -go_library( - name = "query", - srcs = ["query.go"], - importpath = "aspect.build/cli/pkg/aspect/query/query", - visibility = ["//visibility:public"], - deps = [ - "//pkg/aspect/query", - "//pkg/bazel", - "//pkg/ioutils", - "@com_github_spf13_cobra//:cobra", - "@com_github_spf13_viper//:viper", - ], -) - -go_test( - name = "query_test", - srcs = ["query_test.go"], - deps = [ - ":query", - "//pkg/aspect/query", - "//pkg/aspect/query/mock", - "//pkg/bazel/mock", - "//pkg/ioutils", - "@com_github_golang_mock//gomock", - "@com_github_onsi_gomega//:gomega", - "@com_github_spf13_cobra//:cobra", - "@com_github_spf13_viper//:viper", - ], -) diff --git a/pkg/aspect/query/query/query.go b/pkg/aspect/query/query/query.go deleted file mode 100644 index 13928e554..000000000 --- a/pkg/aspect/query/query/query.go +++ /dev/null @@ -1,123 +0,0 @@ -/* -Copyright © 2021 Aspect Build Systems Inc - -Not licensed for re-use. -*/ - -package query - -import ( - "fmt" - - "github.com/spf13/cobra" - "github.com/spf13/viper" - - shared "aspect.build/cli/pkg/aspect/query" - "aspect.build/cli/pkg/bazel" - "aspect.build/cli/pkg/ioutils" -) - -const ( - useCQuery = "query.cquery.use" - useCQueryInquired = "query.cquery.inquired" - allowAllQueries = "query.all.allow" - allowAllQueriesInquired = "query.all.inquired" -) - -type Query struct { - ioutils.Streams - - Bzl bazel.Bazel - IsInteractive bool - - Presets []*shared.PresetQuery - Prefs viper.Viper - - Prompt func(label string) shared.PromptRunner - Confirmation func(question string) shared.ConfirmationRunner - Select func(presetNames []string) shared.SelectRunner -} - -func New(streams ioutils.Streams, bzl bazel.Bazel, isInteractive bool) *Query { - // will potentially be updated during Run() if the user requests that query also show aquery and cquery predefined queries - presets := shared.GetPrecannedQueries("query") - - return &Query{ - Streams: streams, - Bzl: bzl, - IsInteractive: isInteractive, - Presets: presets, - Prompt: shared.Prompt, - Select: shared.Select, - Confirmation: shared.Confirmation, - Prefs: *viper.GetViper(), - } -} - -func (q *Query) Run(cmd *cobra.Command, args []string) error { - err := q.checkConfig( - allowAllQueries, - allowAllQueriesInquired, - "Include predefined aquery's and cquery's when calling query", - ) - if err != nil { - return err - } - - err = q.checkConfig( - useCQuery, - useCQueryInquired, - "Use cquery instead of query", - ) - if err != nil { - return err - } - - verb := "query" - - if q.Prefs.GetBool(useCQuery) { - verb = "cquery" - } - - if q.Prefs.GetBool(allowAllQueries) { - q.Presets = shared.GetPrecannedQueries("") - } - - presets, presetNames, err := shared.ProcessQueries(q.Presets) - if err != nil { - return shared.GetPrettyError(cmd, err) - } - - verb, query, runReplacements, err := shared.SelectQuery(verb, presets, q.Presets, presetNames, q.Streams, args, q.Select) - - if err != nil { - return shared.GetPrettyError(cmd, err) - } - - if runReplacements { - query, err = shared.ReplacePlaceholders(query, args, q.Prompt) - - if err != nil { - return shared.GetPrettyError(cmd, err) - } - } - - return shared.RunQuery(q.Bzl, verb, query) -} - -func (q *Query) checkConfig(baseUseKey string, baseInquiredKey string, question string) error { - if !q.Prefs.GetBool(baseInquiredKey) { - q.Prefs.Set(baseInquiredKey, true) - - // if user types in y or Y then err will be nil. Any other input will not result in nil - _, someErr := q.Confirmation(question).Run() - - q.Prefs.Set(baseUseKey, someErr == nil) - - if err := q.Prefs.WriteConfig(); err != nil { - return fmt.Errorf("failed to update config file: %w", err) - } - } - - return nil -} diff --git a/pkg/aspect/query/query/query_test.go b/pkg/aspect/query/query_test.go similarity index 98% rename from pkg/aspect/query/query/query_test.go rename to pkg/aspect/query/query_test.go index ac7435fa5..7fda72847 100644 --- a/pkg/aspect/query/query/query_test.go +++ b/pkg/aspect/query/query_test.go @@ -17,9 +17,9 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - shared "aspect.build/cli/pkg/aspect/query" - query_mock "aspect.build/cli/pkg/aspect/query/mock" - "aspect.build/cli/pkg/aspect/query/query" + "aspect.build/cli/pkg/aspect/query" + "aspect.build/cli/pkg/aspect/query/shared" + query_mock "aspect.build/cli/pkg/aspect/query/shared/mock" bazel_mock "aspect.build/cli/pkg/bazel/mock" "aspect.build/cli/pkg/ioutils" ) diff --git a/pkg/aspect/query/shared/BUILD.bazel b/pkg/aspect/query/shared/BUILD.bazel new file mode 100644 index 000000000..c5c8b7de3 --- /dev/null +++ b/pkg/aspect/query/shared/BUILD.bazel @@ -0,0 +1,15 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "shared", + srcs = ["query.go"], + importpath = "aspect.build/cli/pkg/aspect/query/shared", + visibility = ["//visibility:public"], + deps = [ + "//pkg/aspecterrors", + "//pkg/bazel", + "//pkg/ioutils", + "@com_github_manifoldco_promptui//:promptui", + "@com_github_spf13_cobra//:cobra", + ], +) diff --git a/pkg/aspect/query/mock/BUILD.bazel b/pkg/aspect/query/shared/mock/BUILD.bazel similarity index 84% rename from pkg/aspect/query/mock/BUILD.bazel rename to pkg/aspect/query/shared/mock/BUILD.bazel index 15c61e58e..fdd646558 100644 --- a/pkg/aspect/query/mock/BUILD.bazel +++ b/pkg/aspect/query/shared/mock/BUILD.bazel @@ -11,7 +11,7 @@ gomock( "PromptRunner", "SelectRunner", ], - library = "//pkg/aspect/query", + library = "//pkg/aspect/query/shared", package = "mock", visibility = ["//visibility:private"], ) @@ -22,7 +22,7 @@ go_library( "doc.go", ":mock_query_source", # keep ], - importpath = "aspect.build/cli/pkg/aspect/query/mock", + importpath = "aspect.build/cli/pkg/aspect/query/shared/mock", visibility = ["//:__subpackages__"], deps = [ "@com_github_golang_mock//gomock", # keep diff --git a/pkg/aspect/query/mock/doc.go b/pkg/aspect/query/shared/mock/doc.go similarity index 100% rename from pkg/aspect/query/mock/doc.go rename to pkg/aspect/query/shared/mock/doc.go diff --git a/pkg/aspect/query/shared/query.go b/pkg/aspect/query/shared/query.go new file mode 100644 index 000000000..df2f965b5 --- /dev/null +++ b/pkg/aspect/query/shared/query.go @@ -0,0 +1,220 @@ +/* +Copyright © 2021 Aspect Build Systems Inc + +Not licensed for re-use. +*/ + +package shared + +/* +This package is meant to contain code that will be shared between the 3 query verbs. query, aquery and cquery +*/ + +import ( + "fmt" + "regexp" + "strings" + + "github.com/manifoldco/promptui" + "github.com/spf13/cobra" + + "aspect.build/cli/pkg/aspecterrors" + "aspect.build/cli/pkg/bazel" + "aspect.build/cli/pkg/ioutils" +) + +type PresetQuery struct { + Name string + Description string + Query string + Verb string +} + +type PromptRunner interface { + Run() (string, error) +} + +func Prompt(label string) PromptRunner { + return &promptui.Prompt{ + Label: label, + } +} + +type ConfirmationRunner interface { + Run() (string, error) +} + +func Confirmation(question string) ConfirmationRunner { + return &promptui.Prompt{ + Label: question, + IsConfirm: true, + } +} + +type SelectRunner interface { + Run() (int, string, error) +} + +func Select(presetNames []string) SelectRunner { + return &promptui.Select{ + Label: "Select a preset query", + Items: presetNames, + } +} + +func GetPrecannedQueries(verb string) []*PresetQuery { + // TODO: Queries should be loadable from the plugin config + // https://github.com/aspect-build/aspect-cli/issues/98 + presets := []*PresetQuery{ + { + Name: "why", + Description: "Determine why targetA depends on targetB", + Query: "somepath(?targetA, ?targetB)", + Verb: "query", + }, + { + Name: "deps", + Description: "Get the deps of a target", + Query: "deps(?target)", + Verb: "query", + }, + { + Name: "adeps", + Description: "Get the deps of a target", + Query: "deps(?target)", + Verb: "aquery", + }, + { + Name: "cdeps", + Description: "Get the deps of a target", + Query: "deps(?target)", + Verb: "cquery", + }, + } + + switch verb { + case "query": + return filterPrecannedQueries("query", presets) + case "cquery": + return filterPrecannedQueries("cquery", presets) + case "aquery": + return filterPrecannedQueries("aquery", presets) + } + + return presets +} + +func filterPrecannedQueries(verb string, presets []*PresetQuery) []*PresetQuery { + filteredPresets := []*PresetQuery{} + for _, presetQuery := range presets { + + if presetQuery.Verb == verb { + filteredPresets = append(filteredPresets, presetQuery) + } + } + + return filteredPresets +} + +func ProcessQueries(presets []*PresetQuery) (map[string]*PresetQuery, []string, error) { + processedPresets := make(map[string]*PresetQuery) + presetNames := make([]string, len(presets)) + for i, p := range presets { + if _, exists := processedPresets[p.Name]; exists { + err := fmt.Errorf("duplicated preset query name %q", p.Name) + return processedPresets, presetNames, err + } + processedPresets[p.Name] = p + presetNames[i] = fmt.Sprintf("%s: %s", p.Name, p.Description) + } + + return processedPresets, presetNames, nil +} + +func RunQuery(bzl bazel.Bazel, verb string, query string) error { + bazelCmd := []string{ + verb, + query, + } + + if exitCode, err := bzl.Spawn(bazelCmd); exitCode != 0 { + err = &aspecterrors.ExitError{ + Err: err, + ExitCode: exitCode, + } + return fmt.Errorf("failed to run %q %q: %w", verb, query, err) + } + + return nil +} + +func ReplacePlaceholders(query string, args []string, p func(label string) PromptRunner) (string, error) { + placeholders := regexp.MustCompile(`(\?[a-zA-Z]*)`).FindAllString(query, -1) + + if len(placeholders) == len(args)-1 { + for i, placeholder := range placeholders { + // todo.... Print out targetA was set to //foo and targetB was set to //bar + query = strings.ReplaceAll(query, placeholder, args[i+1]) + } + } else if len(placeholders) > 0 { + for _, placeholder := range placeholders { + label := fmt.Sprintf("Value for '%s'", strings.TrimPrefix(placeholder, "?")) + prompt := p(label) + val, err := prompt.Run() + + if err != nil { + return "", err + } + + query = strings.ReplaceAll(query, placeholder, val) + } + } + + return query, nil +} + +func SelectQuery( + verb string, + processedPresets map[string]*PresetQuery, + rawPresets []*PresetQuery, + presetNames []string, + streams ioutils.Streams, + args []string, + s func(presetNames []string) SelectRunner, +) (string, string, bool, error) { + + var preset *PresetQuery + if len(args) == 0 { + selectQueryPrompt := s(presetNames) + + i, _, err := selectQueryPrompt.Run() + + if err != nil { + return verb, "", false, err + } + + preset = rawPresets[i] + } else { + maybeQueryOrPreset := args[0] + if value, ok := processedPresets[maybeQueryOrPreset]; ok { + // Treat this as the name of the preset query, so don't prompt for it. + fmt.Fprintf(streams.Stdout, "Preset query \"%s\" selected\n", value.Name) + fmt.Fprintf(streams.Stdout, "%s: %s\n", value.Name, value.Description) + preset = value + } else { + // Treat this as a raw query expression. + return verb, maybeQueryOrPreset, false, nil + } + } + + if preset == nil { + err := fmt.Errorf("unable to determine preset query") + return verb, "", false, err + } + + return preset.Verb, preset.Query, true, nil +} + +func GetPrettyError(cmd *cobra.Command, err error) error { + return fmt.Errorf("failed to run 'aspect %s': %w", cmd.Use, err) +}