Commit 1d3de6e

Richard Luby <richluby@gmail.com>
2016-11-04 15:14:14
solidified command functions
command functions support long and short options, in addition to positional parameters. value flags and boolean flags are supported for commands.
1 parent c92ca42
client.go
@@ -5,7 +5,6 @@ import (
 	"flag"
 	"fmt"
 	"github.com/BurntSushi/toml"
-	"github.com/richluby/commander"
 	"log"
 	"os"
 	"strconv"
@@ -47,10 +46,9 @@ func loadClientConfiguration(cfgFile string) CLIENT_CONFIG {
 
 // initUserSession starts the interactive prompt for the user
 func initUserSession() {
-	//client := &http.Client{}
 	fmt.Printf("%s", "Welcome to the question interface. Use 'help' for more information.")
 	var input string
-	var command commander.Command
+	var command Command
 	var err error
 	reader := bufio.NewReader(os.Stdin)
 	for {
@@ -65,17 +63,16 @@ func initUserSession() {
 			continue
 		}
 		// select a command from the list of available commands
-		if command, err = commander.SelectCommand(strings.TrimSpace(args[0]), commandArray); err != nil {
+		if command, err = SelectCommand(strings.TrimSpace(args[0]), commandArray); err != nil {
 			log.Printf(COLOR_RED+"Error while parsing command: %+v"+COLOR_RESET, err)
 			continue
 		}
 		// parse the flags for the command
-		err = commander.ParseUserCommands(args[1:], &command)
+		err = ParseUserCommands(args[1:], &command)
 		if err != nil {
 			log.Printf(COLOR_RED+"Error while parsing parameters: %+v"+COLOR_RESET, err)
 			continue
 		}
-		log.Printf("Flags: %+v", command.Flags)
 		// execute the command and check for any errors that may have occurred
 		if err = command.Run(command); err != nil {
 			log.Printf(COLOR_RED+"Error while executing command: %+v"+COLOR_RESET, err)
command.go
@@ -5,7 +5,6 @@ import (
 	"bytes"
 	"encoding/json"
 	"fmt"
-	"github.com/richluby/commander"
 	"io/ioutil"
 	"log"
 	"net/http"
@@ -16,27 +15,27 @@ import (
 
 // commandArray contains the full list of commands available to the application
 // note that subcommands are not currently supported
-var commandArray = []commander.Command{commander.Command{Command: "exit",
+var commandArray = []Command{Command{Command: "exit",
 	Description: "Exit the self-assessment application and close the connection.",
-	Run: func(command commander.Command) error {
+	Run: func(command Command) error {
 		fmt.Print("Exiting application.\n")
 		os.Exit(0)
 		return nil
 	}},
-	commander.Command{Command: "score",
+	Command{Command: "score",
 		Description: "Display the score for the user.",
-		Run: func(command commander.Command) error {
+		Run: func(command Command) error {
 			fmt.Print("score was run.")
 			return nil
 		}},
-	commander.Command{Command: "test",
+	Command{Command: "test",
 		Usage: "test [flags]",
-		Flags: map[string]commander.Flag{"blueprint": commander.Flag{
+		Flags: map[string]Flag{"blueprint": Flag{
 			Flag:        "blueprint",
 			Usage:       "--blueprint",
 			Description: "Allows to define a list of categories to use",
 			Value:       "false"},
-			"questions": commander.Flag{
+			"questions": Flag{
 				Flag:        "questions",
 				Usage:       "--questions [n]",
 				HasArgs:     true,
@@ -45,10 +44,10 @@ var commandArray = []commander.Command{commander.Command{Command: "exit",
 		Description: "Execute a test. Given a number, will go through a test with [n] questions." +
 			" Type 'exit' to stop test.",
 		Run: executeTest},
-	commander.Command{
+	Command{
 		Command: "user",
 		Usage:   "user <username> <token>",
-		Flags: map[string]commander.Flag{"username": commander.Flag{Flag: "username",
+		Flags: map[string]Flag{"username": Flag{Flag: "username",
 			Description: "The username to use for this session",
 			Value:       "anonymous",
 			Usage:       ""}},
@@ -57,7 +56,7 @@ var commandArray = []commander.Command{commander.Command{Command: "exit",
 		Run: setUserName}}
 
 // sets the user name for this session
-func setUserName(command commander.Command) error {
+func setUserName(command Command) error {
 	if command.Flags["username"].Value == "" {
 		return fmt.Errorf("Could not set the user due to empty username.")
 	} else if command.Flags[""].Value == "" {
@@ -69,7 +68,7 @@ func setUserName(command commander.Command) error {
 
 // helpCommand prints the help for the list of available commands.
 // it cannot exist in the initialization due to initialization loops
-var helpCommand = commander.Command{Command: "help",
+var helpCommand = Command{Command: "help",
 	Usage: "help [command]",
 	Description: "Display help for all known commands. If " + COLOR_GREEN + "command" + COLOR_RESET + " is specified, " +
 		"displays help for that command.",
@@ -77,9 +76,9 @@ var helpCommand = commander.Command{Command: "help",
 
 // displayHelp prints the help. if a command is specified,
 // help is displayed for that particular command
-func displayHelp(command commander.Command) error {
+func displayHelp(command Command) error {
 	for _, checkCommand := range commandArray {
-		if command.Flags["command"].Value != "" && command.Flags["command"].Value != checkCommand.Command { // only print help for desired command
+		if len(command.PositionalParameters) > 0 && command.PositionalParameters[0] != checkCommand.Command { // only print help for desired command
 			continue
 		}
 		if strings.Compare(checkCommand.Usage, "") == 0 { // print command help
@@ -174,7 +173,7 @@ func getRecordFromServer(client *http.Client, config CLIENT_CONFIG, numQuestions
 // getCategoriesFromServer requests a new list of the available
 // categories from the server. functionality provided for
 // command integration as well
-func getCategoriesFromServer(command commander.Command) error {
+func getCategoriesFromServer(command Command) error {
 	client := &http.Client{}
 	resp, err := client.Get(clientConfig.SERVER_URL + API_ROOT + "/questions/categories")
 	if err != nil {
@@ -191,7 +190,7 @@ func getCategoriesFromServer(command commander.Command) error {
 }
 
 // parseTestCommandFlags parses the flags for the test command
-func parseTestCommandFlags(command commander.Command) (int, bool, error) {
+func parseTestCommandFlags(command Command) (int, bool, error) {
 	questions := 20
 	var err error
 	useBlueprint := false
@@ -212,7 +211,7 @@ func parseTestCommandFlags(command commander.Command) (int, bool, error) {
 
 // executeTest runs a user through a test
 // from retrieving the records to returning the answers
-func executeTest(command commander.Command) error {
+func executeTest(command Command) error {
 	client := &http.Client{}
 	var clientTest ClientTest
 	var err error
@@ -227,7 +226,7 @@ func executeTest(command commander.Command) error {
 		return fmt.Errorf("Error while requesting test from server: %+v", err)
 	}
 	clientTest.Username = clientConfig.USER
-	if err = getCategoriesFromServer(commander.Command{}); err != nil {
+	if err = getCategoriesFromServer(Command{}); err != nil {
 		return fmt.Errorf("Error while pulling server categories: %+v", err)
 	}
 	runTest(&clientTest)
commandParser.go
@@ -0,0 +1,81 @@
+package main
+
+import (
+	"fmt"
+	"strings"
+)
+
+// Command stores the information for running a command
+type Command struct {
+	Command,
+	Description,
+	Usage string
+	Flags                map[string]Flag
+	PositionalParameters []string
+	Run                  func(Command) error
+}
+
+// Flag servers to store command flags for the program commands
+// if HasArgs is true, then the flag MUST have an argument
+type Flag struct {
+	Flag,
+	Description,
+	Usage,
+	Value string
+	HasArgs bool
+	Args    []string
+}
+
+// selectCommand takes a string and converts it to the correct command
+func SelectCommand(str string, commandArray []Command) (Command, error) {
+	for _, value := range commandArray {
+		if strings.Compare(value.Command, str) == 0 {
+			return value, nil
+		}
+	}
+	return Command{}, fmt.Errorf("No command found for: %s", str)
+}
+
+// expandShortOptions takes condensed short options and
+// expands them into properly formatted short options
+func expandShortOptions(shorts string) []string {
+	var options = make([]string, len(shorts)-1)
+	for char := range shorts {
+		options = append(options, "-"+string(char))
+	}
+	return options
+}
+
+// parseUserCommands takes a user string and parses it into separate
+// flags for the specified command, placing the values into the
+// specified struct. invalid flags cause an error
+func ParseUserCommands(args []string, command *Command) error {
+	flagString := ""
+	for len(args) > 0 {
+		if strings.HasPrefix(args[0], "-") {
+			if strings.HasPrefix(args[0], "--") {
+				flagString = args[0][2:]
+			} else if len(args[0]) > 2 { // multiple short options at once
+				args = append(expandShortOptions(args[0]), args...)
+				continue
+			} else { // only 1 short option
+				flagString = args[0][1:]
+			}
+			for key, flagValue := range command.Flags {
+				if flagString == flagValue.Flag {
+					if flagValue.HasArgs {
+						if len(args) < 2 {
+							return fmt.Errorf("Not enough arguments for %s", flagString)
+						}
+						flagValue.Value = args[1]
+					}
+					command.Flags[key] = flagValue
+				}
+			}
+		} else { // flags have been handled
+			command.PositionalParameters = args
+		}
+		args = args[1:]
+	}
+	return nil
+}