Skip to content
This repository has been archived by the owner on May 31, 2024. It is now read-only.

Commit

Permalink
Tabular text implementation and tests (#88)
Browse files Browse the repository at this point in the history
* Tabular text implementation and tests
  • Loading branch information
abassyiouni authored Oct 15, 2021
1 parent 10d1d00 commit a82dd36
Show file tree
Hide file tree
Showing 6 changed files with 388 additions and 2 deletions.
4 changes: 4 additions & 0 deletions packages/cli/cmd/application/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/aws/amazon-genomics-cli/cmd/application/template"
"github.com/aws/amazon-genomics-cli/internal/pkg/cli"
"github.com/aws/amazon-genomics-cli/internal/pkg/cli/clierror"
"github.com/aws/amazon-genomics-cli/internal/pkg/cli/format"
"github.com/aws/amazon-genomics-cli/internal/pkg/logging"
"github.com/aws/amazon-genomics-cli/internal/pkg/term/color"
"github.com/aws/amazon-genomics-cli/internal/pkg/version"
Expand All @@ -33,6 +34,7 @@ slug: %s

type mainVars struct {
docPath string
format string
}

func init() {
Expand Down Expand Up @@ -82,6 +84,7 @@ func buildRootCmd() *cobra.Command {
Displays the help menu for the specified sub-command.
/code $ agc account --help`,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
format.SetFormatter(format.FormatterType(vars.format))
setLoggingLevel()
checkCliVersion()
},
Expand Down Expand Up @@ -113,6 +116,7 @@ func buildRootCmd() *cobra.Command {
cmd.SetUsageTemplate(template.RootUsage)

cmd.PersistentFlags().BoolVarP(&logging.Verbose, cli.VerboseFlag, cli.VerboseFlagShort, false, cli.VerboseFlagDescription)
cmd.PersistentFlags().StringVar(&vars.format, cli.FormatFlag, cli.FormatFlagDefault, cli.FormatFlagDescription)
cmd.Flags().StringVar(&vars.docPath, "docs", "", "generate markdown documenting the CLI to the specified path")
cmd.Flag("docs").Hidden = true

Expand Down
2 changes: 2 additions & 0 deletions packages/cli/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
Expand Down
8 changes: 8 additions & 0 deletions packages/cli/internal/pkg/cli/flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

package cli

import "github.com/aws/amazon-genomics-cli/internal/pkg/cli/format"

const (
argsFlag = "args"
argsFlagShort = "a"
Expand All @@ -15,6 +17,12 @@ const (
VerboseFlagDescription = "Display verbose diagnostic information."
)

const (
FormatFlag = "format"
FormatFlagDefault = string(format.DefaultFormat)
FormatFlagDescription = "Format option for output. Valid options are: text, tabular"
)

const (
AWSProfileFlag = "awsProfile"
AWSProfileFlagShort = "p"
Expand Down
28 changes: 26 additions & 2 deletions packages/cli/internal/pkg/cli/format/format.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,35 @@
package format

import (
"bytes"
"os"
"text/tabwriter"
"bytes"
)
var Default Formatter = NewText()

var Default Formatter = &Text{os.Stdout}
type FormatterType string
const (
textFormat FormatterType = "text"
tableFormat FormatterType = "table"
DefaultFormat = textFormat
)

func NewText() *Text {
return &Text{os.Stdout}
}

func NewTable() *Table {
return &Table{
*tabwriter.NewWriter(os.Stdout, 0, 8, 0, tableDelimiter[0], 0),
}
}

func SetFormatter(format FormatterType) {
switch format {
case textFormat: Default = NewText()
case tableFormat: Default = NewTable()
}
}

type Formatter interface {
Write(interface{})
Expand Down
218 changes: 218 additions & 0 deletions packages/cli/internal/pkg/cli/format/table.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
package format

import (
"fmt"
"reflect"
"strings"
"text/tabwriter"
)

type Table struct {
writer tabwriter.Writer
}

const (
noPrefix = ""
emptyVal = "-"
prefixNameDivider = "-"
tableDelimiter = "\t"
)

func (f *Table) Write(o interface{}) {
val := reflect.ValueOf(o)
if !f.validate(val.Type()) {
SetFormatter(DefaultFormat)
Default.Write(o)
} else {
f.writeHeader(val)
f.writeValue(val)
f.writer.Flush()
}
}

func (f *Table) validate(val reflect.Type) bool {
if f.isSlice(val) {
return f.validateStruct(val.Elem())
} else if f.isStruct(val) {
return f.validateStruct(val)
} else {
return true
}
}

func (f *Table) validateStruct(val reflect.Type) bool {
for i := 0; i < val.NumField(); i++ {
structFieldType := val.Field(i).Type
if f.isStruct(structFieldType) {
return false
} else if f.isSlice(structFieldType) && !f.validateSlice(structFieldType.Elem()) {
return false
}
}
return true
}

func (f *Table) validateSlice(val reflect.Type) bool {
if f.isSimpleType(val) {
return true
} else if f.isStruct(val) {
for i := 0; i < val.NumField(); i++ {
structFieldType := val.Field(i).Type
if !f.isSimpleType(structFieldType) {
return false
}
}
}
return true
}

func (f *Table) writeHeader(value reflect.Value) {
f.write(strings.Join(f.getHeader(noPrefix, value.Type()), tableDelimiter))
f.newLine()
}

func (f *Table) getHeader(prefix string, value reflect.Type) []string {
if f.isStruct(value) {
headers := f.getStructHeaders(prefix, value)
return headers
} else if f.isSlice(value) {
headers := f.getHeader(prefix, value.Elem())
return headers
} else {
return []string{prefix + value.Name()}
}
}

func (f *Table) getStructHeaders(prefix string, val reflect.Type) []string {
var headers []string
for i := 0; i < val.NumField(); i++ {
structFieldType := val.Field(i).Type
if f.isSlice(structFieldType) {
headers = append(headers, f.getHeader(prefix+val.Field(i).Name+prefixNameDivider, val.Field(i).Type.Elem())...)
} else {
headers = append(headers, prefix+val.Field(i).Name)
}
}
return headers
}

func (f *Table) writeValue(value reflect.Value) {
val := reflect.Indirect(value)
if f.isSimpleType(value.Type()) {
f.write(fmt.Sprint(value))
} else if f.isStruct(value.Type()) {
maxCollectionSize := f.getMaxSliceSize(value)
if maxCollectionSize == 0 {
f.write(f.getSimpleStructOutput(val))
} else {
f.write(f.getSliceOutput(maxCollectionSize, value))
}
} else {
f.writeCollection(value)
}
}

func (f *Table) writeCollection(value reflect.Value) {
for i := 0; i < value.Len(); i++ {
maxCollectionSize := f.getMaxSliceSize(value.Index(i))
if maxCollectionSize == 0 {
f.write(f.getSimpleStructOutput(value.Index(i)))
} else {
f.write(f.getSliceOutput(maxCollectionSize, value.Index(i)))
}
f.newLine()
}
}

func (f *Table) getMaxSliceSize(value reflect.Value) int {
maxSize := 0
for i := 0; i < value.Type().NumField(); i++ {
field := value.Field(i)
if f.isSlice(field.Type()) && field.Len() > maxSize {
maxSize = field.Len()
}
}
return maxSize
}

func (f *Table) getSliceOutput(maxCollectionSize int, value reflect.Value) string {
val := reflect.Indirect(value)
var outputRowList []string
output := ""

for rowIndex := 0; rowIndex < maxCollectionSize; rowIndex++ {
for structIndex := 0; structIndex < val.Type().NumField(); structIndex++ {
structField := val.Field(structIndex)
if f.isSimpleType(structField.Type()) {
outputRowList = f.appendSimpleValue(structField, outputRowList, rowIndex)
} else if f.isStruct(structField.Type().Elem()) {
outputRowList = f.appendSliceStructRow(structField, outputRowList, rowIndex)
} else if f.isSlice(structField.Type()) {
outputRowList = f.appendSimpleSlice(structField, outputRowList, rowIndex)
} else {
outputRowList = append(outputRowList, fmt.Sprint(structField))
}
}
output += strings.Join(outputRowList, tableDelimiter) + "\n"
outputRowList = outputRowList[:0]
}

return output
}

func (f *Table) appendSliceStructRow(field reflect.Value, outputList []string, row int) []string {
for k := 0; k < field.Type().Elem().NumField(); k++ {
if row >= field.Len() {
outputList = append(outputList, emptyVal)
} else {
outputList = append(outputList, fmt.Sprint(field.Index(row).Field(k)))
}
}
return outputList
}

func (f *Table) appendSimpleSlice(field reflect.Value, outputList []string, row int) []string {
if row >= field.Len() {
outputList = append(outputList, emptyVal)
} else {
outputList = append(outputList, fmt.Sprint(field.Index(row)))
}
return outputList
}

func (f *Table) appendSimpleValue(field reflect.Value, outputList[]string, row int) []string {
if row > 0 {
outputList = append(outputList, emptyVal)
} else {
outputList = append(outputList, fmt.Sprint(field))
}
return outputList
}

func (f *Table) getSimpleStructOutput(val reflect.Value) string {
var outputList []string
for i := 0; i < val.Type().NumField(); i++ {
outputList = append(outputList, fmt.Sprint(val.Field(i)))
}
return strings.Join(outputList, tableDelimiter)
}

func (f *Table) isSimpleType(value reflect.Type) bool {
return !(value.Kind() == reflect.Slice || value.Kind() == reflect.Struct)
}

func (f *Table) isSlice(value reflect.Type) bool {
return value.Kind() == reflect.Slice
}

func (f *Table) isStruct(value reflect.Type) bool {
return value.Kind() == reflect.Struct
}

func (f *Table) write(v interface{}) {
_, _ = fmt.Fprint(&f.writer, v)
}

func (f *Table) newLine() {
_, _ = fmt.Fprintln(&f.writer)
}
Loading

0 comments on commit a82dd36

Please sign in to comment.