|
| 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