Wrap plugin list output to window width. Closes #235 (#244)

This commit is contained in:
Binaek Sarkar
2021-03-03 17:59:10 +05:30
committed by GitHub
parent d40b632218
commit 7b4e7fb0f1
8 changed files with 121 additions and 113 deletions

View File

@@ -4,13 +4,12 @@ import (
"errors"
"fmt"
"log"
"os"
"strings"
"github.com/turbot/steampipe/constants"
"github.com/turbot/steampipe/display"
"github.com/turbot/steampipe/statefile"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
"github.com/turbot/steampipe-plugin-sdk/logging"
"github.com/turbot/steampipe/cmdconfig"
@@ -418,14 +417,12 @@ func runPluginListCmd(cmd *cobra.Command, args []string) {
utils.ShowErrorWithMessage(err,
fmt.Sprintf("Plugin Listing failed"))
}
t := table.NewWriter()
t.SetStyle(table.StyleLight)
t.SetOutputMirror(os.Stdout)
t.AppendHeader(table.Row{"Name", "Version", "Connections"})
headers := []string{"Name", "Version", "Connections"}
rows := [][]string{}
for _, item := range list {
t.AppendRow(table.Row{item.Name, item.Version, strings.Join(item.Connections, ",")})
rows = append(rows, []string{item.Name, item.Version, strings.Join(item.Connections, ",")})
}
t.Render()
display.ShowWrappedTable(headers, rows, false)
}
func runPluginUninstallCmd(cmd *cobra.Command, args []string) {

View File

@@ -10,17 +10,18 @@ import (
"github.com/turbot/steampipe/cmdconfig"
"github.com/turbot/steampipe/constants"
"github.com/turbot/steampipe/definitions/results"
"github.com/turbot/steampipe/utils"
)
// ExecuteSync :: execute a query against this client and wait for the result
func (c *Client) ExecuteSync(query string) (*SyncQueryResult, error) {
func (c *Client) ExecuteSync(query string) (*results.SyncQueryResult, error) {
// https://github.com/golang/go/wiki/CodeReviewComments#indent-error-flow
result, err := c.executeQuery(query, false)
if err != nil {
return nil, err
}
syncResult := &SyncQueryResult{ColTypes: result.ColTypes}
syncResult := &results.SyncQueryResult{ColTypes: result.ColTypes}
for row := range *result.RowChan {
syncResult.Rows = append(syncResult.Rows, row)
}
@@ -28,9 +29,9 @@ func (c *Client) ExecuteSync(query string) (*SyncQueryResult, error) {
return syncResult, nil
}
func (c *Client) executeQuery(query string, countStream bool) (*QueryResult, error) {
func (c *Client) executeQuery(query string, countStream bool) (*results.QueryResult, error) {
if query == "" {
return &QueryResult{}, nil
return &results.QueryResult{}, nil
}
start := time.Now()
@@ -65,7 +66,7 @@ func (c *Client) executeQuery(query string, countStream bool) (*QueryResult, err
}
cols, err := rows.Columns()
result := newQueryResult(colTypes)
result := results.NewQueryResult(colTypes)
rowCount := 0

View File

@@ -6,6 +6,7 @@ import (
"github.com/turbot/steampipe/autocomplete"
"github.com/turbot/steampipe/cmdconfig"
"github.com/turbot/steampipe/definitions/results"
"github.com/turbot/steampipe/constants"
"github.com/turbot/steampipe/metaquery"
@@ -40,7 +41,7 @@ func (c *InteractiveClient) close() {
}
// InteractiveQuery :: start an interactive prompt and return
func (c *InteractiveClient) InteractiveQuery(resultsStreamer *ResultStreamer, onCompleteCallback func()) {
func (c *InteractiveClient) InteractiveQuery(resultsStreamer *results.ResultStreamer, onCompleteCallback func()) {
defer func() {
onCompleteCallback()
@@ -60,7 +61,7 @@ func (c *InteractiveClient) InteractiveQuery(resultsStreamer *ResultStreamer, on
// this needs to be the last thing we do,
// as the runQueryCmd uses this as an indication
// to quit out of the application
resultsStreamer.close()
resultsStreamer.Close()
}()
fmt.Printf("Welcome to Steampipe v%s\n", version.String())
@@ -81,7 +82,7 @@ func (c *InteractiveClient) InteractiveQuery(resultsStreamer *ResultStreamer, on
}
}
func (c *InteractiveClient) runInteractivePrompt(resultsStreamer *ResultStreamer) (ret utils.InteractiveExitStatus) {
func (c *InteractiveClient) runInteractivePrompt(resultsStreamer *results.ResultStreamer) (ret utils.InteractiveExitStatus) {
defer func() {
// this is to catch the PANIC that gets raised by
// the executor of go-prompt
@@ -171,7 +172,7 @@ func (c *InteractiveClient) breakMultilinePrompt(buffer *prompt.Buffer) {
c.interactiveBuffer = []string{}
}
func (c *InteractiveClient) executor(line string, resultsStreamer *ResultStreamer) {
func (c *InteractiveClient) executor(line string, resultsStreamer *results.ResultStreamer) {
line = strings.TrimSpace(line)
// if it's an empty line, then we don't need to do anything
@@ -210,7 +211,7 @@ func (c *InteractiveClient) executor(line string, resultsStreamer *ResultStreame
utils.ShowError(err)
resultsStreamer.Done()
} else {
resultsStreamer.streamResult(result)
resultsStreamer.StreamResult(result)
}
}

View File

@@ -8,11 +8,12 @@ import (
"github.com/turbot/steampipe-plugin-sdk/logging"
"github.com/turbot/steampipe/constants"
"github.com/turbot/steampipe/definitions/results"
"github.com/turbot/steampipe/utils"
)
// ExecuteQuery :: entry point for executing ad-hoc queries from outside the package
func ExecuteQuery(queryString string) (*ResultStreamer, error) {
func ExecuteQuery(queryString string) (*results.ResultStreamer, error) {
var err error
logging.LogTime("db.ExecuteQuery start")
@@ -48,7 +49,7 @@ func ExecuteQuery(queryString string) (*ResultStreamer, error) {
return nil, fmt.Errorf("failed to add functions: %v", err)
}
resultsStreamer := newQueryResults()
resultsStreamer := results.NewResultStreamer()
// this is a callback to close the db et-al. when things get done - no matter the mode
onComplete := func() { Shutdown(client, InvokerQuery) }
@@ -69,13 +70,15 @@ func ExecuteQuery(queryString string) (*ResultStreamer, error) {
onComplete()
return nil, err
}
go resultsStreamer.streamSingleResult(result, onComplete)
go resultsStreamer.StreamSingleResult(result, onComplete)
}
logging.LogTime("db.ExecuteQuery end")
return resultsStreamer, nil
}
// Shutdown :: closes the client connection and stops the
// database instance if the given `invoker` matches
func Shutdown(client *Client, invoker Invoker) {
log.Println("[TRACE] shutdown")
if client != nil {

View File

@@ -1,4 +1,10 @@
package db
/**
This package is for all interfaces that are imported in multiple packages in the
code base
This package MUST never import any other `steampipe` package
**/
package results
import (
"database/sql"
@@ -29,7 +35,7 @@ func (r QueryResult) StreamError(err error) {
*r.RowChan <- &RowResult{Error: err}
}
func newQueryResult(colTypes []*sql.ColumnType) *QueryResult {
func NewQueryResult(colTypes []*sql.ColumnType) *QueryResult {
rowChan := make(chan *RowResult)
return &QueryResult{
RowChan: &rowChan,

View File

@@ -1,11 +1,11 @@
package db
package results
type ResultStreamer struct {
Results chan *QueryResult
displayReady chan string
}
func newQueryResults() *ResultStreamer {
func NewResultStreamer() *ResultStreamer {
return &ResultStreamer{
// make buffered channel so we can always stream a single result
Results: make(chan *QueryResult, 1),
@@ -13,18 +13,18 @@ func newQueryResults() *ResultStreamer {
}
}
func (q *ResultStreamer) streamResult(result *QueryResult) {
func (q *ResultStreamer) StreamResult(result *QueryResult) {
q.Results <- result
}
func (q *ResultStreamer) streamSingleResult(result *QueryResult, onComplete func()) {
func (q *ResultStreamer) StreamSingleResult(result *QueryResult, onComplete func()) {
q.Results <- result
q.Wait()
onComplete()
close(q.Results)
}
func (q *ResultStreamer) close() {
func (q *ResultStreamer) Close() {
close(q.Results)
}

View File

@@ -11,15 +11,16 @@ import (
"github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text"
"github.com/karrick/gows"
"github.com/turbot/steampipe/cmdconfig"
"github.com/turbot/steampipe/constants"
"github.com/turbot/steampipe/db"
"github.com/turbot/steampipe/definitions/results"
"github.com/turbot/steampipe/utils"
)
// ShowOutput :: displays the output using the proper formatter as applicable
func ShowOutput(result *db.QueryResult) {
func ShowOutput(result *results.QueryResult) {
if cmdconfig.Viper().Get(constants.ArgOutput) == constants.ArgJSON {
displayJSON(result)
} else if cmdconfig.Viper().Get(constants.ArgOutput) == constants.ArgCSV {
@@ -32,7 +33,74 @@ func ShowOutput(result *db.QueryResult) {
}
}
func displayLine(result *db.QueryResult) {
func ShowWrappedTable(headers []string, rows [][]string, autoMerge bool) {
t := table.NewWriter()
t.SetStyle(table.StyleDefault)
t.SetOutputMirror(os.Stdout)
rowConfig := table.RowConfig{AutoMerge: autoMerge}
colConfigs, headerRow := getColumnSettings(headers, rows)
t.SetColumnConfigs(colConfigs)
t.AppendHeader(headerRow)
for _, row := range rows {
rowObj := table.Row{}
for _, col := range row {
rowObj = append(rowObj, col)
}
t.AppendRow(rowObj, rowConfig)
}
t.Render()
}
// calculate and returns column configuration based on header and row content
func getColumnSettings(headers []string, rows [][]string) ([]table.ColumnConfig, table.Row) {
maxCols, _, _ := gows.GetWinSize()
colConfigs := make([]table.ColumnConfig, len(headers))
headerRow := make(table.Row, len(headers))
sumOfAllCols := 0
// account for the spaces around the value of a column and separators
spaceAccounting := ((len(headers) * 3) + 1)
for idx, colName := range headers {
headerRow[idx] = colName
// get the maximum len of strings in this column
maxLen := 0
for _, row := range rows {
colVal := row[idx]
if len(colVal) > maxLen {
maxLen = len(colVal)
}
if len(colName) > maxLen {
maxLen = len(colName)
}
}
colConfigs[idx] = table.ColumnConfig{
Name: colName,
Number: idx + 1,
WidthMax: maxLen,
WidthMin: maxLen,
}
sumOfAllCols += maxLen
}
// now that all columns are set to the widths that they need,
// set the last one to occupy as much as is available - no more - no less
sumOfRest := sumOfAllCols - colConfigs[len(colConfigs)-1].WidthMax
if sumOfAllCols > maxCols {
colConfigs[len(colConfigs)-1].WidthMax = (maxCols - sumOfRest - spaceAccounting)
colConfigs[len(colConfigs)-1].WidthMin = (maxCols - sumOfRest - spaceAccounting)
}
return colConfigs, headerRow
}
func displayLine(result *results.QueryResult) {
colNames := ColumnNames(result.ColTypes)
maxColNameLength := 0
for _, colName := range colNames {
@@ -44,7 +112,7 @@ func displayLine(result *db.QueryResult) {
itemIdx := 0
// define a function to display each row
rowFunc := func(row []interface{}, result *db.QueryResult) {
rowFunc := func(row []interface{}, result *results.QueryResult) {
recordAsString, _ := ColumnValuesAsString(row, result.ColTypes)
requiredTerminalColumnsForValuesOfRecord := 0
for _, colValue := range recordAsString {
@@ -98,11 +166,11 @@ func getTerminalColumnsRequiredForString(str string) int {
return colsRequired
}
func displayJSON(result *db.QueryResult) {
func displayJSON(result *results.QueryResult) {
var jsonOutput []map[string]interface{}
// define function to add each row to the JSON output
rowFunc := func(row []interface{}, result *db.QueryResult) {
rowFunc := func(row []interface{}, result *results.QueryResult) {
record := map[string]interface{}{}
for idx, colType := range result.ColTypes {
value, _ := ParseJSONOutputColumnValue(row[idx], colType)
@@ -125,7 +193,7 @@ func displayJSON(result *db.QueryResult) {
fmt.Printf("%s\n", string(data))
}
func displayCSV(result *db.QueryResult) {
func displayCSV(result *results.QueryResult) {
csvWriter := csv.NewWriter(os.Stdout)
csvWriter.Comma = []rune(cmdconfig.Viper().GetString(constants.ArgSeparator))[0]
@@ -135,7 +203,7 @@ func displayCSV(result *db.QueryResult) {
// print the data as it comes
// define function display each csv row
rowFunc := func(row []interface{}, result *db.QueryResult) {
rowFunc := func(row []interface{}, result *results.QueryResult) {
rowAsString, _ := ColumnValuesAsString(row, result.ColTypes)
_ = csvWriter.Write(rowAsString)
}
@@ -152,7 +220,7 @@ func displayCSV(result *db.QueryResult) {
}
}
func displayTable(result *db.QueryResult) {
func displayTable(result *results.QueryResult) {
// the buffer to put the output data in
outbuf := bytes.NewBufferString("")
@@ -178,7 +246,7 @@ func displayTable(result *db.QueryResult) {
t.AppendHeader(headers)
// define a function to execute for each row
rowFunc := func(row []interface{}, result *db.QueryResult) {
rowFunc := func(row []interface{}, result *results.QueryResult) {
rowAsString, _ := ColumnValuesAsString(row, result.ColTypes)
rowObj := table.Row{}
for _, col := range rowAsString {
@@ -205,10 +273,10 @@ func displayTable(result *db.QueryResult) {
ShowPaged(outbuf.String())
}
type displayResultsFunc func(row []interface{}, result *db.QueryResult)
type displayResultsFunc func(row []interface{}, result *results.QueryResult)
// call func displayResult for each row of results
func iterateResults(result *db.QueryResult, displayResult displayResultsFunc) error {
func iterateResults(result *results.QueryResult, displayResult displayResultsFunc) error {
for row := range *result.RowChan {
if row == nil {
return nil

View File

@@ -2,19 +2,18 @@ package metaquery
import (
"fmt"
"os"
"regexp"
"sort"
"strings"
"github.com/c-bata/go-prompt"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/karrick/gows"
"github.com/turbot/go-kit/helpers"
typeHelpers "github.com/turbot/go-kit/types"
"github.com/turbot/steampipe/cmdconfig"
"github.com/turbot/steampipe/connection_config"
"github.com/turbot/steampipe/constants"
"github.com/turbot/steampipe/display"
"github.com/turbot/steampipe/schema"
"github.com/turbot/steampipe/utils"
)
@@ -174,7 +173,7 @@ To get information about the columns in a table, run '.inspect {connection}.{tab
})
rows = append(rows, tables...)
}
writeTable(header, rows, true)
display.ShowWrappedTable(header, rows, true)
}
return nil
@@ -233,7 +232,7 @@ func listConnections(input *HandlerInput) error {
return rows[i][0] < rows[j][0]
})
writeTable(header, rows, false)
display.ShowWrappedTable(header, rows, false)
fmt.Printf(`
To get information about the tables in a connection, run '.inspect {connection}'
@@ -263,7 +262,7 @@ func inspectConnection(connectionName string, input *HandlerInput) bool {
return rows[i][0] < rows[j][0]
})
writeTable(header, rows, false)
display.ShowWrappedTable(header, rows, false)
return true
}
@@ -295,78 +294,11 @@ func inspectTable(connectionName string, tableName string, input *HandlerInput)
return rows[i][0] < rows[j][0]
})
writeTable(header, rows, false)
display.ShowWrappedTable(header, rows, false)
return nil
}
func writeTable(headers []string, rows [][]string, autoMerge bool) {
t := table.NewWriter()
t.SetStyle(table.StyleDefault)
t.SetOutputMirror(os.Stdout)
rowConfig := table.RowConfig{AutoMerge: autoMerge}
colConfigs, headerRow := getColumnSettings(headers, rows)
t.SetColumnConfigs(colConfigs)
t.AppendHeader(headerRow)
for _, row := range rows {
rowObj := table.Row{}
for _, col := range row {
rowObj = append(rowObj, col)
}
t.AppendRow(rowObj, rowConfig)
}
t.Render()
}
// calculate and returns column configuration based on header and row content
func getColumnSettings(headers []string, rows [][]string) ([]table.ColumnConfig, table.Row) {
maxCols, _, _ := gows.GetWinSize()
colConfigs := make([]table.ColumnConfig, len(headers))
headerRow := make(table.Row, len(headers))
sumOfAllCols := 0
// account for the spaces around the value of a column and separators
spaceAccounting := ((len(headers) * 3) + 1)
for idx, colName := range headers {
headerRow[idx] = colName
// get the maximum len of strings in this column
maxLen := 0
for _, row := range rows {
colVal := row[idx]
if len(colVal) > maxLen {
maxLen = len(colVal)
}
if len(colName) > maxLen {
maxLen = len(colName)
}
}
colConfigs[idx] = table.ColumnConfig{
Name: colName,
Number: idx + 1,
WidthMax: maxLen,
WidthMin: maxLen,
}
sumOfAllCols += maxLen
}
// now that all columns are set to the widths that they need,
// set the last one to occupy as much as is available - no more - no less
sumOfRest := sumOfAllCols - colConfigs[len(colConfigs)-1].WidthMax
if sumOfAllCols > maxCols {
colConfigs[len(colConfigs)-1].WidthMax = (maxCols - sumOfRest - spaceAccounting)
colConfigs[len(colConfigs)-1].WidthMin = (maxCols - sumOfRest - spaceAccounting)
}
return colConfigs, headerRow
}
func buildTable(rows [][]string, autoMerge bool) string {
t := table.NewWriter()
t.SetStyle(table.StyleDefault)