-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #11 from tamada/parsing_args
ref #1 implement args parsing routine
- Loading branch information
Showing
7 changed files
with
234 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
package main | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"os" | ||
"os/exec" | ||
"strconv" | ||
"strings" | ||
|
||
flag "github.com/spf13/pflag" | ||
) | ||
|
||
// Command shows the executing command after time. | ||
type Command interface { | ||
Execute() int | ||
} | ||
|
||
type printCommand struct { | ||
message string | ||
} | ||
|
||
func (pc *printCommand) Execute() int { | ||
fmt.Println(pc.message) | ||
return 0 | ||
} | ||
|
||
type cliCommand struct { | ||
args []string | ||
} | ||
|
||
func (cc *cliCommand) Execute() int { | ||
cmd := cc.args[0] | ||
args := []string{} | ||
if len(cc.args) > 1 { | ||
args = cc.args[1:] | ||
} | ||
command := exec.Command(cmd, args...) | ||
return execImpl(command) | ||
} | ||
|
||
func execImpl(cmd *exec.Cmd) int { | ||
cmd.Stdin = os.Stdin | ||
cmd.Stdout = os.Stdout | ||
cmd.Stderr = os.Stderr | ||
err := cmd.Run() | ||
if err != nil { | ||
fmt.Printf("exec error: %s\n", err.Error()) | ||
return 1 | ||
} | ||
return cmd.ProcessState.ExitCode() | ||
} | ||
|
||
type options struct { | ||
time *timer | ||
header bool | ||
background bool | ||
help bool | ||
command Command | ||
} | ||
|
||
func applyNumber(opts *options, args []string) error { | ||
if len(args) <= 1 { | ||
return errors.New("<NUMBER> is mandatory.") | ||
} | ||
value, err := strconv.ParseInt(args[1], 10, 64) | ||
if err != nil { | ||
return fmt.Errorf("%s: %w", args[1], err) | ||
} | ||
opts.time.number = value | ||
return nil | ||
} | ||
|
||
func applyGivenCommands(opts *options, args []string, index int) (*options, error) { | ||
if index >= len(args) { | ||
return nil, fmt.Errorf("no commands were given") | ||
} | ||
opts.command = &cliCommand{args: args[index:]} | ||
return opts, nil | ||
} | ||
|
||
func applyDefaultMessage(opts *options, args []string) (*options, error) { | ||
time := opts.time | ||
opts.command = &printCommand{message: fmt.Sprintf("MESSAGE FROM NML ARRIVE!! (%d %s left)", time.number, time.unit)} | ||
return opts, nil | ||
} | ||
|
||
func applyArguments(opts *options, args []string, original []string) (*options, error) { | ||
err := applyNumber(opts, args) | ||
if err != nil { | ||
return nil, err | ||
} | ||
for i, arg := range original { | ||
if arg == "--" { | ||
return applyGivenCommands(opts, original, i+1) | ||
} | ||
} | ||
return applyDefaultMessage(opts, args) | ||
} | ||
|
||
func validate(time *timer) error { | ||
availableUnits := []string{"nsec", "usec", "msec", "sec", "min", "hour"} | ||
value := strings.TrimSpace(strings.ToLower(time.unit)) | ||
for _, available := range availableUnits { | ||
if value == available { | ||
time.unit = available | ||
return nil | ||
} | ||
} | ||
return fmt.Errorf("%s: unknown unit", time.unit) | ||
} | ||
|
||
func parseArguments(opts *options, args []string, original []string) (*options, error) { | ||
err := validate(opts.time) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return applyArguments(opts, args, original) | ||
} | ||
|
||
func parseArgs(args []string) (*options, error) { | ||
opts := &options{time: &timer{}, header: false, help: false} | ||
flags := flag.NewFlagSet("nml", flag.ContinueOnError) | ||
flags.Usage = func() { fmt.Println(helpMessage(args[0])) } | ||
flags.StringVarP(&opts.time.unit, "unit", "u", "min", "specifies the time unit. Default is min") | ||
flags.BoolVarP(&opts.header, "with-header", "H", false, "shows the header on notification") | ||
flags.BoolVarP(&opts.help, "help", "h", false, "prints this message") | ||
flags.Parse(args) | ||
if !opts.help { | ||
// pflags が "--" を解釈し,削除してしまうため,オリジナルの args も渡している. | ||
return parseArguments(opts, flags.Args(), args) | ||
} | ||
return opts, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package main | ||
|
||
import "testing" | ||
|
||
func TestParseArguments(t *testing.T) { | ||
testdata := []struct { | ||
giveArgs []string | ||
wontHeader bool | ||
wontBackground bool | ||
wontHelp bool | ||
wontError bool | ||
message string | ||
}{ | ||
{[]string{"nml"}, false, false, false, true, "NUMBER is mandatory"}, | ||
{[]string{"nml", "not_a_number"}, false, false, false, true, "not a number"}, | ||
{[]string{"nml", "10"}, false, false, false, false, "success"}, | ||
{[]string{"nml", "-h"}, false, false, true, false, "success with help"}, | ||
{[]string{"nml", "-h", "--background"}, false, false, true, false, "success with help"}, | ||
{[]string{"nml", "-H", "10"}, true, false, false, false, "success with header"}, | ||
{[]string{"nml", "10", "--", "echo", "hoge"}, false, false, false, false, "success with command"}, | ||
{[]string{"nml", "-u", "unknown_unit", "10"}, false, false, false, true, "parsing fail: the unknown unit"}, | ||
{[]string{"nml", "10", "--"}, false, false, false, true, "parsing fail: no commands given"}, | ||
{[]string{"nml", "-unknown-flag"}, false, false, false, true, "parsing fail: the unknown flag"}, | ||
} | ||
for _, td := range testdata { | ||
opts, err := parseArgs(td.giveArgs) | ||
if (err == nil) && td.wontError { | ||
t.Errorf("parseArgs(%v) wont error, but got no error: %s", td.giveArgs, td.message) | ||
} | ||
if err != nil && !td.wontError { | ||
t.Errorf("parseArgs(%v) wont no error, but got error: %s (%s)", td.giveArgs, err.Error(), td.message) | ||
} | ||
if err != nil { | ||
continue | ||
} | ||
if opts.background != td.wontBackground { | ||
t.Errorf("parseArgs(%v) background did not match, wont %v, but got %v", td.giveArgs, td.wontBackground, opts.background) | ||
} | ||
if opts.header != td.wontHeader { | ||
t.Errorf("parseArgs(%v) header did not match, wont %v, but got %v", td.giveArgs, td.wontHeader, opts.header) | ||
} | ||
if opts.help != td.wontHelp { | ||
t.Errorf("parseArgs(%v) help did not match, wont %v, but got %v", td.giveArgs, td.wontHelp, opts.help) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package main | ||
|
||
import "time" | ||
|
||
type timer struct { | ||
number int64 | ||
unit string | ||
} | ||
|
||
func (t *timer) Duration() time.Duration { | ||
switch t.unit { | ||
case "min": | ||
return time.Duration(t.number) * time.Minute | ||
case "nsec": | ||
return time.Duration(t.number) * time.Nanosecond | ||
case "usec": // micro seconds | ||
return time.Duration(t.number) * time.Nanosecond * time.Duration(1000) | ||
case "msec": | ||
return time.Duration(t.number) * time.Nanosecond * time.Duration(1000000) | ||
case "hour": | ||
return time.Duration(t.number) * time.Hour | ||
case "sec": | ||
return time.Duration(t.number) * time.Second | ||
default: | ||
return time.Second | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package main | ||
|
||
import "testing" | ||
|
||
func TestDuration(t *testing.T) { | ||
|
||
} |