diff --git a/cmd/flags/generic.go b/cmd/flags/generic.go index b86ab3c..472a7c1 100644 --- a/cmd/flags/generic.go +++ b/cmd/flags/generic.go @@ -33,7 +33,7 @@ var RemoteFlag = cli.StringFlag{ var OutputFlag = cli.StringFlag{ Name: "output", Aliases: []string{"o"}, - Usage: "Output format. (csv, simple, table, tsv, yaml)", + Usage: "Output format. (simple, table, csv, tsv, yaml, json)", } // PaginationPageFlag provides flag for pagination options diff --git a/modules/print/table.go b/modules/print/table.go index 26f49f0..1ce6b34 100644 --- a/modules/print/table.go +++ b/modules/print/table.go @@ -6,7 +6,9 @@ package print import ( "fmt" + "io" "os" + "regexp" "sort" "strconv" "strings" @@ -72,25 +74,39 @@ func (t table) Less(i, j int) bool { } func (t *table) print(output string) { + t.fprint(os.Stdout, output) +} + +func (t *table) fprint(f io.Writer, output string) { switch output { case "", "table": - outputtable(t.headers, t.values) + outputTable(f, t.headers, t.values) case "csv": - outputdsv(t.headers, t.values, ",") + outputDsv(f, t.headers, t.values, ",") case "simple": - outputsimple(t.headers, t.values) + outputSimple(f, t.headers, t.values) case "tsv": - outputdsv(t.headers, t.values, "\t") + outputDsv(f, t.headers, t.values, "\t") case "yml", "yaml": - outputyaml(t.headers, t.values) + outputYaml(f, t.headers, t.values) + case "json": + outputJSON(f, t.headers, t.values) default: - fmt.Printf("unknown output type '" + output + "', available types are:\n- csv: comma-separated values\n- simple: space-separated values\n- table: auto-aligned table format (default)\n- tsv: tab-separated values\n- yaml: YAML format\n") + fmt.Fprintf(f, `"unknown output type '%s', available types are: +- csv: comma-separated values +- simple: space-separated values +- table: auto-aligned table format (default) +- tsv: tab-separated values +- yaml: YAML format +- json: JSON format +`, output) + os.Exit(1) } } -// outputtable prints structured data as table -func outputtable(headers []string, values [][]string) { - table := tablewriter.NewWriter(os.Stdout) +// outputTable prints structured data as table +func outputTable(f io.Writer, headers []string, values [][]string) { + table := tablewriter.NewWriter(f) if len(headers) > 0 { table.SetHeader(headers) } @@ -100,47 +116,89 @@ func outputtable(headers []string, values [][]string) { table.Render() } -// outputsimple prints structured data as space delimited value -func outputsimple(headers []string, values [][]string) { +// outputSimple prints structured data as space delimited value +func outputSimple(f io.Writer, headers []string, values [][]string) { for _, value := range values { - fmt.Printf(strings.Join(value, " ")) - fmt.Printf("\n") + fmt.Fprint(f, strings.Join(value, " ")) + fmt.Fprintf(f, "\n") } } -// outputdsv prints structured data as delimiter separated value format -func outputdsv(headers []string, values [][]string, delimiterOpt ...string) { +// outputDsv prints structured data as delimiter separated value format +func outputDsv(f io.Writer, headers []string, values [][]string, delimiterOpt ...string) { delimiter := "," if len(delimiterOpt) > 0 { delimiter = delimiterOpt[0] } - fmt.Println("\"" + strings.Join(headers, "\""+delimiter+"\"") + "\"") + fmt.Fprintln(f, "\""+strings.Join(headers, "\""+delimiter+"\"")+"\"") for _, value := range values { - fmt.Printf("\"") - fmt.Printf(strings.Join(value, "\""+delimiter+"\"")) - fmt.Printf("\"") - fmt.Printf("\n") + fmt.Fprintf(f, "\"") + fmt.Fprint(f, strings.Join(value, "\""+delimiter+"\"")) + fmt.Fprintf(f, "\"") + fmt.Fprintf(f, "\n") } } -// outputyaml prints structured data as yaml -func outputyaml(headers []string, values [][]string) { +// outputYaml prints structured data as yaml +func outputYaml(f io.Writer, headers []string, values [][]string) { for _, value := range values { - fmt.Println("-") + fmt.Fprintln(f, "-") for j, val := range value { intVal, _ := strconv.Atoi(val) if strconv.Itoa(intVal) == val { - fmt.Printf(" %s: %s\n", headers[j], val) + fmt.Fprintf(f, " %s: %s\n", headers[j], val) } else { - fmt.Printf(" %s: '%s'\n", headers[j], val) + fmt.Fprintf(f, " %s: '%s'\n", headers[j], val) } } } } +var ( + matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)") + matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])") +) + +func toSnakeCase(str string) string { + snake := matchFirstCap.ReplaceAllString(str, "${1}_${2}") + snake = matchAllCap.ReplaceAllString(snake, "${1}_${2}") + return strings.ToLower(snake) +} + +// outputJSON prints structured data as json +func outputJSON(f io.Writer, headers []string, values [][]string) { + fmt.Fprintln(f, "[") + itemCount := len(values) + headersCount := len(headers) + const space = " " + for i, value := range values { + fmt.Fprintf(f, "%s{\n", space) + for j, val := range value { + intVal, _ := strconv.Atoi(val) + if strconv.Itoa(intVal) == val { + fmt.Fprintf(f, "%s%s\"%s\": %s", space, space, toSnakeCase(headers[j]), val) + } else { + fmt.Fprintf(f, "%s%s\"%s\": \"%s\"", space, space, toSnakeCase(headers[j]), val) + } + if j != headersCount-1 { + fmt.Fprintln(f, ",") + } else { + fmt.Fprintln(f) + } + } + + if i != itemCount-1 { + fmt.Fprintf(f, "%s},\n", space) + } else { + fmt.Fprintf(f, "%s}\n", space) + } + } + fmt.Fprintln(f, "]") +} + func isMachineReadable(outputFormat string) bool { switch outputFormat { - case "yml", "yaml", "csv", "tsv": + case "yml", "yaml", "csv", "tsv", "json": return true } return false diff --git a/modules/print/table_test.go b/modules/print/table_test.go new file mode 100644 index 0000000..664b24d --- /dev/null +++ b/modules/print/table_test.go @@ -0,0 +1,40 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package print + +import ( + "bytes" + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestToSnakeCase(t *testing.T) { + assert.EqualValues(t, "some_test_var_at2d", toSnakeCase("SomeTestVarAt2d")) +} + +func TestPrint(t *testing.T) { + tData := &table{ + headers: []string{"A", "B"}, + values: [][]string{ + {"new a", "some bbbb"}, + {"AAAAA", "b2"}, + }, + } + + buf := &bytes.Buffer{} + + tData.fprint(buf, "json") + result := []struct { + A string + B string + }{} + assert.NoError(t, json.NewDecoder(buf).Decode(&result)) + + if assert.Len(t, result, 2) { + assert.EqualValues(t, "new a", result[0].A) + } +}