Skip to content

Commit

Permalink
Stop exporting test-only package members
Browse files Browse the repository at this point in the history
  • Loading branch information
aramprice committed May 29, 2024
1 parent a99ea01 commit ee09d28
Show file tree
Hide file tree
Showing 2 changed files with 182 additions and 149 deletions.
168 changes: 168 additions & 0 deletions cmd/completion/completion.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,177 @@
package completion

import (
"bytes"
"reflect"
"strings"

boshlog "github.com/cloudfoundry/bosh-utils/logger"
"github.com/spf13/cobra"

boshcmd "github.com/cloudfoundry/bosh-cli/v7/cmd"
"github.com/cloudfoundry/bosh-cli/v7/cmd/opts"
boshui "github.com/cloudfoundry/bosh-cli/v7/ui"
)

const initCmdName = "help"
const cobraCompletionCmdName = "completion"
const cobraCompleteCmdName = "__complete"

const logTag = "completion"

func IsItCompletionCommand(args []string) bool {
return len(args) > 0 && (args[0] == cobraCompletionCmdName || args[0] == cobraCompleteCmdName)
}

type CmdContext struct {
ConfigPath string
EnvironmentName string
DeploymentName string
}

func (c *CmdContext) findStoreForFlagValue(flagName string) *string {
switch flagName {
case "environment":
return &c.EnvironmentName
case "config":
return &c.ConfigPath
case "deployment":
return &c.DeploymentName
}
return nil
}

type BoshComplete struct {
completionFunctionsMap *CompleteFunctionsMap
rootCmd *cobra.Command
cmdContext *CmdContext
logger boshlog.Logger
}

func NewBoshComplete(blindUi *boshui.ConfUI, logger boshlog.Logger) *BoshComplete {
deps := boshcmd.NewBasicDeps(blindUi, logger)
cmdFactory := boshcmd.NewFactory(deps)
var session boshcmd.Session
cmd, err := cmdFactory.New([]string{initCmdName}) // just to init session
if err != nil {
logger.Debug(logTag, "session initialization (command '%s') error: %v", initCmdName, err)
} else {
session = NewCmdBridge(cmd, deps).Session()
}
cmdContext := &CmdContext{}
dq := NewDirectorQuery(logger, cmdContext, session)
cfMap := NewCompleteFunctionsMap(logger, dq)
return NewBoshCompleteWithFunctions(logger, cmdContext, cfMap)
}

func NewBoshCompleteWithFunctions(logger boshlog.Logger, cmdContext *CmdContext, completionFunctionsMap *CompleteFunctionsMap) *BoshComplete {
// https://github.com/spf13/cobra/blob/main/site/content/completions/_index.md

c := &BoshComplete{
completionFunctionsMap: completionFunctionsMap,
logger: logger,
rootCmd: &cobra.Command{Use: "bosh"},
cmdContext: cmdContext,
}
c.discoverBoshCommands(c.rootCmd, reflect.TypeOf(opts.BoshOpts{}), 0)
return c
}

func (c *BoshComplete) discoverBoshCommands(parentCommand *cobra.Command, fieldType reflect.Type, level int) {
for i := 0; i < fieldType.NumField(); i++ {
field := fieldType.Field(i)
if field.Name == "Args" {
c.tryToBindValidArgsFunction(parentCommand, field.Type.Name())
} else if field.Tag.Get("long") != "" {
c.addFlag(parentCommand, field, level == 0)
} else if fc := field.Tag.Get("command"); fc != "" && fc != cobraCompletionCmdName && fc != cobraCompleteCmdName {
newCmd := c.addCommand(parentCommand, field)
if field.Type.Kind() == reflect.Struct {
c.discoverBoshCommands(newCmd, field.Type, level+1)
}
}
}
}

func (c *BoshComplete) addCommand(parentCommand *cobra.Command, field reflect.StructField) *cobra.Command {
cmdName := field.Tag.Get("command")
newCmd := &cobra.Command{
Use: cmdName,
Short: field.Tag.Get("description"),
Aliases: c.getTagValues(field.Tag, "alias"),
Run: func(_ *cobra.Command, _ []string) {},
}
parentCommand.AddCommand(newCmd)
return newCmd
}

func (c *BoshComplete) getTagValues(fieldTag reflect.StructTag, tagName string) []string {
rawTag := string(fieldTag)
parts := strings.Split(rawTag, " ")
prefix := tagName + ":"
var values []string
for _, part := range parts {
if strings.HasPrefix(part, prefix) {
value := strings.TrimPrefix(part, prefix)
values = append(values, value)
}
}
return values
}

func (c *BoshComplete) addFlag(cmd *cobra.Command, field reflect.StructField, rootLevel bool) {
name := field.Tag.Get("long")
short := field.Tag.Get("short")
value := field.Tag.Get("default")
usage := field.Tag.Get("description")
env := field.Tag.Get("env")
if env != "" {
usage = usage + ", env: " + env
}
flagSet := cmd.Flags()
if rootLevel {
flagSet = cmd.PersistentFlags()
}
p := c.cmdContext.findStoreForFlagValue(name)
if p == nil {
flagSet.StringP(name, short, value, usage)
} else {
flagSet.StringVarP(p, name, short, value, usage)
}
if fun, ok := (*c.completionFunctionsMap)["--"+name]; ok {
err := cmd.RegisterFlagCompletionFunc(name, fun)
if err != nil {
c.logger.Warn(logTag, "register flag %s completion function error: %v", name, err)
}
}
}

func (c *BoshComplete) Execute(args []string) (*cobra.Command, error) {
c.rootCmd.SetArgs(args)
return c.rootCmd.ExecuteC()
}

func (c *BoshComplete) ExecuteCaptured(args []string) (*CapturedResult, error) {
buf := new(bytes.Buffer)
c.rootCmd.SetOut(buf)
retCmd, err := c.Execute(args)
if err != nil {
return nil, err
}
retLines := strings.Split(buf.String(), "\n")
return &CapturedResult{Lines: retLines, Command: retCmd}, nil
}

func (c *BoshComplete) tryToBindValidArgsFunction(cmd *cobra.Command, argsTypeName string) {
if fun, ok := (*c.completionFunctionsMap)[argsTypeName]; ok {
//c.logger.Debug(c.logTag, "Command ValidArgsFunction '%s': `%v`", cmd.Name(), GetShortFunName(fun))
cmd.ValidArgsFunction = fun
} else {
c.logger.Warn(logTag, "Unknown Args Type %s, command %s", argsTypeName, cmd.Name())
}
}

type CapturedResult struct {
Lines []string
Command *cobra.Command
}
163 changes: 14 additions & 149 deletions cmd/completion/completion_test.go
Original file line number Diff line number Diff line change
@@ -1,171 +1,36 @@
package completion_test

import (
"bytes"
"fmt"
"reflect"
"strings"

"github.com/cloudfoundry/bosh-cli/v7/cmd/completion"
"github.com/cloudfoundry/bosh-cli/v7/cmd/opts"
boshlog "github.com/cloudfoundry/bosh-utils/logger"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/spf13/cobra"
)

const cobraCompletionCmdName = "completion"
const cobraCompleteCmdName = "__complete"

func IsItCompletionCommand(args []string) bool {
return len(args) > 0 && (args[0] == cobraCompletionCmdName || args[0] == cobraCompleteCmdName)
}

type BoshComplete struct {
completionFunctionsMap *completion.CompleteFunctionsMap
rootCmd *cobra.Command
cmdContext *completion.CmdContext
}

func NewBoshCompleteWithFunctions(cmdContext *completion.CmdContext, completionFunctionsMap *completion.CompleteFunctionsMap) *BoshComplete {
// https://github.com/spf13/cobra/blob/main/site/content/completions/_index.md

c := &BoshComplete{
completionFunctionsMap: completionFunctionsMap,
rootCmd: &cobra.Command{Use: "bosh"},
cmdContext: cmdContext,
}
c.discoverBoshCommands(c.rootCmd, reflect.TypeOf(opts.BoshOpts{}), 0)
return c
}

func (c *BoshComplete) discoverBoshCommands(parentCommand *cobra.Command, fieldType reflect.Type, level int) {
for i := 0; i < fieldType.NumField(); i++ {
field := fieldType.Field(i)
if field.Name == "Args" {
c.tryToBindValidArgsFunction(parentCommand, field.Type.Name())
} else if field.Tag.Get("long") != "" {
c.addFlag(parentCommand, field, level == 0)
} else if fc := field.Tag.Get("command"); fc != "" && fc != cobraCompletionCmdName && fc != cobraCompleteCmdName {
newCmd := c.addCommand(parentCommand, field)
if field.Type.Kind() == reflect.Struct {
c.discoverBoshCommands(newCmd, field.Type, level+1)
}
}
}
}

func (c *BoshComplete) addCommand(parentCommand *cobra.Command, field reflect.StructField) *cobra.Command {
cmdName := field.Tag.Get("command")
newCmd := &cobra.Command{
Use: cmdName,
Short: field.Tag.Get("description"),
Aliases: c.getTagValues(field.Tag, "alias"),
Run: func(_ *cobra.Command, _ []string) {},
}
parentCommand.AddCommand(newCmd)
return newCmd
}

func (c *BoshComplete) getTagValues(fieldTag reflect.StructTag, tagName string) []string {
rawTag := string(fieldTag)
parts := strings.Split(rawTag, " ")
prefix := tagName + ":"
var values []string
for _, part := range parts {
if strings.HasPrefix(part, prefix) {
value := strings.TrimPrefix(part, prefix)
values = append(values, value)
}
}
return values
}

func (c *BoshComplete) addFlag(cmd *cobra.Command, field reflect.StructField, rootLevel bool) {
name := field.Tag.Get("long")
short := field.Tag.Get("short")
value := field.Tag.Get("default")
usage := field.Tag.Get("description")
env := field.Tag.Get("env")
if env != "" {
usage = usage + ", env: " + env
}
flagSet := cmd.Flags()
if rootLevel {
flagSet = cmd.PersistentFlags()
}
p := c.findStoreForFlagValue(name)
if p == nil {
flagSet.StringP(name, short, value, usage)
} else {
flagSet.StringVarP(p, name, short, value, usage)
}
if fun, ok := (*c.completionFunctionsMap)["--"+name]; ok {
err := cmd.RegisterFlagCompletionFunc(name, fun)
if err != nil {
GinkgoLogr.Info(fmt.Sprintf("register flag %s completion function error: %v", name, err))
}
}
}

func (c *BoshComplete) findStoreForFlagValue(flagName string) *string {
switch flagName {
case "environment":
return &c.cmdContext.EnvironmentName
case "config":
return &c.cmdContext.ConfigPath
case "deployment":
return &c.cmdContext.DeploymentName
}
return nil
}

func (c *BoshComplete) ExecuteCaptured(args []string) (*CapturedResult, error) {
buf := new(bytes.Buffer)
c.rootCmd.SetOut(buf)
c.rootCmd.SetErr(GinkgoWriter)
c.rootCmd.SetArgs(args)
retCmd, err := c.rootCmd.ExecuteC()
if err != nil {
return nil, err
}
retLines := strings.Split(buf.String(), "\n")
return &CapturedResult{Lines: retLines, Command: retCmd}, nil
}

func (c *BoshComplete) tryToBindValidArgsFunction(cmd *cobra.Command, argsTypeName string) {
if fun, ok := (*c.completionFunctionsMap)[argsTypeName]; ok {
cmd.ValidArgsFunction = fun
} else {
GinkgoLogr.Info(fmt.Sprintf("Unknown Args Type %s, command %s", argsTypeName, cmd.Name()))
}
}

type CapturedResult struct {
Lines []string
Command *cobra.Command
}
"github.com/cloudfoundry/bosh-cli/v7/cmd/completion"
)

var _ = Describe("Completion Integration Tests", func() {
var boshComplete *BoshComplete
var boshComplete *completion.BoshComplete

BeforeEach(func() {
testLogger := boshlog.NewWriterLogger(boshlog.LevelInfo, GinkgoWriter)
fakeCmdCtx := &completion.CmdContext{}
fakeDq := completion.NewDirectorQueryFake(fakeCmdCtx)
fakeCompletionFunctionMap :=
completion.NewCompleteFunctionsMap(boshlog.NewWriterLogger(boshlog.LevelInfo, GinkgoWriter), fakeDq)
completion.NewCompleteFunctionsMap(testLogger, fakeDq)

boshComplete = NewBoshCompleteWithFunctions(fakeCmdCtx, fakeCompletionFunctionMap)
boshComplete = completion.NewBoshCompleteWithFunctions(testLogger, fakeCmdCtx, fakeCompletionFunctionMap)
})

It("is this bosh completion command", func() {
Expect(IsItCompletionCommand([]string{"completion"})).To(BeTrue())
Expect(IsItCompletionCommand([]string{"completion", "something-else"})).To(BeTrue())
Expect(IsItCompletionCommand([]string{"__complete"})).To(BeTrue())
Expect(IsItCompletionCommand([]string{"__complete", "something-else"})).To(BeTrue())
Expect(IsItCompletionCommand([]string{})).To(BeFalse())
Expect(IsItCompletionCommand([]string{"deployments"})).To(BeFalse())
Expect(IsItCompletionCommand([]string{"something-else"})).To(BeFalse())
Expect(completion.IsItCompletionCommand([]string{"completion"})).To(BeTrue())
Expect(completion.IsItCompletionCommand([]string{"completion", "something-else"})).To(BeTrue())
Expect(completion.IsItCompletionCommand([]string{"__complete"})).To(BeTrue())
Expect(completion.IsItCompletionCommand([]string{"__complete", "something-else"})).To(BeTrue())
Expect(completion.IsItCompletionCommand([]string{})).To(BeFalse())
Expect(completion.IsItCompletionCommand([]string{"deployments"})).To(BeFalse())
Expect(completion.IsItCompletionCommand([]string{"something-else"})).To(BeFalse())
})

It("completion", func() {
Expand Down Expand Up @@ -256,7 +121,7 @@ type testCase struct {
wantErr bool
}

func (tc testCase) check(boshComplete *BoshComplete) {
func (tc testCase) check(boshComplete *completion.BoshComplete) {
args := strings.Split(tc.args, " ")
result, err := boshComplete.ExecuteCaptured(args)
if tc.wantErr {
Expand Down

0 comments on commit ee09d28

Please sign in to comment.