Skip to content

Commit

Permalink
add command 'show' to display profile properties
Browse files Browse the repository at this point in the history
  • Loading branch information
creativeprojects committed Jun 30, 2020
1 parent d2ebc7b commit 85a5d59
Show file tree
Hide file tree
Showing 4 changed files with 231 additions and 0 deletions.
30 changes: 30 additions & 0 deletions commands.go
Expand Up @@ -8,6 +8,7 @@ import (
"text/tabwriter"

"github.com/creativeprojects/resticprofile/config"
"github.com/creativeprojects/resticprofile/constants"
"github.com/creativeprojects/resticprofile/systemd"
)

Expand Down Expand Up @@ -39,6 +40,13 @@ var (
action: createSystemdTimer,
needConfiguration: true,
},
{
name: "show",
description: "show all the details of the current profile",
action: showProfile,
needConfiguration: true,
hide: false,
},
{
name: "panic",
description: "(debug only) simulates a panic",
Expand Down Expand Up @@ -148,3 +156,25 @@ func sortedMapKeys(data map[string][]string) []string {
sort.Strings(keys)
return keys
}

func showProfile(c *config.Config, flags commandLineFlags, args []string) error {
// Show global section first
global, err := c.GetGlobalSection()
if err != nil {
return fmt.Errorf("cannot show global: %w", err)
}
fmt.Printf("\n%s:\n", constants.SectionConfigurationGlobal)
config.ShowStruct(os.Stdout, global)

// Then show profile
profile, err := c.GetProfile(flags.name)
if err != nil {
return fmt.Errorf("cannot show profile '%s': %w", flags.name, err)
}
if profile == nil {
return fmt.Errorf("profile '%s' not found", flags.name)
}
fmt.Printf("\n%s:\n", flags.name)
config.ShowStruct(os.Stdout, profile)
return nil
}
5 changes: 5 additions & 0 deletions config/flag.go
Expand Up @@ -67,6 +67,11 @@ func stringifyValue(value reflect.Value) ([]string, bool) {
stringVal := fmt.Sprintf("%d", intVal)
return []string{stringVal}, intVal != 0

case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
intVal := value.Uint()
stringVal := fmt.Sprintf("%d", intVal)
return []string{stringVal}, intVal != 0

case reflect.Float32, reflect.Float64:
floatVal := value.Float()
stringVal := fmt.Sprintf("%f", floatVal)
Expand Down
127 changes: 127 additions & 0 deletions config/show.go
@@ -0,0 +1,127 @@
package config

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

const (
templateIndent = " " // 4 spaces
)

func ShowStruct(w io.Writer, orig interface{}) error {
return showSubStruct(w, orig, "")
}

func showSubStruct(outputWriter io.Writer, orig interface{}, prefix string) error {
typeOf := reflect.TypeOf(orig)
valueOf := reflect.ValueOf(orig)

if typeOf.Kind() == reflect.Ptr {
// Deference the pointer
typeOf = typeOf.Elem()
valueOf = valueOf.Elem()
}

// NumField() will panic if typeOf is not a struct
if typeOf.Kind() != reflect.Struct {
return fmt.Errorf("unsupported type %s, expected %s", typeOf.Kind(), reflect.Struct)
}

// make a temporary buffer to display sub structs and maps after direct properties (which are buffered through a tabWriter)
buffer := bufio.NewWriter(outputWriter)
tabWriter := tabwriter.NewWriter(outputWriter, 0, 2, 2, ' ', 0)
prefix = addIndentation(prefix)

for i := 0; i < typeOf.NumField(); i++ {
field := typeOf.Field(i)

if key, ok := field.Tag.Lookup("mapstructure"); ok {
if key == "" {
continue
}
if valueOf.Field(i).Kind() == reflect.Ptr {
if valueOf.Field(i).IsNil() {
continue
}
// start of a new pointer to a struct
fmt.Fprintf(buffer, "%s%s:\n", prefix, key)
err := showSubStruct(buffer, valueOf.Field(i).Elem().Interface(), prefix)
if err != nil {
return err
}
continue
}
if valueOf.Field(i).Kind() == reflect.Struct {
// start of a new struct
fmt.Fprintf(buffer, "%s%s:\n", prefix, key)
err := showSubStruct(buffer, valueOf.Field(i).Interface(), prefix)
if err != nil {
return err
}
continue
}
if valueOf.Field(i).Kind() == reflect.Map {
if valueOf.Field(i).Len() == 0 {
continue
}
// special case of the map of remaining parameters...
if key == ",remain" {
showMap(tabWriter, prefix, valueOf.Field(i))
continue
}
fmt.Fprintf(buffer, "%s%s:\n", prefix, key)
showNewMap(buffer, prefix, valueOf.Field(i))
continue
}
showKeyValue(tabWriter, prefix, key, valueOf.Field(i))
}
}

tabWriter.Flush()
fmt.Fprintln(buffer, "")
buffer.Flush()
prefix = removeIndentation(prefix)

return nil
}

func addIndentation(indent string) string {
return indent + templateIndent
}

func removeIndentation(indent string) string {
return indent[:len(indent)-len(templateIndent)]
}

func showNewMap(outputWriter io.Writer, prefix string, valueOf reflect.Value) {
subWriter := tabwriter.NewWriter(outputWriter, 0, 2, 2, ' ', 0)
prefix = addIndentation(prefix)
showMap(subWriter, prefix, valueOf)
subWriter.Flush()
fmt.Fprintln(outputWriter, "")
prefix = removeIndentation(prefix)
}

func showMap(tabWriter io.Writer, prefix string, valueOf reflect.Value) {
iter := valueOf.MapRange()
for iter.Next() {
showKeyValue(tabWriter, prefix, iter.Key().String(), iter.Value())
}
}

func showKeyValue(tabWriter io.Writer, prefix, key string, valueOf reflect.Value) {
// This is reusing the stringifyValue function used to build the restic flags
convert, ok := stringifyValue(valueOf)
if ok {
if len(convert) == 0 {
// special case of a true flag that shows no value
convert = append(convert, "true")
}
fmt.Fprintf(tabWriter, "%s%s:\t%s\n", prefix, key, strings.Join(convert, ", "))
}
}
69 changes: 69 additions & 0 deletions config/show_test.go
@@ -0,0 +1,69 @@
package config

import (
"strings"
"testing"

"github.com/stretchr/testify/assert"
)

type showStructData struct {
input interface{}
output string
}

type testObject struct {
Id int `mapstructure:"id"`
Name string `mapstructure:"name"`
Person testPerson `mapstructure:"person"`
Pointer *testPointer `mapstructure:"pointer"`
Map map[string][]string `mapstructure:",remain"`
}

type testPerson struct {
Name string `mapstructure:"name"`
IsValid bool `mapstructure:"valid"`
Properties map[string][]string `mapstructure:"properties"`
}

type testPointer struct {
IsValid bool `mapstructure:"valid"`
}

func TestShowStruct(t *testing.T) {
testData := []showStructData{
{
input: testObject{Id: 11, Name: "test"},
output: " person:\n id: 11\n name: test\n",
},
{
input: testObject{Id: 11, Person: testPerson{Name: "test"}},
output: " person:\n name: test\n id: 11\n",
},
{
input: testObject{Id: 11, Person: testPerson{Name: "test", IsValid: true}},
output: " person:\n name: test\n valid: true\n id: 11\n",
},
{
input: testObject{Id: 11, Pointer: &testPointer{IsValid: true}},
output: " person:\n pointer:\n valid: true\n id: 11\n",
},
{
input: testObject{Id: 11, Person: testPerson{Properties: map[string][]string{
"list": {"one", "two", "three"},
}}},
output: " person:\n properties:\n list: one, two, three\n id: 11\n",
},
{
input: testObject{Id: 11, Name: "test", Map: map[string][]string{"left": {"over"}}},
output: " person:\n id: 11\n name: test\n left:\tover\n",
},
}

for _, testItem := range testData {
b := &strings.Builder{}
err := ShowStruct(b, testItem.input)
assert.NoError(t, err)
assert.Equal(t, testItem.output, strings.ReplaceAll(b.String(), " ", " "))
}
}

0 comments on commit 85a5d59

Please sign in to comment.