diff --git a/cmd/tasker/main.go b/cmd/tasker/main.go index ede66ad..e740412 100644 --- a/cmd/tasker/main.go +++ b/cmd/tasker/main.go @@ -9,40 +9,46 @@ import ( "github.com/adhocore/gronx/pkg/tasker" ) +var exit = os.Exit +var tick = time.Minute + +var opt tasker.Option + +func init() { + flag.StringVar(&opt.File, "file", "", "The task file in crontab format (without user)") + flag.StringVar(&opt.Tz, "tz", "Local", "The timezone to use for tasks") + flag.StringVar(&opt.Shell, "shell", tasker.Shell()[0], "The shell to use for running tasks") + flag.StringVar(&opt.Out, "out", "", "The fullpath to file where output from tasks are sent to") + flag.BoolVar(&opt.Verbose, "verbose", false, "The verbose mode outputs as much as possible") + flag.Int64Var(&opt.Until, "until", 0, "The timeout for task daemon in minutes") +} + func main() { - opt := mustGetOption() - taskr := tasker.New(opt) + mustParseOption() + taskr := tasker.New(opt) for _, task := range tasker.MustParseTaskfile(opt) { taskr.Task(task.Expr, taskr.Taskify(task.Cmd, opt)) } if opt.Until > 0 { - taskr.Until(time.Duration(opt.Until) * time.Minute) + taskr.Until(time.Duration(opt.Until) * tick) } taskr.Run() } -func mustGetOption() tasker.Option { - var opt tasker.Option - - flag.StringVar(&opt.File, "file", "", "The task file in crontab format") - flag.StringVar(&opt.Tz, "tz", "Local", "The timezone to use for tasks") - flag.StringVar(&opt.Shell, "shell", tasker.Shell()[0], "The shell to use for running tasks") - flag.StringVar(&opt.Out, "out", "", "The fullpath to file where output from tasks are sent to") - flag.BoolVar(&opt.Verbose, "verbose", false, "The verbose mode outputs as much as possible") - flag.Int64Var(&opt.Until, "until", 0, "The timeout for task daemon in minutes") +func mustParseOption() { + opt = tasker.Option{} flag.Parse() if opt.File == "" { flag.Usage() - os.Exit(1) + exit(1) } if _, err := os.Stat(opt.File); err != nil { - log.Fatalf("can't read taskfile: %s", opt.File) + log.Printf("can't read taskfile: %s", opt.File) + exit(1) } - - return opt } diff --git a/cmd/tasker/main_test.go b/cmd/tasker/main_test.go index 7209d62..b88ff67 100644 --- a/cmd/tasker/main_test.go +++ b/cmd/tasker/main_test.go @@ -3,22 +3,45 @@ package main import ( "os" "testing" + "time" "github.com/adhocore/gronx/pkg/tasker" ) func TestMustGetOption(t *testing.T) { old := os.Args + exit = func (code int) {} t.Run("Main", func(t *testing.T) { expect := tasker.Option{File: "../../test/taskfile.txt", Out: "../../test/out.txt"} - os.Args = append(os.Args, "-verbose", "-file", expect.File, "-out", expect.Out) - opt := mustGetOption() - os.Args = old + os.Args = append(old, "-verbose", "-file", expect.File, "-out", expect.Out) + mustParseOption() if opt.File != expect.File { t.Errorf("file: expected %v, got %v", opt.File, expect.File) } if opt.Out != expect.Out { t.Errorf("out: expected %v, got %v", opt.Out, expect.Out) } + + t.Run("must parse option", func (t *testing.T) { + os.Args = append(old, "-verbose", "-out", expect.Out) + mustParseOption() + if opt.File != "" { + t.Error("opt.File must be empty "+opt.File) + } + + os.Args = append(old, "-verbose", "-file", "invalid", "-out", expect.Out) + mustParseOption() + if opt.File != "invalid" { + t.Error("opt.File must be invalid") + } + }) + + t.Run("run", func (t *testing.T) { + tick = time.Second + os.Args = append(old, "-verbose", "-file", expect.File, "-out", expect.Out, "-until", "2") + main() + }) + + os.Args = old }) } diff --git a/gronx_test.go b/gronx_test.go index e3c2ce1..a854c4a 100644 --- a/gronx_test.go +++ b/gronx_test.go @@ -137,7 +137,7 @@ func testcases() []Case { {"0 0 * * 0,2-6", "2011-06-20 23:09:00", false, "2011-06-21 00:00"}, {"0 0 1 1 0", "2011-06-15 23:09:00", false, "2012-01-01 00:00"}, {"0 0 1 JAN 0", "2011-06-15 23:09:00", false, "2012-01-01 00:00"}, - {"0 0 1 * 0", "2011-06-15 23:09:00", false, "2012-01-01 00:00"}, // 1 + {"0 0 1 * 0", "2011-06-15 23:09:00", false, "2012-01-01 00:00"}, {"0 0 L * *", "2011-07-15 00:00:00", false, "2011-07-31 00:00"}, {"0 0 2W * *", "2011-07-01 00:00:00", true, "2011-08-02 00:00"}, {"0 0 1W * *", "2011-05-01 00:00:00", false, "2011-05-02 00:00"}, @@ -147,10 +147,10 @@ func testcases() []Case { {"0 0 28W * *", "2011-07-01 00:00:00", false, "2011-07-28 00:00"}, {"0 0 30W * *", "2011-07-01 00:00:00", false, "2011-07-29 00:00"}, {"0 0 31W * *", "2011-07-01 00:00:00", false, "2011-07-29 00:00"}, - {"* * * * * 2012", "2011-05-01 00:00:00", false, "err"}, + {"* * * * * 2012", "2011-05-01 00:00:00", false, "2012-05-01 00:00"}, // 1 {"* * * * 5L", "2011-07-01 00:00:00", false, "2011-07-29 00:00"}, {"* * * * 6L", "2011-07-01 00:00:00", false, "2011-07-30 00:00"}, - {"* * * * 7L", "2011-07-01 00:00:00", false, "2011-10-09 00:00"}, // 2 + {"* * * * 7L", "2011-07-01 00:00:00", false, "2011-10-09 00:00"}, {"* * * * 1L", "2011-07-24 00:00:00", false, "2011-07-25 00:00"}, {"* * * * TUEL", "2011-07-24 00:00:00", false, "2011-07-26 00:00"}, {"* * * 1 5L", "2011-12-25 00:00:00", false, "2012-01-27 00:00"}, @@ -161,8 +161,8 @@ func testcases() []Case { {"5/20 * * * *", "2018-08-13 00:24:00", false, "2018-08-13 00:25"}, {"5/20 * * * *", "2018-08-13 00:45:00", true, "2018-08-13 01:05"}, {"5-11/4 * * * *", "2018-08-13 00:03:00", false, "2018-08-13 00:05"}, - {"0 0 L * 0", "2011-06-15 23:09:00", false, "2011-07-31 00:00"}, // 3 - {"3-59/15 6-12 */15 1 2-5", "2017-01-08 00:00:00", false, "2018-01-30 06:03"}, // 4 + {"0 0 L * 0", "2011-06-15 23:09:00", false, "2011-07-31 00:00"}, + {"3-59/15 6-12 */15 1 2-5", "2017-01-08 00:00:00", false, "2018-01-30 06:03"}, {"* * * * MON-FRI", "2017-01-08 00:00:00", false, "2017-01-09 00:00"}, {"* * * * TUE", "2017-01-08 00:00:00", false, "2017-01-10 00:00"}, {"0 1 15 JUL mon,Wed,FRi", "2019-11-14 00:00:00", false, "2020-07-15 01:00"}, diff --git a/next.go b/next.go index 41ee5f3..54ee366 100644 --- a/next.go +++ b/next.go @@ -3,6 +3,7 @@ package gronx import ( "errors" "fmt" + "regexp" "strconv" "strings" "time" @@ -61,18 +62,24 @@ func loop(gron Gronx, segments []string, start time.Time, incl bool) (next time. return start, errors.New("tried so hard") } +var dashRe = regexp.MustCompile(`/.*$`) + func isPastYear(year string, ref time.Time, incl bool) bool { + if year == "*" || year == "?" { + return false + } + min := ref.Year() if !incl { min++ } for _, offset := range strings.Split(year, ",") { - if strings.Contains(offset, "/") && strings.Index(offset, "*/") != 0 && strings.Index(offset, "0/") != 0 { + if strings.Index(offset, "*/") == 0 || strings.Index(offset, "0/") == 0 { return false } - for _, part := range strings.Split(offset, "-") { + for _, part := range strings.Split(dashRe.ReplaceAllString(offset, ""), "-") { val, err := strconv.Atoi(part) - if err != nil || val > min { + if err != nil || val >= min { return false } } diff --git a/pkg/tasker/parser_test.go b/pkg/tasker/parser_test.go index 2a450e1..9c2ca02 100644 --- a/pkg/tasker/parser_test.go +++ b/pkg/tasker/parser_test.go @@ -7,8 +7,8 @@ import ( func TestMustParseTaskfile(t *testing.T) { t.Run("MustParseTaskfile", func(t *testing.T) { tasks := MustParseTaskfile(Option{File: "../../test/taskfile.txt"}) - if len(tasks) != 6 { - t.Errorf("should have 6 tasks, got %d", len(tasks)) + if len(tasks) != 8 { + t.Errorf("should have 8 tasks, got %d", len(tasks)) } if tasks[0].Expr != "*/1 0/1 * * *" { diff --git a/test/taskfile.txt b/test/taskfile.txt index e3244d0..06d271e 100644 --- a/test/taskfile.txt +++ b/test/taskfile.txt @@ -8,6 +8,10 @@ * * * * * echo '[task 5] * * * * *' > test/task5.out * * * * * echo '[task 6] it should go to outfile' && invalid-cmd +# failure tasks +@always xgronx +@always false + # below are invalid @invalid * * * * *