From 5201295109d0ec6452b1044f01dae1b6708e79dc Mon Sep 17 00:00:00 2001
From: a-mt <amistri@live.fr>
Date: Sat, 19 Jan 2019 13:11:21 +0100
Subject: [PATCH] Add writeas config command

---
 cmd/writeas/cli.go        | 26 ++++++++++-
 cmd/writeas/commands.go   | 77 ++++++++++++++++++++++++++++++
 cmd/writeas/userconfig.go | 98 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 200 insertions(+), 1 deletion(-)

diff --git a/cmd/writeas/cli.go b/cmd/writeas/cli.go
index 6092688..f99a0b3 100644
--- a/cmd/writeas/cli.go
+++ b/cmd/writeas/cli.go
@@ -289,13 +289,37 @@ func main() {
 				},
 			},
 		},
+		{
+			Name:   "config",
+			Usage:  "Get and set options",
+			UsageText: "config name [value]\n   writeas config [command options]",
+			Action: cmdOptions,
+			Flags: []cli.Flag{
+				cli.BoolFlag{
+					Name:  "edit, e",
+					Usage: "Opens an editor to modify the config file",
+				},
+				cli.BoolFlag{
+					Name:  "list, l",
+					Usage: "List all variables set in config file, along with their values",
+				},
+				cli.BoolFlag{
+					Name:  "list-all, a",
+					Usage: "List all config variables, along with their values",
+				},
+				cli.BoolFlag{
+					Name:  "verbose, v",
+					Usage: "Make the operation more talkative",
+				},
+			},
+		},
 	}
 
 	cli.CommandHelpTemplate = `NAME:
    {{.Name}} - {{.Usage}}
 
 USAGE:
-   writeas {{.Name}}{{if .Flags}} [command options]{{end}} [arguments...]{{if .Description}}
+   writeas {{if .UsageText}}{{.UsageText}}{{else}}{{.Name}}{{if .Flags}} [command options]{{end}} [arguments...]{{end}}{{if .Description}}
 
 DESCRIPTION:
    {{.Description}}{{end}}{{if .Flags}}
diff --git a/cmd/writeas/commands.go b/cmd/writeas/commands.go
index 20e2b7c..941e8d9 100644
--- a/cmd/writeas/commands.go
+++ b/cmd/writeas/commands.go
@@ -8,6 +8,7 @@ import (
 	"io/ioutil"
 	"os"
 	"path/filepath"
+	"strconv"
 )
 
 func cmdPost(c *cli.Context) error {
@@ -240,3 +241,79 @@ func cmdAuth(c *cli.Context) error {
 func cmdLogOut(c *cli.Context) error {
 	return DoLogOut(c)
 }
+
+func cmdOptions(c *cli.Context) error {
+
+	// Edit config file
+	if c.Bool("e") {
+		composeConfig()
+
+	// List configs
+	} else if c.Bool("l") || c.Bool("a") {
+		uc, err := loadConfig()
+		if err != nil {
+			ErrorlnQuit(fmt.Sprintf("Error loading config: %v", err), 1)
+		}
+		printConfig(uc, "", c.Bool("a"))
+
+	// Check arguments
+	} else {
+		nargs := len(c.Args())
+
+		// No arguments nor options: display command usage
+		if nargs == 0 {
+			cli.ShowSubcommandHelp(c)
+			return nil
+		}
+		name  := c.Args().Get(0)
+		value := c.Args().Get(1)
+
+		// Load config file
+		uc, err := loadConfig()
+		if err != nil {
+			ErrorlnQuit(fmt.Sprintf("Error loading config: %v", err), 1)
+		}
+
+		// Get reflection of field
+		rval, err := getConfigField(uc, name)
+		if err != nil {
+			ErrorlnQuit(fmt.Sprintf("%v", err), 1)
+		}
+
+		// Print value
+		if nargs == 1 {
+			fmt.Printf("%s=%v\n", name, *rval)
+
+		// Set value
+		} else {
+
+			// Cast and set value
+			switch typ := rval.Kind().String(); typ {
+				case "bool":
+					b, err := strconv.ParseBool(value)
+					if err != nil {
+						ErrorlnQuit(fmt.Sprintf("error: \"%s\" is not a valid boolean", value), 1)
+					}
+					rval.SetBool(b)
+
+				case "int":
+					i, err := strconv.ParseInt(value, 0, 0)
+					if err != nil {
+						ErrorlnQuit(fmt.Sprintf("error: \"%s\" is not a valid integer", value), 1)
+					}
+					rval.SetInt(i)
+
+				case "string":
+					rval.SetString(value)
+			}
+
+			// Save config to file
+			err = saveConfig(uc)
+			if err != nil {
+				ErrorlnQuit(fmt.Sprintf("Unable to save config: %s", err), 1)
+			}
+			fmt.Println("Saved config.")
+		}
+	}
+	return nil
+}
diff --git a/cmd/writeas/userconfig.go b/cmd/writeas/userconfig.go
index e904dff..b3d35f2 100644
--- a/cmd/writeas/userconfig.go
+++ b/cmd/writeas/userconfig.go
@@ -7,6 +7,10 @@ import (
 	"gopkg.in/ini.v1"
 	"io/ioutil"
 	"path/filepath"
+	"reflect"
+	"fmt"
+	"os"
+	"strings"
 )
 
 const (
@@ -88,3 +92,97 @@ func saveUser(u *writeas.AuthUser) error {
 	}
 	return nil
 }
+
+// Prints all values of the given struct
+// For subfields: the field name is separated with dots (ex: Posts.Directory=)
+func printConfig(x interface{}, prefix string, showEmptyValues bool) {
+	values := reflect.ValueOf(x)
+
+	if values.Kind() == reflect.Ptr {
+		values = values.Elem()
+	}
+	typ := values.Type()
+
+	for i := 0; i < typ.NumField(); i++ {
+		val  := values.Field(i)
+		name := typ.Field(i).Name
+
+		if prefix != "" {
+			name = prefix + "." + name
+		}
+		if(val.Kind() == reflect.Struct) {
+			printConfig(val.Interface(), name, showEmptyValues)
+		} else {
+			if showEmptyValues || val.Interface() != reflect.Zero(val.Type()).Interface() {
+				fmt.Printf("%s=%v\n", name, val)
+			}
+		}
+	}
+}
+
+// Get the value of a given field
+// For subfields: the name should be separated with dots (ex: Posts.Directory)
+func getConfigField(x interface{}, name string) (*reflect.Value, error) {
+	path   := strings.Split(name, ".")
+	values := reflect.ValueOf(x)
+
+	if values.Kind() == reflect.Ptr {
+		values = values.Elem()
+	}
+	for _, part := range path {
+		values = values.FieldByName(part)
+
+		if !values.IsValid() {
+			err := fmt.Errorf("error: key does not contain a section: %v", name)
+			return nil, err
+		}
+	}
+	if values.Kind() == reflect.Struct {
+		err := fmt.Errorf("error: key does not contain a section: %v", name)
+		return nil, err
+	}
+	return &values, nil
+}
+
+// Opens an editor to modify the config file
+func composeConfig() error {
+	filename := filepath.Join(userDataDir(), userConfigFile)
+
+	// Open the editor
+	cmd := editPostCmd(filename)
+	if cmd == nil {
+		fmt.Println(noEditorErr)
+		return nil
+	}
+	cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
+	if err := cmd.Start(); err != nil {
+		if debug {
+			panic(err)
+		} else {
+			Errorln("Error starting editor: %s", err)
+			return nil
+		}
+	}
+
+	// Wait until the editor is closed
+	if err := cmd.Wait(); err != nil {
+		if debug {
+			panic(err)
+		} else {
+			Errorln("Editor finished with error: %s", err)
+			return nil
+		}
+	}
+
+	// Check if the config file is valid
+	_, err := loadConfig()
+	if err != nil {
+		if debug {
+			panic(err)
+		} else {
+			Errorln("Error loading config: %s", err)
+			return nil
+		}
+	}
+	return nil
+}