Skip to content

Commit 15cd304

Browse files
committed
Initial commit
- current working pattern, broken into pieces as separate files - working tests + test helper script - working build script, to compile multiline pattern into single line
0 parents  commit 15cd304

12 files changed

+2061
-0
lines changed

README.md

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# AWS EventBridge Schedule Expression Validation with Regular Expressions
2+
3+
As this is intended for use in CloudFormation templates (as an `AllowedPattern`), we are aiming for compatibility with [Java's regular expression syntax](https://docs.oracle.com/javase/6/docs/api/java/util/regex/Pattern.html).
4+
5+
However, due to the massive complexity and sheer scope of the overall expression we are maintiaining here, it will be broken up into various pieces for the `cron()` portion and versioned as native Perl regex.
6+
7+
The most recent working version of this pattern is also available (with unit tests!) on [Regex101](https://regex101.com/r/YHscM3/2).
8+
9+
## Project Structure
10+
11+
The complete pattern can be found in `main.pl`, in two different forms and assigned to two different global variables:
12+
13+
- `$expanded` - here, the complete pattern is broken out across multiple lines and commented heavily (using the `/x` flag) for readability
14+
- `$collapsed` - here, any non-leading and non-trailing comments and whitespace have been removed, making it fit for copy and paste elsewhere
15+
16+
Due to the complexity of the `cron()` matching, each individual _field_ of the cron format has been broken out into its own file under the `cron` directory:
17+
18+
```
19+
./cron
20+
├── daysofmonth.pl
21+
├── daysofweek.pl
22+
├── hours.pl
23+
├── minutes.pl
24+
├── months.pl
25+
└── years.pl
26+
```
27+
28+
These individual field patterns are then merged back into the main pattern manually.
29+
30+
## Testing
31+
32+
Currently, several valid `rate` and `cron` patterns are recorded as "positive" test cases as well as several invalid patterns as "negative" cases in tab delimited files under the `tests` directory.
33+
34+
You can test them against the latest complete patterns (again, housed in `main.pl`) by running the test script:
35+
36+
```shell
37+
perl test.pl
38+
# outputs TAP formatted results...
39+
```
40+
41+
## Building
42+
43+
The current multiline pattern can be condensed into a single line format (and compared against the currently versioned collapsed pattern) by running the build script:
44+
45+
> Note: you may need to install the `Text::Diff` CPAN module in order for this script to run successfully
46+
47+
```shell
48+
perl build.pl
49+
# prints a diff if anything in the main pattern has changed
50+
# subsequently prints the condensed pattern to stdout
51+
```

build.pl

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
use strict;
2+
use warnings;
3+
4+
use Term::ANSIColor qw(:constants);
5+
use Text::Diff;
6+
7+
require "./main.pl";
8+
our ($expanded, $collapsed);
9+
10+
# remove the wrappings from qr//
11+
my $pattern = $expanded =~ s/(^\(\?\^x\:\n+|\n+\)$)//rg;
12+
13+
# remove any whole single line comments (at ANY indent)
14+
$pattern =~ s/\n([ \t]*[#][^\n]+\n)+/\n/g;
15+
16+
# remove any indents
17+
$pattern =~ s/^[ \t]*//gm;
18+
19+
# remove rest of newlines
20+
$pattern =~ s/\n//g;
21+
22+
my %options = (
23+
STYLE => "Unified",
24+
OUTPUT => \*STDOUT,
25+
FILENAME_A => 'Existing',
26+
FILENAME_B => 'New'
27+
);
28+
29+
# get existing collapsed pattern to compare against
30+
my $expected = $collapsed =~ s/(^\(\?\^x\:\n+|\n+\)$)//rg;
31+
32+
if ($expected eq $pattern) {
33+
print BRIGHT_GREEN ON_BLACK "No changes detected!\n", RESET;
34+
} else {
35+
print BRIGHT_RED ON_BLACK "Differences detected:\n", RESET;
36+
diff \$expected, \$pattern, \%options;
37+
}
38+
39+
print "\nCompiled pattern:\n\n$pattern\n\n";

cron/daysofmonth.pl

+204
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
# full field
2+
/
3+
#### top-level days of month field
4+
# field, any number of expressions broken by literal comma
5+
(?:
6+
### first unit expression, either range of two units or increment of a single unit
7+
## first single day of month unit
8+
(?:
9+
(?:
10+
# number (roughly) between 1-31
11+
(?:
12+
# single digit num from 0-9 OR double digit num whose first digit is between 1 and 3
13+
[1-3]?
14+
[0-9]
15+
)
16+
# followed by zero or one W wildcard (weekday)
17+
W?
18+
)
19+
|
20+
# OR ordered combo of L and W wildcards (last weekday of month)
21+
LW
22+
|
23+
# OR standalone wildcard of L (last day of month) or asterisk (any)
24+
[L*]
25+
)
26+
## end single day of month unit
27+
# optional second unit (range) or increment
28+
(?:
29+
# range, a literal dash followed by ANOTHER single unit
30+
(?:
31+
[-]
32+
## second single day of month unit
33+
(?:
34+
(?:
35+
# number (roughly) between 1-31
36+
(?:
37+
# single digit num from 0-9 OR double digit num whose first digit is between 1 and 3
38+
[1-3]?
39+
[0-9]
40+
)
41+
# followed by zero or one W wildcard (weekday)
42+
W?
43+
)
44+
|
45+
# OR ordered combo of L and W wildcards (last weekday of month)
46+
LW
47+
|
48+
# OR standalone wildcard of L (last day of month) or asterisk (any)
49+
[L*]
50+
)
51+
## end second single day of month unit
52+
)
53+
|
54+
# OR increment, a literal slash followed by integer increment value
55+
(?:
56+
[/]
57+
[0-9]+
58+
)
59+
)?
60+
### end first unit expression
61+
(?:
62+
[,]
63+
### second unit expression, either range of two units or increment of a single unit
64+
## first single day of month unit
65+
(?:
66+
(?:
67+
# number (roughly) between 1-31
68+
(?:
69+
# single digit num from 0-9 OR double digit num whose first digit is between 1 and 3
70+
[1-3]?
71+
[0-9]
72+
)
73+
# followed by zero or one W wildcard (weekday)
74+
W?
75+
)
76+
|
77+
# OR ordered combo of L and W wildcards (last weekday of month)
78+
LW
79+
|
80+
# OR standalone wildcard of L (last day of month) or asterisk (any)
81+
[L*]
82+
)
83+
## end single day of month unit
84+
# optional second unit (range) or increment
85+
(?:
86+
# range, a literal dash followed by ANOTHER single unit
87+
(?:
88+
[-]
89+
## second single day of month unit
90+
(?:
91+
(?:
92+
# number (roughly) between 1-31
93+
(?:
94+
# single digit num from 0-9 OR double digit num whose first digit is between 1 and 3
95+
[1-3]?
96+
[0-9]
97+
)
98+
# followed by zero or one W wildcard (weekday)
99+
W?
100+
)
101+
|
102+
# OR ordered combo of L and W wildcards (last weekday of month)
103+
LW
104+
|
105+
# OR standalone wildcard of L (last day of month) or asterisk (any)
106+
[L*]
107+
)
108+
## end second single day of month unit
109+
)
110+
|
111+
# OR increment, a literal slash followed by integer increment value
112+
(?:
113+
[/]
114+
[0-9]+
115+
)
116+
)?
117+
### end second unit expression
118+
)*
119+
)
120+
#### end top-level days of month field
121+
/x;
122+
123+
# unit expression
124+
/
125+
### unit expression, either range of two units or increment of a single unit
126+
## first single day of month unit
127+
(?:
128+
(?:
129+
# number (roughly) between 1-31
130+
(?:
131+
# single digit num from 0-9 OR double digit num whose first digit is between 1 and 3
132+
[1-3]?
133+
[0-9]
134+
)
135+
# followed by zero or one W wildcard (weekday)
136+
W?
137+
)
138+
|
139+
# OR ordered combo of L and W wildcards (last weekday of month)
140+
LW
141+
|
142+
# OR standalone wildcard of L (last day of month) or asterisk (any)
143+
[L*]
144+
)
145+
## end single day of month unit
146+
# optional second unit (range) or increment
147+
(?:
148+
# range, a literal dash followed by ANOTHER single unit
149+
(?:
150+
[-]
151+
## second single day of month unit
152+
(?:
153+
(?:
154+
# number (roughly) between 1-31
155+
(?:
156+
# single digit num from 0-9 OR double digit num whose first digit is between 1 and 3
157+
[1-3]?
158+
[0-9]
159+
)
160+
# followed by zero or one W wildcard (weekday)
161+
W?
162+
)
163+
|
164+
# OR ordered combo of L and W wildcards (last weekday of month)
165+
LW
166+
|
167+
# OR standalone wildcard of L (last day of month) or asterisk (any)
168+
[L*]
169+
)
170+
## end second single day of month unit
171+
)
172+
|
173+
# OR increment, a literal slash followed by integer increment value
174+
(?:
175+
[/]
176+
[0-9]+
177+
)
178+
)?
179+
### end unit expression
180+
/x;
181+
182+
# single unit
183+
/
184+
## single day of month unit
185+
(?:
186+
(?:
187+
# number (roughly) between 1-31
188+
(?:
189+
# single digit num from 0-9 OR double digit num whose first digit is between 1 and 3
190+
[1-3]?
191+
[0-9]
192+
)
193+
# followed by zero or one W wildcard (weekday)
194+
W?
195+
)
196+
|
197+
# OR ordered combo of L and W wildcards (last weekday of month)
198+
LW
199+
|
200+
# OR standalone wildcard of L (last day of month) or asterisk (any)
201+
[L*]
202+
)
203+
## end single day of month unit
204+
/x;

0 commit comments

Comments
 (0)