From 2942793f40d596eef7bde4868a0ec451d0982498 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Tue, 12 Apr 2022 10:35:29 +0200 Subject: [PATCH] Added support for x-y syntax --- src/cron/cron.v | 2 +- src/cron/expression.v | 60 +++++++++++++++++++++++++------- src/cron/expression_parse_test.v | 30 +++++++++++----- 3 files changed, 70 insertions(+), 22 deletions(-) diff --git a/src/cron/cron.v b/src/cron/cron.v index 74e203f..a049eec 100644 --- a/src/cron/cron.v +++ b/src/cron/cron.v @@ -29,7 +29,7 @@ pub fn cron(conf Config) ? { // println(queue) // exp := '10/2 5 *' // println(parse_expression(exp) ?) - ce := parse_expression('0 3 */2') ? + ce := parse_expression('0 35 */2') ? println(ce) // ce := CronExpression{ // minutes: [0] diff --git a/src/cron/expression.v b/src/cron/expression.v index 46f92f9..0bc1591 100644 --- a/src/cron/expression.v +++ b/src/cron/expression.v @@ -118,17 +118,28 @@ fn (ce &CronExpression) next_from_now() ?time.Time { // possible. fn parse_range(s string, min int, max int, mut bitv []bool) ? { mut start := min + mut end := max mut interval := 1 exps := s.split('/') + if exps.len > 2 { + return error('Invalid expression.') + } + if exps[0] != '*' { - start = exps[0].int() + dash_parts := exps[0].split('-') + + if dash_parts.len > 2 { + return error('Invalid expression.') + } + + start = dash_parts[0].int() // The builtin parsing functions return zero if the string can't be // parsed into a number, so we have to explicitely check whether they // actually entered zero or if it's an invalid number. - if start == 0 && exps[0] != '0' { + if start == 0 && dash_parts[0] != '0' { return error('Invalid number.') } @@ -136,6 +147,18 @@ fn parse_range(s string, min int, max int, mut bitv []bool) ? { if start < min || start > max { return error('Out of range.') } + + if dash_parts.len == 2 { + end = dash_parts[1].int() + + if end == 0 && dash_parts[1] != '0' { + return error('Invalid number.') + } + + if end < start || end > max { + return error('Out of range.') + } + } } if exps.len > 1 { @@ -146,7 +169,7 @@ fn parse_range(s string, min int, max int, mut bitv []bool) ? { if interval == 0 { if exps[1] != '0' { return error('Invalid number.') - }else{ + } else { return error('Step size zero not allowed.') } } @@ -157,12 +180,12 @@ fn parse_range(s string, min int, max int, mut bitv []bool) ? { } // Here, s solely consists of a number, so that's the only value we // should return. - else if exps[0] != '*' { + else if exps[0] != '*' && !exps[0].contains('-') { bitv[start - min] = true return } - for start <= max { + for start <= end { bitv[start - min] = true start += interval } @@ -171,7 +194,7 @@ fn parse_range(s string, min int, max int, mut bitv []bool) ? { fn bitv_to_ints(bitv []bool, min int) []int { mut out := []int{} - for i in 0..bitv.len { + for i in 0 .. bitv.len { if bitv[i] { out << min + i } @@ -181,7 +204,7 @@ fn bitv_to_ints(bitv []bool, min int) []int { } fn parse_part(s string, min int, max int) ?[]int { - mut bitv := []bool{init: false, len: max - min + 1} + mut bitv := []bool{len: max - min + 1, init: false} for range in s.split(',') { parse_range(range, min, max, mut bitv) ? @@ -190,7 +213,8 @@ fn parse_part(s string, min int, max int) ?[]int { return bitv_to_ints(bitv, min) } -// min hour day month day-of-week +// parse_expression parses an entire cron expression string into a +// CronExpression object, if possible. fn parse_expression(exp string) ?CronExpression { // The filter allows for multiple spaces between parts mut parts := exp.split(' ').filter(it != '') @@ -205,10 +229,22 @@ fn parse_expression(exp string) ?CronExpression { parts << '*' } + mut part_results := [][]int{} + + mins := [0, 0, 1, 1] + maxs := [59, 23, 31, 12] + + // This for loop allows us to more clearly propagate the error to the user. + for i, min in mins { + part_results << parse_part(parts[i], min, maxs[i]) or { + return error('An error occurred with part $i: $err.msg') + } + } + return CronExpression{ - minutes: parse_part(parts[0], 0, 59) ? - hours: parse_part(parts[1], 0, 23) ? - days: parse_part(parts[2], 1, 31) ? - months: parse_part(parts[3], 1, 12) ? + minutes: part_results[0] + hours: part_results[1] + days: part_results[2] + months: part_results[3] } } diff --git a/src/cron/expression_parse_test.v b/src/cron/expression_parse_test.v index 8f22850..8f3ac38 100644 --- a/src/cron/expression_parse_test.v +++ b/src/cron/expression_parse_test.v @@ -3,25 +3,23 @@ module cron // parse_range_error returns the returned error message. If the result is '', // that means the function didn't error. fn parse_range_error(s string, min int, max int) string { - mut bitv := []bool{init: false, len: max - min + 1} + mut bitv := []bool{len: max - min + 1, init: false} - parse_range(s, min, max, mut bitv) or { - return err.msg - } + parse_range(s, min, max, mut bitv) or { return err.msg } return '' } // =====parse_range===== fn test_range_star_range() ? { - mut bitv := []bool{init: false, len: 6} + mut bitv := []bool{len: 6, init: false} parse_range('*', 0, 5, mut bitv) ? assert bitv == [true, true, true, true, true, true] } fn test_range_number() ? { - mut bitv := []bool{init: false, len: 6} + mut bitv := []bool{len: 6, init: false} parse_range('4', 0, 5, mut bitv) ? assert bitv_to_ints(bitv, 0) == [4] @@ -40,14 +38,14 @@ fn test_range_number_invalid() ? { } fn test_range_step_star_1() ? { - mut bitv := []bool{init: false, len: 21} + mut bitv := []bool{len: 21, init: false} parse_range('*/4', 0, 20, mut bitv) ? assert bitv_to_ints(bitv, 0) == [0, 4, 8, 12, 16, 20] } fn test_range_step_star_2() ? { - mut bitv := []bool{init: false, len: 9} + mut bitv := []bool{len: 8, init: false} parse_range('*/3', 1, 8, mut bitv) ? assert bitv_to_ints(bitv, 1) == [1, 4, 7] @@ -62,7 +60,7 @@ fn test_range_step_zero() ? { } fn test_range_step_number() ? { - mut bitv := []bool{init: false, len: 21} + mut bitv := []bool{len: 21, init: false} parse_range('5/4', 2, 22, mut bitv) ? assert bitv_to_ints(bitv, 2) == [5, 9, 13, 17, 21] @@ -76,6 +74,20 @@ fn test_range_step_number_too_small() ? { assert parse_range_error('2/4', 5, 10) == 'Out of range.' } +fn test_range_dash() ? { + mut bitv := []bool{len: 10, init: false} + parse_range('4-8', 0, 9, mut bitv) ? + + assert bitv_to_ints(bitv, 0) == [4, 5, 6, 7, 8] +} + +fn test_range_dash_step() ? { + mut bitv := []bool{len: 10, init: false} + parse_range('4-8/2', 0, 9, mut bitv) ? + + assert bitv_to_ints(bitv, 0) == [4, 6, 8] +} + // =====parse_part===== fn test_part_single() ? { assert parse_part('*', 0, 5) ? == [0, 1, 2, 3, 4, 5]