Skip to content

Commit 8da0efd

Browse files
committedSep 7, 2013
split/rename cronexpr_impl.go
1 parent 8a4a15a commit 8da0efd

File tree

2 files changed

+485
-0
lines changed

2 files changed

+485
-0
lines changed
 

‎cronexpr_impl.go ‎cronexpr_next.go

File renamed without changes.

‎cronexpr_parse.go

+485
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,485 @@
1+
/*!
2+
* Copyright 2013 Raymond Hill
3+
*
4+
* Project: github.com/gorhill/cronexpr
5+
* File: cronexpr_parser.go
6+
* Version: 1.0
7+
* License: GPL v3 see <https://www.gnu.org/licenses/gpl.html>
8+
*
9+
*/
10+
11+
package cronexpr
12+
13+
/******************************************************************************/
14+
15+
import (
16+
"fmt"
17+
"regexp"
18+
"sort"
19+
"strings"
20+
)
21+
22+
/******************************************************************************/
23+
24+
var (
25+
genericDefaultList = []int{
26+
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
27+
10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
28+
20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
29+
30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
30+
40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
31+
50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
32+
}
33+
yearDefaultList = []int{
34+
1970, 1971, 1972, 1973, 1974, 1975, 1976, 1977, 1978, 1979,
35+
1980, 1981, 1982, 1983, 1984, 1985, 1986, 1987, 1988, 1989,
36+
1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999,
37+
2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
38+
2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
39+
2020, 2021, 2022, 2023, 2024, 2025, 2026, 2027, 2028, 2029,
40+
2030, 2031, 2032, 2033, 2034, 2035, 2036, 2037, 2038, 2039,
41+
2040, 2041, 2042, 2043, 2044, 2045, 2046, 2047, 2048, 2049,
42+
2050, 2051, 2052, 2053, 2054, 2055, 2056, 2057, 2058, 2059,
43+
2060, 2061, 2062, 2063, 2064, 2065, 2066, 2067, 2068, 2069,
44+
2070, 2071, 2072, 2073, 2074, 2075, 2076, 2077, 2078, 2079,
45+
2080, 2081, 2082, 2083, 2084, 2085, 2086, 2087, 2088, 2089,
46+
2090, 2091, 2092, 2093, 2094, 2095, 2096, 2097, 2098, 2099,
47+
}
48+
)
49+
50+
/******************************************************************************/
51+
52+
var (
53+
numberTokens = map[string]int{
54+
"0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9,
55+
"10": 10, "11": 11, "12": 12, "13": 13, "14": 14, "15": 15, "16": 16, "17": 17, "18": 18, "19": 19,
56+
"20": 20, "21": 21, "22": 22, "23": 23, "24": 24, "25": 25, "26": 26, "27": 27, "28": 28, "29": 29,
57+
"30": 30, "31": 31, "32": 32, "33": 33, "34": 34, "35": 35, "36": 36, "37": 37, "38": 38, "39": 39,
58+
"40": 40, "41": 41, "42": 42, "43": 43, "44": 44, "45": 45, "46": 46, "47": 47, "48": 48, "49": 49,
59+
"50": 50, "51": 51, "52": 52, "53": 53, "54": 54, "55": 55, "56": 56, "57": 57, "58": 58, "59": 59,
60+
"1970": 1970, "1971": 1971, "1972": 1972, "1973": 1973, "1974": 1974, "1975": 1975, "1976": 1976, "1977": 1977, "1978": 1978, "1979": 1979,
61+
"1980": 1980, "1981": 1981, "1982": 1982, "1983": 1983, "1984": 1984, "1985": 1985, "1986": 1986, "1987": 1987, "1988": 1988, "1989": 1989,
62+
"1990": 1990, "1991": 1991, "1992": 1992, "1993": 1993, "1994": 1994, "1995": 1995, "1996": 1996, "1997": 1997, "1998": 1998, "1999": 1999,
63+
"2000": 2000, "2001": 2001, "2002": 2002, "2003": 2003, "2004": 2004, "2005": 2005, "2006": 2006, "2007": 2007, "2008": 2008, "2009": 2009,
64+
"2010": 2010, "2011": 2011, "2012": 2012, "2013": 2013, "2014": 2014, "2015": 2015, "2016": 2016, "2017": 2017, "2018": 2018, "2019": 2019,
65+
"2020": 2020, "2021": 2021, "2022": 2022, "2023": 2023, "2024": 2024, "2025": 2025, "2026": 2026, "2027": 2027, "2028": 2028, "2029": 2029,
66+
"2030": 2030, "2031": 2031, "2032": 2032, "2033": 2033, "2034": 2034, "2035": 2035, "2036": 2036, "2037": 2037, "2038": 2038, "2039": 2039,
67+
"2040": 2040, "2041": 2041, "2042": 2042, "2043": 2043, "2044": 2044, "2045": 2045, "2046": 2046, "2047": 2047, "2048": 2048, "2049": 2049,
68+
"2050": 2050, "2051": 2051, "2052": 2052, "2053": 2053, "2054": 2054, "2055": 2055, "2056": 2056, "2057": 2057, "2058": 2058, "2059": 2059,
69+
"2060": 2060, "2061": 2061, "2062": 2062, "2063": 2063, "2064": 2064, "2065": 2065, "2066": 2066, "2067": 2067, "2068": 2068, "2069": 2069,
70+
"2070": 2070, "2071": 2071, "2072": 2072, "2073": 2073, "2074": 2074, "2075": 2075, "2076": 2076, "2077": 2077, "2078": 2078, "2079": 2079,
71+
"2080": 2080, "2081": 2081, "2082": 2082, "2083": 2083, "2084": 2084, "2085": 2085, "2086": 2086, "2087": 2087, "2088": 2088, "2089": 2089,
72+
"2090": 2090, "2091": 2091, "2092": 2092, "2093": 2093, "2094": 2094, "2095": 2095, "2096": 2096, "2097": 2097, "2098": 2098, "2099": 2099,
73+
}
74+
monthTokens = map[string]int{
75+
`1`: 1, `jan`: 1, `january`: 1,
76+
`2`: 2, `feb`: 2, `february`: 2,
77+
`3`: 3, `mar`: 3, `march`: 3,
78+
`4`: 4, `apr`: 4, `april`: 4,
79+
`5`: 5, `may`: 5,
80+
`6`: 6, `jun`: 6, `june`: 6,
81+
`7`: 7, `jul`: 7, `july`: 7,
82+
`8`: 8, `aug`: 8, `august`: 8,
83+
`9`: 9, `sep`: 9, `september`: 9,
84+
`10`: 10, `oct`: 10, `october`: 10,
85+
`11`: 11, `nov`: 11, `november`: 11,
86+
`12`: 12, `dec`: 12, `december`: 12,
87+
}
88+
dowTokens = map[string]int{
89+
`0`: 0, `sun`: 0, `sunday`: 0,
90+
`1`: 1, `mon`: 1, `monday`: 1,
91+
`2`: 2, `tue`: 2, `tuesday`: 2,
92+
`3`: 3, `wed`: 3, `wednesday`: 3,
93+
`4`: 4, `thu`: 4, `thursday`: 4,
94+
`5`: 5, `fri`: 5, `friday`: 5,
95+
`6`: 6, `sat`: 6, `saturday`: 6,
96+
`7`: 0,
97+
}
98+
)
99+
100+
/******************************************************************************/
101+
102+
func atoi(s string) int {
103+
return numberTokens[s]
104+
}
105+
106+
type fieldDescriptor struct {
107+
name string
108+
min, max int
109+
defaultList []int
110+
valuePattern string
111+
atoi func(string) int
112+
}
113+
114+
var (
115+
secondDescriptor = fieldDescriptor{
116+
name: "second",
117+
min: 0,
118+
max: 59,
119+
defaultList: genericDefaultList[0:60],
120+
valuePattern: `[0-9]|[1-5][0-9]`,
121+
atoi: atoi,
122+
}
123+
minuteDescriptor = fieldDescriptor{
124+
name: "minute",
125+
min: 0,
126+
max: 59,
127+
defaultList: genericDefaultList[0:60],
128+
valuePattern: `[0-9]|[1-5][0-9]`,
129+
atoi: atoi,
130+
}
131+
hourDescriptor = fieldDescriptor{
132+
name: "hour",
133+
min: 0,
134+
max: 23,
135+
defaultList: genericDefaultList[0:24],
136+
valuePattern: `[0-9]|1[0-9]|2[0-3]`,
137+
atoi: atoi,
138+
}
139+
domDescriptor = fieldDescriptor{
140+
name: "day-of-month",
141+
min: 1,
142+
max: 31,
143+
defaultList: genericDefaultList[1:32],
144+
valuePattern: `[1-9]|[12][0-9]|3[01]`,
145+
atoi: atoi,
146+
}
147+
monthDescriptor = fieldDescriptor{
148+
name: "month",
149+
min: 1,
150+
max: 12,
151+
defaultList: genericDefaultList[1:13],
152+
valuePattern: `[1-9]|1[012]|jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec|january|february|march|april|march|april|june|july|august|september|october|november|december`,
153+
atoi: func(s string) int {
154+
return monthTokens[s]
155+
},
156+
}
157+
dowDescriptor = fieldDescriptor{
158+
name: "day-of-week",
159+
min: 0,
160+
max: 6,
161+
defaultList: genericDefaultList[0:7],
162+
valuePattern: `[0-7]|sun|mon|tue|wed|thu|fri|sat|sunday|monday|tuesday|wednesday|thursday|friday|saturday`,
163+
atoi: func(s string) int {
164+
return dowTokens[s]
165+
},
166+
}
167+
yearDescriptor = fieldDescriptor{
168+
name: "year",
169+
min: 1970,
170+
max: 2099,
171+
defaultList: yearDefaultList[:],
172+
valuePattern: `19[789][0-9]|20[0-9]{2}`,
173+
atoi: atoi,
174+
}
175+
)
176+
177+
/******************************************************************************/
178+
179+
var (
180+
layoutWildcard = `^\*|\?$`
181+
layoutValue = `^(%value%)$`
182+
layoutRange = `^(%value%)-(%value%)$`
183+
layoutWildcardAndInterval = `^\*/(\d+)$`
184+
layoutValueAndInterval = `^(%value%)/(\d+)$`
185+
layoutRangeAndInterval = `^(%value%)-(%value%)/(\d+)$`
186+
layoutLastDom = `^l$`
187+
layoutWorkdom = `^(%value%)w$`
188+
layoutLastWorkdom = `^lw$`
189+
layoutDowOfLastWeek = `^(%value%)l$`
190+
layoutDowOfSpecificWeek = `^(%value%)#([1-5])$`
191+
fieldFinder = regexp.MustCompile(`\S+`)
192+
entryFinder = regexp.MustCompile(`[^,]+`)
193+
layoutRegexp = make(map[string]*regexp.Regexp)
194+
)
195+
196+
/******************************************************************************/
197+
198+
var cronNormalizer = strings.NewReplacer(
199+
"@yearly", "0 0 0 1 1 * *",
200+
"@annually", "0 0 0 1 1 * *",
201+
"@monthly", "0 0 0 1 * * *",
202+
"@weekly", "0 0 0 * * 0 *",
203+
"@daily", "0 0 0 * * * *",
204+
"@hourly", "0 0 * * * * *")
205+
206+
/******************************************************************************/
207+
208+
func (expr *Expression) secondFieldHandler(s string) error {
209+
var err error
210+
expr.secondList, err = genericFieldHandler(s, secondDescriptor)
211+
return err
212+
}
213+
214+
/******************************************************************************/
215+
216+
func (expr *Expression) minuteFieldHandler(s string) error {
217+
var err error
218+
expr.minuteList, err = genericFieldHandler(s, minuteDescriptor)
219+
return err
220+
}
221+
222+
/******************************************************************************/
223+
224+
func (expr *Expression) hourFieldHandler(s string) error {
225+
var err error
226+
expr.hourList, err = genericFieldHandler(s, hourDescriptor)
227+
return err
228+
}
229+
230+
/******************************************************************************/
231+
232+
func (expr *Expression) monthFieldHandler(s string) error {
233+
var err error
234+
expr.monthList, err = genericFieldHandler(s, monthDescriptor)
235+
return err
236+
}
237+
238+
/******************************************************************************/
239+
240+
func (expr *Expression) yearFieldHandler(s string) error {
241+
var err error
242+
expr.yearList, err = genericFieldHandler(s, yearDescriptor)
243+
return err
244+
}
245+
246+
/******************************************************************************/
247+
248+
const (
249+
none = 0
250+
one = 1
251+
span = 2
252+
all = 3
253+
)
254+
255+
type cronDirective struct {
256+
kind int
257+
first int
258+
last int
259+
step int
260+
sbeg int
261+
send int
262+
}
263+
264+
func genericFieldHandler(s string, desc fieldDescriptor) ([]int, error) {
265+
directives, err := genericFieldParse(s, desc)
266+
if err != nil {
267+
return nil, err
268+
}
269+
values := make(map[int]bool)
270+
for _, directive := range directives {
271+
switch directive.kind {
272+
case none:
273+
return nil, fmt.Errorf("syntax error in %s field: '%s'", desc.name, s[directive.sbeg:directive.send])
274+
case one:
275+
populateOne(values, directive.first)
276+
case span:
277+
populateMany(values, directive.first, directive.last, directive.step)
278+
case all:
279+
return desc.defaultList, nil
280+
}
281+
}
282+
return toList(values), nil
283+
}
284+
285+
func (expr *Expression) dowFieldHandler(s string) error {
286+
expr.daysOfWeekRestricted = true
287+
expr.daysOfWeek = make(map[int]bool)
288+
expr.lastWeekDaysOfWeek = make(map[int]bool)
289+
expr.specificWeekDaysOfWeek = make(map[int]bool)
290+
291+
directives, err := genericFieldParse(s, dowDescriptor)
292+
if err != nil {
293+
return err
294+
}
295+
296+
for _, directive := range directives {
297+
switch directive.kind {
298+
case none:
299+
sdirective := s[directive.sbeg:directive.send]
300+
snormal := strings.ToLower(sdirective)
301+
// `5L`
302+
pairs := makeLayoutRegexp(layoutDowOfLastWeek, dowDescriptor.valuePattern).FindStringSubmatchIndex(snormal)
303+
if len(pairs) > 0 {
304+
populateOne(expr.lastWeekDaysOfWeek, dowDescriptor.atoi(snormal[pairs[2]:pairs[3]]))
305+
} else {
306+
// `5#3`
307+
pairs := makeLayoutRegexp(layoutDowOfSpecificWeek, dowDescriptor.valuePattern).FindStringSubmatchIndex(snormal)
308+
if len(pairs) > 0 {
309+
populateOne(expr.specificWeekDaysOfWeek, (dowDescriptor.atoi(snormal[pairs[4]:pairs[5]])-1)*7+(dowDescriptor.atoi(snormal[pairs[2]:pairs[3]])%7))
310+
} else {
311+
return fmt.Errorf("syntax error in day-of-week field: '%s'", sdirective)
312+
}
313+
}
314+
case one:
315+
populateOne(expr.daysOfWeek, directive.first)
316+
case span:
317+
populateMany(expr.daysOfWeek, directive.first, directive.last, directive.step)
318+
case all:
319+
populateMany(expr.daysOfWeek, directive.first, directive.last, directive.step)
320+
expr.daysOfWeekRestricted = false
321+
}
322+
}
323+
return nil
324+
}
325+
326+
func (expr *Expression) domFieldHandler(s string) error {
327+
expr.daysOfMonthRestricted = true
328+
expr.lastDayOfMonth = false
329+
expr.lastWorkdayOfMonth = false
330+
expr.daysOfMonth = make(map[int]bool) // days of month map
331+
expr.workdaysOfMonth = make(map[int]bool) // work days of month map
332+
333+
directives, err := genericFieldParse(s, domDescriptor)
334+
if err != nil {
335+
return err
336+
}
337+
338+
for _, directive := range directives {
339+
switch directive.kind {
340+
case none:
341+
sdirective := s[directive.sbeg:directive.send]
342+
snormal := strings.ToLower(sdirective)
343+
// `L`
344+
if makeLayoutRegexp(layoutLastDom, domDescriptor.valuePattern).MatchString(snormal) {
345+
expr.lastDayOfMonth = true
346+
} else {
347+
// `LW`
348+
if makeLayoutRegexp(layoutLastWorkdom, domDescriptor.valuePattern).MatchString(snormal) {
349+
expr.lastWorkdayOfMonth = true
350+
} else {
351+
// `15W`
352+
pairs := makeLayoutRegexp(layoutWorkdom, domDescriptor.valuePattern).FindStringSubmatchIndex(snormal)
353+
if len(pairs) > 0 {
354+
populateOne(expr.workdaysOfMonth, domDescriptor.atoi(snormal[pairs[2]:pairs[3]]))
355+
} else {
356+
return fmt.Errorf("syntax error in day-of-month field: '%s'", sdirective)
357+
}
358+
}
359+
}
360+
case one:
361+
populateOne(expr.daysOfMonth, directive.first)
362+
case span:
363+
populateMany(expr.daysOfMonth, directive.first, directive.last, directive.step)
364+
case all:
365+
populateMany(expr.daysOfMonth, directive.first, directive.last, directive.step)
366+
expr.daysOfMonthRestricted = false
367+
}
368+
}
369+
return nil
370+
}
371+
372+
/******************************************************************************/
373+
374+
func genericFieldParse(s string, desc fieldDescriptor) ([]*cronDirective, error) {
375+
// At least one entry must be present
376+
indices := entryFinder.FindAllStringIndex(s, -1)
377+
if len(indices) == 0 {
378+
return nil, fmt.Errorf("%s field: missing directive", desc.name)
379+
}
380+
381+
directives := make([]*cronDirective, 0, len(indices))
382+
383+
for i := range indices {
384+
directive := cronDirective{
385+
sbeg: indices[i][0],
386+
send: indices[i][1],
387+
}
388+
snormal := strings.ToLower(s[indices[i][0]:indices[i][1]])
389+
// `*`
390+
if makeLayoutRegexp(layoutWildcard, desc.valuePattern).MatchString(snormal) {
391+
directive.kind = all
392+
directive.first = desc.min
393+
directive.last = desc.max
394+
directive.step = 1
395+
directives = append(directives, &directive)
396+
continue
397+
}
398+
// `5`
399+
if makeLayoutRegexp(layoutValue, desc.valuePattern).MatchString(snormal) {
400+
directive.kind = one
401+
directive.first = desc.atoi(snormal)
402+
directives = append(directives, &directive)
403+
continue
404+
}
405+
// `5-20`
406+
pairs := makeLayoutRegexp(layoutRange, desc.valuePattern).FindStringSubmatchIndex(snormal)
407+
if len(pairs) > 0 {
408+
directive.kind = span
409+
directive.first = desc.atoi(snormal[pairs[2]:pairs[3]])
410+
directive.last = desc.atoi(snormal[pairs[4]:pairs[5]])
411+
directive.step = 1
412+
directives = append(directives, &directive)
413+
continue
414+
}
415+
// `*/2`
416+
pairs = makeLayoutRegexp(layoutWildcardAndInterval, desc.valuePattern).FindStringSubmatchIndex(snormal)
417+
if len(pairs) > 0 {
418+
directive.kind = span
419+
directive.first = desc.min
420+
directive.last = desc.max
421+
directive.step = atoi(snormal[pairs[2]:pairs[3]])
422+
directives = append(directives, &directive)
423+
continue
424+
}
425+
// `5/2`
426+
pairs = makeLayoutRegexp(layoutValueAndInterval, desc.valuePattern).FindStringSubmatchIndex(snormal)
427+
if len(pairs) > 0 {
428+
directive.kind = span
429+
directive.first = desc.atoi(snormal[pairs[2]:pairs[3]])
430+
directive.last = desc.max
431+
directive.step = atoi(snormal[pairs[4]:pairs[5]])
432+
directives = append(directives, &directive)
433+
continue
434+
}
435+
// `5-20/2`
436+
pairs = makeLayoutRegexp(layoutRangeAndInterval, desc.valuePattern).FindStringSubmatchIndex(snormal)
437+
if len(pairs) > 0 {
438+
directive.kind = span
439+
directive.first = desc.atoi(snormal[pairs[2]:pairs[3]])
440+
directive.last = desc.atoi(snormal[pairs[4]:pairs[5]])
441+
directive.step = atoi(snormal[pairs[6]:pairs[7]])
442+
directives = append(directives, &directive)
443+
continue
444+
}
445+
// No behavior for this one, let caller deal with it
446+
directive.kind = none
447+
directives = append(directives, &directive)
448+
}
449+
return directives, nil
450+
}
451+
452+
/******************************************************************************/
453+
454+
func populateOne(values map[int]bool, v int) {
455+
values[v] = true
456+
}
457+
458+
func populateMany(values map[int]bool, min, max, step int) {
459+
for i := min; i <= max; i += step {
460+
values[i] = true
461+
}
462+
}
463+
464+
func toList(set map[int]bool) []int {
465+
list := make([]int, len(set))
466+
i := 0
467+
for k := range set {
468+
list[i] = k
469+
i += 1
470+
}
471+
sort.Ints(list)
472+
return list
473+
}
474+
475+
/******************************************************************************/
476+
477+
func makeLayoutRegexp(layout, value string) *regexp.Regexp {
478+
layout = strings.Replace(layout, `%value%`, value, -1)
479+
re := layoutRegexp[layout]
480+
if re == nil {
481+
re = regexp.MustCompile(layout)
482+
layoutRegexp[layout] = re
483+
}
484+
return re
485+
}

0 commit comments

Comments
 (0)
Please sign in to comment.