Skip to content

Commit

Permalink
Merge pull request #11 from tamada/parsing_args
Browse files Browse the repository at this point in the history
ref #1 implement args parsing routine
  • Loading branch information
tamada committed Jun 7, 2021
2 parents d74dcd2 + b27efc4 commit 9bfde41
Show file tree
Hide file tree
Showing 7 changed files with 234 additions and 7 deletions.
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 *~
134 changes: 134 additions & 0 deletions args.go
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
}
46 changes: 46 additions & 0 deletions args_test.go
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)
}
}
}
19 changes: 16 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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!! (<NUMBER> <UNIT> 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)
Expand Down
2 changes: 1 addition & 1 deletion main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
27 changes: 27 additions & 0 deletions timer.go
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
}
}
7 changes: 7 additions & 0 deletions timer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package main

import "testing"

func TestDuration(t *testing.T) {

}

0 comments on commit 9bfde41

Please sign in to comment.