Refactor main to accept commands and arguments
This commit is contained in:
parent
d624a3ca21
commit
6c09ee3826
3 changed files with 163 additions and 111 deletions
232
cmd/rove/main.go
232
cmd/rove/main.go
|
@ -2,7 +2,6 @@ package main
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
@ -10,6 +9,7 @@ import (
|
|||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/mdiluz/rove/pkg/bearing"
|
||||
"github.com/mdiluz/rove/pkg/game"
|
||||
"github.com/mdiluz/rove/pkg/rove"
|
||||
"github.com/mdiluz/rove/pkg/version"
|
||||
|
@ -17,42 +17,90 @@ import (
|
|||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// Command usage
|
||||
func printUsage() {
|
||||
fmt.Fprintf(os.Stderr, "Usage: %s COMMAND [OPTIONS]...\n", os.Args[0])
|
||||
fmt.Fprintln(os.Stderr, "\nCommands:")
|
||||
fmt.Fprintln(os.Stderr, "\tstatus \tprints the server status")
|
||||
fmt.Fprintln(os.Stderr, "\tregister\tregisters an account and stores it (use with -name)")
|
||||
fmt.Fprintln(os.Stderr, "\tmove \tissues move command to rover")
|
||||
fmt.Fprintln(os.Stderr, "\tradar \tgathers radar data for the current rover")
|
||||
fmt.Fprintln(os.Stderr, "\trover \tgets data for current rover")
|
||||
fmt.Fprintln(os.Stderr, "\tconfig \toutputs the local config info")
|
||||
fmt.Fprintln(os.Stderr, "\tversion \toutputs version info")
|
||||
fmt.Fprintln(os.Stderr, "\nOptions:")
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
|
||||
var home = os.Getenv("HOME")
|
||||
var defaultDataPath = path.Join(home, ".local/share/")
|
||||
|
||||
// Command usage
|
||||
func printUsage() {
|
||||
fmt.Fprintf(os.Stderr, "Usage: %s COMMAND [ARGS]...\n", os.Args[0])
|
||||
fmt.Fprintln(os.Stderr, "\nCommands")
|
||||
fmt.Fprintln(os.Stderr, "\tstatus prints the server status")
|
||||
fmt.Fprintln(os.Stderr, "\tregister NAME registers an account and stores it (use with -name)")
|
||||
fmt.Fprintln(os.Stderr, "\tcommands COMMAND [VAL...] issues move command to rover")
|
||||
fmt.Fprintln(os.Stderr, "\tradar gathers radar data for the current rover")
|
||||
fmt.Fprintln(os.Stderr, "\trover gets data for current rover")
|
||||
fmt.Fprintln(os.Stderr, "\tconfig [HOST] outputs the local config info, optionally sets host")
|
||||
fmt.Fprintln(os.Stderr, "\thelp outputs this usage information")
|
||||
fmt.Fprintln(os.Stderr, "\tversion outputs version info")
|
||||
fmt.Fprintln(os.Stderr, "\nEnvironment")
|
||||
fmt.Fprintln(os.Stderr, "\tUSER_DATA path to user data, defaults to "+defaultDataPath)
|
||||
fmt.Fprintln(os.Stderr, "\tROVE_HOST path to rove host server")
|
||||
}
|
||||
|
||||
const gRPCport = 9090
|
||||
|
||||
// General usage
|
||||
var host = flag.String("host", "", "path to game host server")
|
||||
var data = flag.String("data", defaultDataPath, "data location for storage (or $USER_DATA if set)")
|
||||
|
||||
// For register command
|
||||
var name = flag.String("name", "", "used with status command for the account name")
|
||||
|
||||
// For the move command
|
||||
var bearing = flag.String("bearing", "", "used for the move command bearing (compass direction)")
|
||||
|
||||
// Config is used to store internal data
|
||||
type Config struct {
|
||||
Host string `json:"host,omitempty"`
|
||||
Accounts map[string]string `json:"accounts,omitempty"`
|
||||
}
|
||||
|
||||
// ConfigPath returns the configuration path
|
||||
func ConfigPath() string {
|
||||
// Allow overriding the data path
|
||||
var datapath = defaultDataPath
|
||||
var override = os.Getenv("USER_DATA")
|
||||
if len(override) > 0 {
|
||||
datapath = override
|
||||
}
|
||||
datapath = path.Join(datapath, "rove.json")
|
||||
|
||||
return datapath
|
||||
}
|
||||
|
||||
// LoadConfig loads the config from a chosen path
|
||||
func LoadConfig() (config Config, err error) {
|
||||
|
||||
datapath := ConfigPath()
|
||||
config.Accounts = make(map[string]string)
|
||||
|
||||
// Create the path if needed
|
||||
path := filepath.Dir(datapath)
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
os.MkdirAll(path, os.ModePerm)
|
||||
} else {
|
||||
// Read the file
|
||||
_, err = os.Stat(datapath)
|
||||
if !os.IsNotExist(err) {
|
||||
if b, err := ioutil.ReadFile(datapath); err != nil {
|
||||
return Config{}, fmt.Errorf("failed to read file %s error: %s", datapath, err)
|
||||
|
||||
} else if len(b) == 0 {
|
||||
return Config{}, fmt.Errorf("file %s was empty, assumin fresh data", datapath)
|
||||
|
||||
} else if err := json.Unmarshal(b, &config); err != nil {
|
||||
return Config{}, fmt.Errorf("failed to unmarshal file %s error: %s", datapath, err)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SaveConfig saves the config out
|
||||
func SaveConfig(config Config) error {
|
||||
// Save out the persistent file
|
||||
datapath := ConfigPath()
|
||||
if b, err := json.MarshalIndent(config, "", "\t"); err != nil {
|
||||
return fmt.Errorf("failed to marshal data error: %s", err)
|
||||
} else if err := ioutil.WriteFile(datapath, b, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("failed to save file %s error: %s", datapath, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// verifyID will verify an account ID
|
||||
func verifyID(id string) error {
|
||||
if len(id) == 0 {
|
||||
|
@ -62,61 +110,43 @@ func verifyID(id string) error {
|
|||
}
|
||||
|
||||
// InnerMain wraps the main function so we can test it
|
||||
func InnerMain(command string) error {
|
||||
func InnerMain(command string, args ...string) error {
|
||||
|
||||
// Load in the persistent file
|
||||
var config = Config{
|
||||
Accounts: make(map[string]string),
|
||||
}
|
||||
|
||||
// Allow overriding the data path
|
||||
var datapath = *data
|
||||
var override = os.Getenv("USER_DATA")
|
||||
if len(override) > 0 {
|
||||
datapath = override
|
||||
}
|
||||
datapath = path.Join(datapath, "rove.json")
|
||||
|
||||
// Create the path if needed
|
||||
path := filepath.Dir(datapath)
|
||||
_, err := os.Stat(path)
|
||||
if os.IsNotExist(err) {
|
||||
os.MkdirAll(path, os.ModePerm)
|
||||
} else {
|
||||
// Read the file
|
||||
_, err = os.Stat(datapath)
|
||||
if !os.IsNotExist(err) {
|
||||
if b, err := ioutil.ReadFile(datapath); err != nil {
|
||||
return fmt.Errorf("failed to read file %s error: %s", datapath, err)
|
||||
|
||||
} else if len(b) == 0 {
|
||||
return fmt.Errorf("file %s was empty, assumin fresh data", datapath)
|
||||
|
||||
} else if err := json.Unmarshal(b, &config); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal file %s error: %s", datapath, err)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Early bails
|
||||
// Early simple bails
|
||||
switch command {
|
||||
case "help":
|
||||
printUsage()
|
||||
return nil
|
||||
case "version":
|
||||
fmt.Println(version.Version)
|
||||
return nil
|
||||
case "config":
|
||||
}
|
||||
|
||||
// Load in the persistent file
|
||||
config, err := LoadConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Run config command before server needed
|
||||
if command == "config" {
|
||||
if len(args) > 1 {
|
||||
config.Host = args[1]
|
||||
SaveConfig(config)
|
||||
}
|
||||
fmt.Printf("host: %s\taccount: %s\n", config.Host, config.Accounts[config.Host])
|
||||
return nil
|
||||
}
|
||||
|
||||
// If there's a host set on the command line, override the one in the config
|
||||
if len(*host) != 0 {
|
||||
config.Host = *host
|
||||
// Allow overriding the host
|
||||
hostOverride := os.Getenv("ROVE_HOST")
|
||||
if len(hostOverride) > 0 {
|
||||
config.Host = hostOverride
|
||||
}
|
||||
|
||||
// If there's still no host, bail
|
||||
if len(config.Host) == 0 {
|
||||
return fmt.Errorf("no host set, please set one with -host")
|
||||
return fmt.Errorf("no host set, set one with '%s config {HOST}'", os.Args[0])
|
||||
}
|
||||
|
||||
// Set up the server
|
||||
|
@ -144,11 +174,12 @@ func InnerMain(command string) error {
|
|||
}
|
||||
|
||||
case "register":
|
||||
if len(*name) == 0 {
|
||||
return fmt.Errorf("must set name with -name")
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("must pass name to 'register'")
|
||||
}
|
||||
name := args[0]
|
||||
d := rove.RegisterRequest{
|
||||
Name: *name,
|
||||
Name: name,
|
||||
}
|
||||
_, err := client.Register(ctx, &d)
|
||||
switch {
|
||||
|
@ -156,19 +187,44 @@ func InnerMain(command string) error {
|
|||
return err
|
||||
|
||||
default:
|
||||
fmt.Printf("Registered account with id: %s\n", *name)
|
||||
config.Accounts[config.Host] = *name
|
||||
fmt.Printf("Registered account with id: %s\n", name)
|
||||
config.Accounts[config.Host] = name
|
||||
}
|
||||
|
||||
case "commands":
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("must pass commands to 'commands'")
|
||||
}
|
||||
|
||||
// Iterate through each command
|
||||
var commands []*rove.Command
|
||||
for i := 0; i < len(args); i++ {
|
||||
switch args[i] {
|
||||
case "move":
|
||||
i++
|
||||
if len(args) == i {
|
||||
return fmt.Errorf("move command must be passed bearing")
|
||||
} else if _, err := bearing.FromString(args[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
commands = append(commands,
|
||||
&rove.Command{
|
||||
Command: game.CommandMove,
|
||||
Bearing: args[i],
|
||||
},
|
||||
)
|
||||
case "stash":
|
||||
commands = append(commands,
|
||||
&rove.Command{
|
||||
Command: game.CommandStash,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
case "move":
|
||||
d := rove.CommandsRequest{
|
||||
Account: config.Accounts[config.Host],
|
||||
Commands: []*rove.Command{
|
||||
{
|
||||
Command: game.CommandMove,
|
||||
Bearing: *bearing,
|
||||
},
|
||||
},
|
||||
Account: config.Accounts[config.Host],
|
||||
Commands: commands,
|
||||
}
|
||||
|
||||
if err := verifyID(d.Account); err != nil {
|
||||
|
@ -222,30 +278,20 @@ func InnerMain(command string) error {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Save out the persistent file
|
||||
if b, err := json.MarshalIndent(config, "", "\t"); err != nil {
|
||||
return fmt.Errorf("failed to marshal data error: %s", err)
|
||||
} else if err := ioutil.WriteFile(datapath, b, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("failed to save file %s error: %s", datapath, err)
|
||||
}
|
||||
|
||||
SaveConfig(config)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Simple main
|
||||
func main() {
|
||||
flag.Usage = printUsage
|
||||
|
||||
// Bail without any args
|
||||
if len(os.Args) == 1 {
|
||||
printUsage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
flag.CommandLine.Parse(os.Args[2:])
|
||||
|
||||
// Run the inner main
|
||||
if err := InnerMain(os.Args[1]); err != nil {
|
||||
if err := InnerMain(os.Args[1], os.Args[1:]...); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
|
|
@ -3,10 +3,9 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
@ -15,42 +14,50 @@ import (
|
|||
|
||||
func Test_InnerMain(t *testing.T) {
|
||||
|
||||
// Use temporary local user data
|
||||
tmp, err := ioutil.TempDir(os.TempDir(), "rove-*")
|
||||
assert.NoError(t, err)
|
||||
os.Setenv("USER_DATA", tmp)
|
||||
|
||||
// Used for configuring this test
|
||||
var address = os.Getenv("ROVE_GRPC")
|
||||
if len(address) == 0 {
|
||||
log.Fatal("Must set $ROVE_GRPC")
|
||||
}
|
||||
|
||||
// Set up the flags to act locally and use a temporary file
|
||||
flag.Set("data", path.Join(os.TempDir(), uuid.New().String()))
|
||||
|
||||
// First attempt should error
|
||||
assert.Error(t, InnerMain("status"))
|
||||
|
||||
// Now set the host
|
||||
flag.Set("host", address)
|
||||
|
||||
// Then set the host
|
||||
// No error now as we have a host
|
||||
os.Setenv("ROVE_HOST", address)
|
||||
assert.NoError(t, InnerMain("status"))
|
||||
|
||||
// Set the host in the config
|
||||
assert.NoError(t, InnerMain("config", address))
|
||||
assert.NoError(t, InnerMain("status"))
|
||||
|
||||
// Register should fail without a name
|
||||
assert.Error(t, InnerMain("register"))
|
||||
|
||||
// These methods should fail without an account
|
||||
assert.Error(t, InnerMain("move"))
|
||||
assert.Error(t, InnerMain("radar"))
|
||||
assert.Error(t, InnerMain("rover"))
|
||||
|
||||
// Now set the name
|
||||
flag.Set("name", uuid.New().String())
|
||||
|
||||
// Perform the register
|
||||
assert.NoError(t, InnerMain("register"))
|
||||
assert.NoError(t, InnerMain("register", uuid.New().String()))
|
||||
|
||||
// These should now work
|
||||
assert.NoError(t, InnerMain("radar"))
|
||||
assert.NoError(t, InnerMain("rover"))
|
||||
|
||||
// Move should work with arguments
|
||||
flag.Set("bearing", "N")
|
||||
assert.NoError(t, InnerMain("move"))
|
||||
// Commands should fail with no commands
|
||||
assert.Error(t, InnerMain("commands"))
|
||||
|
||||
// Give it commands
|
||||
assert.NoError(t, InnerMain("commands", "move", "N"))
|
||||
assert.NoError(t, InnerMain("commands", "stash"))
|
||||
|
||||
// Give it malformed commands
|
||||
assert.Error(t, InnerMain("commands", "move", "stash"))
|
||||
}
|
||||
|
|
|
@ -78,10 +78,9 @@ func TestWorld_GetSetMovePosition(t *testing.T) {
|
|||
assert.Equal(t, pos, newPos, "Failed to correctly set position for rover")
|
||||
|
||||
b := bearing.North
|
||||
duration := 1
|
||||
newPos, err = world.MoveRover(a, b)
|
||||
assert.NoError(t, err, "Failed to set position for rover")
|
||||
pos.Add(vector.Vector{X: 0, Y: duration})
|
||||
pos.Add(vector.Vector{X: 0, Y: 1})
|
||||
assert.Equal(t, pos, newPos, "Failed to correctly move position for rover")
|
||||
|
||||
// Place a tile in front of the rover
|
||||
|
|
Loading…
Add table
Reference in a new issue