From 6ac6c7756f1b0f18b7594a00525757b741070f11 Mon Sep 17 00:00:00 2001 From: Haruaki Tamada Date: Mon, 7 Jun 2021 09:57:22 +0900 Subject: [PATCH 1/3] ref #1 implement args parsing routine --- args.go | 134 +++++++++++++++++++++++++++++++++++++++++++++++++++ args_test.go | 46 ++++++++++++++++++ main.go | 19 ++++++-- main_test.go | 2 +- 4 files changed, 197 insertions(+), 4 deletions(-) create mode 100644 args.go create mode 100644 args_test.go diff --git a/args.go b/args.go new file mode 100644 index 0000000..84d8282 --- /dev/null +++ b/args.go @@ -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(" 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 +} diff --git a/args_test.go b/args_test.go new file mode 100644 index 0000000..8b8b76d --- /dev/null +++ b/args_test.go @@ -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) + } + } +} diff --git a/main.go b/main.go index 9515aad..cd488a6 100644 --- a/main.go +++ b/main.go @@ -17,18 +17,31 @@ OPTIONS Available units are: nsec, msec, sec, min, and hour. -h, --help prints this message. NUMBER - specifies the number for timer. + specifies the number for timer by the integer value. COMMANDS specifies the commands execute after timer. If no commands are specified, nml notifies by printing message "MESSAGE FROM NML ARRIVE!! ( left)" to STDOUT.`, programName, programName) } -func goMain(args []string) int { - fmt.Println(helpMessage(args[0])) +func perform(opts *options) int { return 0 } +func goMain(args []string) int { + opts, err := parseArgs(args) + if err != nil { + fmt.Printf("parsing args fail: %s\n", err.Error()) + fmt.Println(helpMessage(filepath.Base(args[0]))) + return 1 + } + if opts.help { + fmt.Println(helpMessage(filepath.Base(args[0]))) + return 0 + } + return perform(opts) +} + func main() { status := goMain(os.Args) os.Exit(status) diff --git a/main_test.go b/main_test.go index 819b805..b834b91 100644 --- a/main_test.go +++ b/main_test.go @@ -3,7 +3,7 @@ package main import "testing" func Test_main(t *testing.T) { - status := goMain([]string{"nml"}) + status := goMain([]string{"nml", "-h"}) if status != 0 { t.Errorf("status code wont 0, but got %d", status) } From 93adc82106db4f89ac61611c896a38827a74fe0f Mon Sep 17 00:00:00 2001 From: Haruaki Tamada Date: Mon, 7 Jun 2021 10:02:17 +0900 Subject: [PATCH 2/3] add timer --- timer.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 timer.go diff --git a/timer.go b/timer.go new file mode 100644 index 0000000..5824f96 --- /dev/null +++ b/timer.go @@ -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 + } +} From b27efc49b37b9001114aa68bd1797a4fa0a9d27e Mon Sep 17 00:00:00 2001 From: Haruaki Tamada Date: Mon, 7 Jun 2021 10:07:09 +0900 Subject: [PATCH 3/3] update build routine in Makefile --- Makefile | 6 +++--- timer_test.go | 7 +++++++ 2 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 timer_test.go diff --git a/Makefile b/Makefile index c33ab40..4e2e206 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ test: setup define __create_dist() mkdir -p dist/$(1)_$(2)/$(DIST) - GOOS=$1 GOARCH=$2 go build -o dist/$(1)_$(2)/$(DIST)/$(NAME)$(3) main.go + GOOS=$1 GOARCH=$2 go build -o dist/$(1)_$(2)/$(DIST)/$(NAME)$(3) main.go args.go timer.go cp -r README.md LICENSE dist/$(1)_$(2)/$(DIST) tar cvfz dist/$(DIST)_$(1)_$(2).tar.gz -C dist/$(1)_$(2) $(DIST) endef @@ -24,8 +24,8 @@ dist: all @$(call __create_dist,windows,amd64,.exe) @$(call __create_dist,linux,amd64,) -build: main.go - go build -o $(NAME) -v main.go +build: main.go args.go timer.go + go build -o $(NAME) -v main.go args.go timer.go clean: @rm -f nml *~ diff --git a/timer_test.go b/timer_test.go new file mode 100644 index 0000000..7c0a033 --- /dev/null +++ b/timer_test.go @@ -0,0 +1,7 @@ +package main + +import "testing" + +func TestDuration(t *testing.T) { + +}