Skip to content

Commit

Permalink
- Add geolocation for the 'from address' #7
Browse files Browse the repository at this point in the history
- Added 'list' command to view saved locations
  • Loading branch information
KyleBanks committed Feb 22, 2017
1 parent dc85959 commit 509c5e1
Show file tree
Hide file tree
Showing 19 changed files with 806 additions and 304 deletions.
12 changes: 6 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Builds the commuter CLI.
build:
@go build github.com/KyleBanks/commuter
.PHONY: build
# Builds and installs the commuter CLI.
install:
@go install github.com/KyleBanks/commuter
.PHONY: install

# Runs an example commuter request for travel duration.
example: | build
@./commuter -to "Toronto, Canada"
example: | install
@commuter -to "Toronto, Canada"
.PHONY: example

# Runs test suit, vet, golint, and fmt.
Expand Down
34 changes: 29 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ $ go get github.com/KyleBanks/commuter

The first time you run `commuter`, you'll be prompted to provide a [Google Maps API Key](https://developers.google.com/console) and default location.

**Important:** Ensure you enable the *Google Maps Distance Matrix API* at [https://developers.google.com/console](https://developers.google.com/console).
**Important:** Ensure you enable the *Google Maps Distance Matrix API* at [developers.google.com/console](https://developers.google.com/console). If you want to use the `-from-current` and `-to-current` flags, you will also need to enable the *Google Maps Geolocation API*.

```sh
$ commuter
Expand All @@ -40,20 +40,22 @@ Next, request your commute time:
```sh
# From your default to a specific location:
$ commuter -to "321 Maple Ave. Toronto, Ontario"
> 32 Minutes
32 Minutes

# From a specific location to your default:
$ commuter -from "Toronto, Ontario"
> 20 Minutes
20 Minutes
```

If you want a commute time beginning and ending somewhere other than your default location, you can use supply full locations for both the `-from` and `-to` flags:

```sh
$ commuter -from "123 Main St. Toronto, Ontario" -to "321 Maple Ave. Toronto, Ontario"
> 32 Minutes
32 Minutes
```

### `commuter add`

You can also add names for your frequent locations like so:

```sh
Expand All @@ -65,7 +67,29 @@ And use them as the `from` and/or `to` location:

```sh
$ commuter -from home -to work
> 32 Minutes
32 Minutes
```

### `commuter list`

To see a list of all your named locations:

```sh
& commuter list
default: 123 Main St. Toronto, Ontario
gym: 1024 Fitness Lane Toronto, Ontario
work: 321 Maple Ave. Toronto, Ontario
```

### Using Your Current Location

If you [enabled](https://developers.google.com/console) the *Google Maps Geolocation API* for your API key, you can use the `-from-current` and `-to-current` flags to use your current location. This is done by attempting to use your IP Address to determine your latitude and longitude, and use that as either the start or destination of your commute:

```sh
$ commuter -from-current -to work
32 Minutes
$ commuter -from gym -to-current
12 Minutes
```

## License
Expand Down
27 changes: 14 additions & 13 deletions cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,23 @@ import (
)

const (
cmdDefault = "commuter"
defaultFromParam = "from"
defaultFromUsage = `The starting point of your commute, either a named location [ex. "work"] or an address [ex. "123 Main St. Toronto, Canada"].`
defaultToParam = "to"
defaultToUsage = `The destination of your commute, either a named location [ex. "work"] or an address [ex. "123 Main St. Toronto, Canada"].`
cmdDefault = "commuter"
defaultFromParam = "from"
defaultFromUsage = "The starting point of your commute, either a named location [ex. 'work'] or an address [ex. '123 Main St. Toronto, Canada']."
defaultToParam = "to"
defaultToUsage = "The destination of your commute, either a named location [ex. 'work'] or an address [ex. '123 Main St. Toronto, Canada']."
defaultFromCurrentParam = "from-current"
defaultFromCurrentUsage = "Sets your current location as the starting point of your commute. This uses Geolocation to attempt to determine your Latitude/Longitude based on IP Address. [Accuracy may vary]"
defaultToCurrentParam = "to-current"
defaultToCurrentUsage = "Sets your current location as the destination of your commute. This uses Geolocation to attempt to determine your Latitude/Longitude based on IP Address. [Accuracy may vary]"

cmdAdd = "add"
addNameParam = "name"
addNameUsage = `The name of the location you'd like to add [ex. "work"]. (required)`
addNameUsage = `The name of the location you'd like to add [ex. "work"]. (required)\n`
addLocationParam = "location"
addLocationUsage = `The location to be added [ex. "123 Main St. Toronto, Canada"]. (required)`
addLocationUsage = `The location to be added [ex. "123 Main St. Toronto, Canada"]. (required)\n`

cmdList = "list"
)

// Stdout provides an output mechanism to notify the user via stdout.
Expand All @@ -36,12 +42,7 @@ func NewStdout() Stdout {
}

// Indicate prints an indication to the user.
func (s Stdout) Indicate(v interface{}) {
s.Indicatef("%v", v)
}

// Indicatef prints an indication to the user with formatting.
func (s Stdout) Indicatef(msg string, args ...interface{}) {
func (s Stdout) Indicate(msg string, args ...interface{}) {
fmt.Fprintf(s, "%v\n", fmt.Sprintf(msg, args...))
}

Expand Down
28 changes: 1 addition & 27 deletions cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,32 +15,6 @@ func TestNewStdout(t *testing.T) {
}

func TestStdout_Indicate(t *testing.T) {
tests := []struct {
v interface{}
expected string
}{
{"", "\n"},
{"hey", "hey\n"},
{1, "1\n"},
{struct{ test string }{"test"}, "{test}\n"},
}

for idx, tt := range tests {
var b bytes.Buffer
s := Stdout{
Writer: &b,
}

s.Indicate(tt.v)
out := b.String()

if out != tt.expected {
t.Fatalf("[#%v] Unexpected output, expected=%v, got=%v", idx, tt.expected, out)
}
}
}

func TestStdout_Indicatef(t *testing.T) {
tests := []struct {
msg string
args []interface{}
Expand All @@ -57,7 +31,7 @@ func TestStdout_Indicatef(t *testing.T) {
Writer: &b,
}

s.Indicatef(tt.msg, tt.args...)
s.Indicate(tt.msg, tt.args...)
out := b.String()

if out != tt.expected {
Expand Down
11 changes: 10 additions & 1 deletion cli/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ func (a *ArgParser) Parse(conf *cmd.Configuration, s cmd.StorageProvider) (cmd.R
switch a.Args[0] {
case cmdAdd:
return a.parseAddCmd(s, a.Args[1:])
case cmdList:
return a.parseListCmd(s, a.Args[1:])
}

return a.parseCommuteCmd(conf, a.Args)
Expand All @@ -49,11 +51,13 @@ func (a *ArgParser) parseCommuteCmd(conf *cmd.Configuration, args []string) (*cm
return nil, err
}

c := cmd.CommuteCmd{Durationer: r}
c := cmd.CommuteCmd{Durationer: r, Locator: r}

f := flag.NewFlagSet(cmdDefault, flag.ExitOnError)
f.StringVar(&c.From, defaultFromParam, cmd.DefaultLocationAlias, defaultFromUsage)
f.BoolVar(&c.FromCurrent, defaultFromCurrentParam, false, defaultFromCurrentUsage)
f.StringVar(&c.To, defaultToParam, cmd.DefaultLocationAlias, defaultToUsage)
f.BoolVar(&c.ToCurrent, defaultToCurrentParam, false, defaultToCurrentUsage)
f.Parse(args)

return &c, nil
Expand All @@ -70,3 +74,8 @@ func (a *ArgParser) parseAddCmd(s cmd.StorageProvider, args []string) (*cmd.AddC

return &c, nil
}

// parseListCmd parses and returns a ListCmd.
func (a *ArgParser) parseListCmd(s cmd.StorageProvider, args []string) (*cmd.ListCmd, error) {
return &cmd.ListCmd{}, nil
}
16 changes: 16 additions & 0 deletions cli/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,19 @@ func TestArgParser_Parse(t *testing.T) {
{[]string{"-to", "123 Etc Ave.", "-from", "home"}, &conf, &cmd.CommuteCmd{}},
{[]string{"-to", "work", "-from", "123 Etc Ave."}, &conf, &cmd.CommuteCmd{}},
{[]string{"-to", "321 Example Drive", "-from", "123 Etc Ave."}, &conf, &cmd.CommuteCmd{}},
{[]string{"-to", "321 Example Drive", "-from-current"}, &conf, &cmd.CommuteCmd{}},
{[]string{"-to-current", "-from", "123 Etc Ave."}, &conf, &cmd.CommuteCmd{}},
{[]string{"-to-current", "-from-current"}, &conf, &cmd.CommuteCmd{}},

// Add command
{[]string{"add"}, &conf, &cmd.AddCmd{}},
{[]string{"add", "-name", "work"}, &conf, &cmd.AddCmd{}},
{[]string{"add", "-name", "work", "-location", "123 Sample Lane"}, &conf, &cmd.AddCmd{}},

// List command
{[]string{"list"}, &conf, &cmd.ListCmd{}},
{[]string{"list", "-arg"}, &conf, &cmd.ListCmd{}},

// Empty args should prompt a ConfigureCommand
{[]string{}, &conf, &cmd.ConfigureCmd{}},

Expand Down Expand Up @@ -121,6 +128,9 @@ func TestArgParser_parseCommuteCmd(t *testing.T) {
{[]string{"-to", "123 Etc Ave.", "-from", "home"}, cmd.CommuteCmd{To: "123 Etc Ave.", From: "home"}},
{[]string{"-to", "work", "-from", "123 Etc Ave."}, cmd.CommuteCmd{To: "work", From: "123 Etc Ave."}},
{[]string{"-to", "321 Example Drive", "-from", "123 Etc Ave."}, cmd.CommuteCmd{To: "321 Example Drive", From: "123 Etc Ave."}},
{[]string{"-to", "321 Example Drive", "-from-current"}, cmd.CommuteCmd{To: "321 Example Drive", FromCurrent: true, From: "default"}},
{[]string{"-to-current", "-from", "123 Etc Ave."}, cmd.CommuteCmd{ToCurrent: true, From: "123 Etc Ave.", To: "default"}},
{[]string{"-to-current", "-from-current"}, cmd.CommuteCmd{ToCurrent: true, FromCurrent: true, To: "default", From: "default"}},
}

for idx, tt := range tests {
Expand All @@ -133,8 +143,14 @@ func TestArgParser_parseCommuteCmd(t *testing.T) {
t.Fatalf("[%v] Unexpected 'To' parsed, expected=%v, got=%v", idx, tt.expected.To, r.To)
} else if tt.expected.From != r.From {
t.Fatalf("[%v] Unexpected 'From' parsed, expected=%v, got=%v", idx, tt.expected.From, r.From)
} else if tt.expected.FromCurrent != r.FromCurrent {
t.Fatalf("[%v] Unexpected 'FromCurrent' parsed, expected=%v, got=%v", idx, tt.expected.FromCurrent, r.FromCurrent)
} else if tt.expected.ToCurrent != r.ToCurrent {
t.Fatalf("[%v] Unexpected 'ToCurrent' parsed, expected=%v, got=%v", idx, tt.expected.ToCurrent, r.ToCurrent)
} else if r.Durationer == nil {
t.Fatalf("[%v] Unexpected nil Durationer", idx)
} else if r.Locator == nil {
t.Fatalf("[%v] Unexpected nil Locator", idx)
}
}
}
Expand Down
44 changes: 44 additions & 0 deletions cmd/add.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package cmd

import (
"errors"
"fmt"
)

var (
// ErrAddNameMissing is returned when running the add command the the -name arugment is missing.
ErrAddNameMissing = errors.New("missing -name parameter")
// ErrAddLocationMissing is returned when running the add command the the -location arugment is missing.
ErrAddLocationMissing = errors.New("missing -location parameter")
)

// AddCmd represents a command to add a named location.
type AddCmd struct {
Name string
Value string

Store StorageProvider
}

// Run adds the named location, overwriting the existing value if necessary.
func (a *AddCmd) Run(conf *Configuration, i Indicator) error {
conf.Locations[a.Name] = a.Value
return a.Store.Save(conf)
}

// Validate validates the AddCmd is properly initialized and ready to be Run.
func (a *AddCmd) Validate(conf *Configuration) error {
if len(a.Name) == 0 {
return ErrAddNameMissing
}
if len(a.Value) == 0 {
return ErrAddLocationMissing
}

return nil
}

// String returns a string representation of the AddCmd.
func (a *AddCmd) String() string {
return fmt.Sprintf("Adding named location '%v' with value '%v'", a.Name, a.Value)
}
73 changes: 73 additions & 0 deletions cmd/add_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package cmd

import (
"errors"
"testing"
)

func TestAddCmd_Run(t *testing.T) {
tests := []struct {
conf *Configuration

name string
value string
}{
{&Configuration{Locations: make(map[string]string)}, "name", "value"},
{&Configuration{Locations: map[string]string{"name": "value1"}}, "name", "value2"},
}

for idx, tt := range tests {
m := mockStorageProvider{
saveFn: func(i interface{}) error {
if i != tt.conf {
t.Fatalf("[#%v] Unexpected parameter to Save, got=%v", idx, i)
}
return nil
},
}
a := AddCmd{Name: tt.name, Value: tt.value, Store: &m}

if err := a.Run(tt.conf, nil); err != nil {
t.Fatal(err)
}

if tt.conf.Locations[tt.name] != tt.value {
t.Fatalf("[#%v] Unexpected value stored, expected=%v, got=%v", idx, tt.value, tt.conf.Locations[tt.name])
}
}

// Negative
{
testErr := errors.New("mock err")
var m mockStorageProvider
m.saveFn = func(i interface{}) error {
return testErr
}

conf := Configuration{Locations: make(map[string]string)}
a := AddCmd{Name: "name", Value: "value", Store: &m}
if err := a.Run(&conf, nil); err != testErr {
t.Fatalf("Unexpected error, expected=%v, got=%v", testErr, err)
}
}
}

func TestAddCmd_Validate(t *testing.T) {
tests := []struct {
name string
value string
err error
}{
{"name", "value", nil},
{"", "value", ErrAddNameMissing},
{"name", "", ErrAddLocationMissing},
}

for idx, tt := range tests {
a := AddCmd{Name: tt.name, Value: tt.value}

if err := a.Validate(nil); err != tt.err {
t.Fatalf("[#%v] Unexpected error, expected=%v, got=%v", idx, tt.err, err)
}
}
}
Loading

0 comments on commit 509c5e1

Please sign in to comment.