diff --git a/apps/cnquery/cmd/shell.go b/apps/cnquery/cmd/shell.go index 95a216d0b..d9e79d6a5 100644 --- a/apps/cnquery/cmd/shell.go +++ b/apps/cnquery/cmd/shell.go @@ -131,7 +131,8 @@ func StartShell(runtime *providers.Runtime, conf *ShellConfig) error { connectAsset = filteredAssets[selectedAsset] } } else { - fmt.Println(components.List(theme.OperatingSystemTheme, "assets", invAssets)) + log.Info().Msgf("discovered %d assets(s)", len(invAssets)) + fmt.Println(components.List(theme.OperatingSystemTheme, invAssets)) log.Fatal().Msg("cannot connect to more than one asset, use --platform-id to select a specific asset") } } diff --git a/cli/components/README.md b/cli/components/README.md new file mode 100644 index 000000000..691da44a9 --- /dev/null +++ b/cli/components/README.md @@ -0,0 +1,58 @@ +# `components` package + +This Go package has interactive helpers used by `cnquery` and `cnspec`. + +We use a powerful little TUI framework called [bubbletea](https://github.com/charmbracelet/bubbletea). + +## `Select` component + +Select is an interactive prompt that displays the provided message and displays a +list of items to be selected. + +e.g. +```go +type CustomString string + +func (s CustomString) Display() string { + return string(s) +} + +func main() { + customStrings := []CustomString{"first", "second", "third"} + selected := components.Select("Choose a string", customStrings) + fmt.Printf("You chose the %s string.\n", customStrings[selected]) +} +``` + +To execute this example: +``` +go run cli/components/_examples/selector/main.go +``` + +## `List` component + +List is a non-interactive function that lists items to the user. + +e.g. +```go +type CustomString string + +func (s CustomString) PrintableKeys() []string { + return []string{"string"} +} +func (s CustomString) PrintableValue(_ int) string { + return string(s) +} + +func main() { + customStrings := []CustomString{"first", "second", "third"} + list := components.List(theme.OperatingSystemTheme, customStrings) + fmt.Printf(list) +} +``` + +To execute this example: +``` +go run cli/components/_examples/list/main.go +``` + diff --git a/cli/components/_examples/rawlist/main.go b/cli/components/_examples/list/main.go similarity index 85% rename from cli/components/_examples/rawlist/main.go rename to cli/components/_examples/list/main.go index 4f784e07e..316ca1be2 100644 --- a/cli/components/_examples/rawlist/main.go +++ b/cli/components/_examples/list/main.go @@ -21,6 +21,6 @@ func (s CustomString) PrintableValue(_ int) string { func main() { customStrings := []CustomString{"first", "second", "third"} - list := components.List(theme.OperatingSystemTheme, "string", customStrings) + list := components.List(theme.OperatingSystemTheme, customStrings) fmt.Printf(list) } diff --git a/cli/components/_examples/selector/main.go b/cli/components/_examples/selector/main.go index 7dd6b56a0..0b835e764 100644 --- a/cli/components/_examples/selector/main.go +++ b/cli/components/_examples/selector/main.go @@ -11,7 +11,7 @@ import ( type CustomString string -func (s CustomString) HumanName() string { +func (s CustomString) Display() string { return string(s) } diff --git a/cli/components/list_raw_object.go b/cli/components/list_raw_items.go similarity index 71% rename from cli/components/list_raw_object.go rename to cli/components/list_raw_items.go index 806b4b56f..5baaab1d2 100644 --- a/cli/components/list_raw_object.go +++ b/cli/components/list_raw_items.go @@ -8,12 +8,11 @@ import ( "strings" "text/tabwriter" - "github.com/rs/zerolog/log" "go.mondoo.com/cnquery/v11/cli/theme" ) -// Object is the interface that a list need to implement so we can display its objects. -type Object interface { +// ListableItem is the interface that a list need to implement so we can display its items. +type ListableItem interface { // PrintableKeys returns the list of keys that will be printed. PrintableKeys() []string @@ -21,7 +20,7 @@ type Object interface { PrintableValue(index int) string } -// List is a non-interactive function that lists objects to the user. +// List is a non-interactive function that lists items to the user. // // e.g. // ```go @@ -37,17 +36,15 @@ type Object interface { // // func main() { // customStrings := []CustomString{"first", "second", "third"} -// list := components.List(theme.OperatingSystemTheme, "string", customStrings) +// list := components.List(theme.OperatingSystemTheme, customStrings) // fmt.Printf(list) // } // // ``` -func List[O Object](theme *theme.Theme, listType string, list []O) string { +func List[O ListableItem](theme *theme.Theme, list []O) string { b := &strings.Builder{} w := tabwriter.NewWriter(b, 1, 1, 1, ' ', tabwriter.TabIndent) - log.Debug().Msgf("discovered %d %s(s)", len(list), listType) - for i := range list { assetObj := list[i] diff --git a/cli/components/selector.go b/cli/components/selector.go index 6e6010cec..2f8694ec6 100644 --- a/cli/components/selector.go +++ b/cli/components/selector.go @@ -11,12 +11,12 @@ import ( "github.com/rs/zerolog/log" ) -// Selector is the interface that items need to implement so that we can select them. -type Selector interface { - HumanName() string +// SelectableItem is the interface that items need to implement so that we can select them. +type SelectableItem interface { + Display() string } -// Select is an interactive prompt that displays the provided message and displays a +// SelectableItem is an interactive prompt that displays the provided message and displays a // list of items to be selected. // // e.g. @@ -24,7 +24,7 @@ type Selector interface { // // type CustomString string // -// func (s CustomString) HumanName() string { +// func (s CustomString) Display() string { // return string(s) // } // @@ -35,11 +35,11 @@ type Selector interface { // } // // ``` -func Select[S Selector](msg string, items []S) int { +func Select[S SelectableItem](msg string, items []S) int { list := make([]string, len(items)) for i := range items { - list[i] = items[i].HumanName() + list[i] = items[i].Display() } selection := -1 // make sure we have an invalid index @@ -58,7 +58,7 @@ func Select[S Selector](msg string, items []S) int { selected := items[selection] log.Debug(). Int("selection", selection). - Str("asset", selected.HumanName()). + Str("item", selected.Display()). Msg("selected") return selection } diff --git a/providers-sdk/v1/inventory/asset.go b/providers-sdk/v1/inventory/asset.go index 94b898c8f..8301e2590 100644 --- a/providers-sdk/v1/inventory/asset.go +++ b/providers-sdk/v1/inventory/asset.go @@ -22,7 +22,7 @@ func (a *Asset) PrintableKeys() []string { func (a *Asset) PrintableValue(index int) string { switch assetPrintableKeys[index] { case "name": - return a.HumanName() + return a.Display() case "platform-id": return strings.Join(a.PlatformIds, " ") default: @@ -30,6 +30,11 @@ func (a *Asset) PrintableValue(index int) string { } } +// Display implements SelectableItem from the cli/components package. +func (a *Asset) Display() string { + return a.HumanName() +} + func (a *Asset) HumanName() string { if a == nil { return ""