Skip to content

Commit f05cba2

Browse files
committed
Allow lists of slashes that include ranges. Allow day of week to prefix L.
1 parent c4f8bf5 commit f05cba2

File tree

3 files changed

+91
-67
lines changed

3 files changed

+91
-67
lines changed

.github/workflows/publish-to-test-pypi.yml

Lines changed: 60 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -8,62 +8,63 @@ jobs:
88
runs-on: ubuntu-latest
99

1010
steps:
11-
- uses: actions/checkout@master
12-
- name: 🐍 Set up Python 3.7
13-
uses: actions/setup-python@v3
14-
with:
15-
python-version: "3.7"
16-
- name: 🛠 Install dev requirements
17-
run: >-
18-
python -m
19-
pip install
20-
--requirement requirements_dev.txt
21-
--user
22-
- name: 🧪 Python linting
23-
run: >-
24-
python -m
25-
pylint
26-
./src
27-
./tests
28-
- name: 🧪 Python format checking
29-
run: >-
30-
python -m
31-
black
32-
./src
33-
./tests
34-
-l 120
35-
- name: 🧪 Python type checking
36-
run: >-
37-
python -m
38-
mypy
39-
--pretty
40-
--show-error-codes
41-
--show-error-context
42-
./src
43-
./tests
44-
- name: 🧪 Python testing & coverage
45-
run: >-
46-
python -m
47-
pytest
48-
--cov
49-
- name: 🧰 Build binary wheel & source tarball
50-
run: >-
51-
python -m
52-
build
53-
--sdist
54-
--wheel
55-
--outdir dist/
56-
- name: 📦 Publish package to Test PyPI
57-
uses: pypa/gh-action-pypi-publish@release/v1
58-
with:
59-
password: ${{ secrets.TEST_PYPI_API_TOKEN }}
60-
repository_url: https://test.pypi.org/legacy/
61-
verbose: true
62-
skip_existing: true
63-
- name: 📦 Publish package to PyPI
64-
if: startsWith(github.ref, 'refs/tags/v')
65-
uses: pypa/gh-action-pypi-publish@release/v1
66-
with:
67-
password: ${{ secrets.PYPI_API_TOKEN }}
68-
verbose: true
69-
skip_existing: true
11+
- uses: actions/checkout@master
12+
- name: 🐍 Set up Python 3.7
13+
uses: actions/setup-python@v3
14+
with:
15+
python-version: "3.7"
16+
- name: 🛠 Install dev requirements
17+
run: >-
18+
python -m
19+
pip install
20+
--requirement requirements_dev.txt
21+
--user
22+
- name: 🧪 Python linting
23+
run: >-
24+
python -m
25+
pylint
26+
./src
27+
./tests
28+
- name: 🧪 Python format checking
29+
run: >-
30+
python -m
31+
black
32+
./src
33+
./tests
34+
-l 120
35+
--check
36+
- name: 🧪 Python type checking
37+
run: >-
38+
python -m
39+
mypy
40+
--pretty
41+
--show-error-codes
42+
--show-error-context
43+
./src
44+
./tests
45+
- name: 🧪 Python testing & coverage
46+
run: >-
47+
python -m
48+
pytest
49+
--cov
50+
- name: 🧰 Build binary wheel & source tarball
51+
run: >-
52+
python -m
53+
build
54+
--sdist
55+
--wheel
56+
--outdir dist/
57+
- name: 📦 Publish package to Test PyPI
58+
uses: pypa/gh-action-pypi-publish@release/v1
59+
with:
60+
password: ${{ secrets.TEST_PYPI_API_TOKEN }}
61+
repository_url: https://test.pypi.org/legacy/
62+
verbose: true
63+
skip_existing: true
64+
- name: 📦 Publish package to PyPI
65+
if: startsWith(github.ref, 'refs/tags/v')
66+
uses: pypa/gh-action-pypi-publish@release/v1
67+
with:
68+
password: ${{ secrets.PYPI_API_TOKEN }}
69+
verbose: true
70+
skip_existing: true

src/aws_cron_expression_validator/validator.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ def range_regex(cls, values: str) -> str:
9494
return rf"({values}|(\*\-{values})|({values}\-{values})|({values}\-\*))" # v , *-v , v-v or v-*
9595

9696
@classmethod
97-
def list_regex(cls, values: str) -> str:
97+
def list_range_regex(cls, values: str) -> str:
9898
range_ = cls.range_regex(values)
9999
return rf"({range_}(\,{range_})*)" # One or more ranges separated by a comma
100100

@@ -105,9 +105,15 @@ def slash_regex(cls, values: str) -> str:
105105
# Slash can be preceded by *, range, or a valid value and must be followed by a natural
106106
# number as the increment.
107107

108+
@classmethod
109+
def list_slash_regex(cls, values: str) -> str:
110+
slash = cls.slash_regex(values)
111+
slash_or_range = rf"({slash}|{cls.range_regex(values)})"
112+
return rf"({slash_or_range}(\,{slash_or_range})*)" # One or more separated by a comma
113+
108114
@classmethod
109115
def common_regex(cls, values: str) -> str:
110-
return rf"({cls.list_regex(values)}|\*|{cls.slash_regex(values)})" # values , - * /
116+
return rf"({cls.list_range_regex(values)}|\*|{cls.list_slash_regex(values)})" # values , - * /
111117

112118
@classmethod
113119
def minute_regex(cls) -> str:
@@ -129,7 +135,8 @@ def month_regex(cls):
129135

130136
@classmethod
131137
def day_of_week_regex(cls):
132-
return rf"^({cls.list_regex(cls.day_of_week_values)}|\*|\?|L|{cls.day_of_week_hash})$" # values , - * ? L #
138+
range_list = cls.list_range_regex(cls.day_of_week_values)
139+
return rf"^({range_list}|\*|\?|{cls.day_of_week_values}L|L|{cls.day_of_week_hash})$" # values , - * ? L #
133140

134141
@classmethod
135142
def year_regex(cls):

tests/aws_cron_expression_validator/test_validator.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ def _check_non_match(self, regex: str, match: str):
2323
def test_slash_regex(self):
2424
minute_values = r"(0?[0-9]|[1-5][0-9])" # [0]0-59
2525
given_regex = validator.AWSCronExpressionValidator.slash_regex(minute_values)
26-
given_valid_matches = ["*/10", "1/10", "0/05", "0/15", "0/30", "55/1"]
26+
given_valid_matches = ["*/10", "1/10", "0/05", "0/15", "0/30", "55/1", "1-7/2"]
27+
2728
given_invalid_matches = [
2829
"",
2930
"/",
@@ -44,15 +45,23 @@ def test_slash_regex(self):
4445
self._then_matches(given_regex, given_valid_matches)
4546
self._then_does_not_match(given_regex, given_invalid_matches)
4647

48+
def test_list_slash_regex(self):
49+
given_regex = validator.AWSCronExpressionValidator.list_slash_regex(r"[B-Y]")
50+
given_valid_matches = ["B", "B/2,C-D/2", "*/10,*/3"]
51+
given_valid_matches = ["B/2", "B/2,C/2", "B/2,C/2,D/2", "*/10,*/3", "B,C/2,D/2", "B/2,C,D/2", "C/2,D-T/2"]
52+
given_invalid_matches = ["*/10,*/3,"]
53+
self._then_matches(given_regex, given_valid_matches)
54+
self._then_does_not_match(given_regex, given_invalid_matches)
55+
4756
def test_range_regex(self):
4857
given_regex = validator.AWSCronExpressionValidator.range_regex(r"[B-Y]")
4958
given_valid_matches = ["B", "*-D", "D-*", "B-Y", "D-F", "D-B"]
5059
given_invalid_matches = ["*-*", "*B", "A-D", "D-Z", "B-C-D", ""]
5160
self._then_matches(given_regex, given_valid_matches)
5261
self._then_does_not_match(given_regex, given_invalid_matches)
5362

54-
def test_list_regex(self):
55-
given_regex = validator.AWSCronExpressionValidator.list_regex(r"[B-Y]")
63+
def test_list_range_regex(self):
64+
given_regex = validator.AWSCronExpressionValidator.list_range_regex(r"[B-Y]")
5665
given_valid_matches = ["D", "D,F", "F-D", "D,F,C,J", "D-F,J", "B,D-F,J", "D,F-J", "*-R,X", "R,X-*", "*-R,T,X-*"]
5766
given_invalid_matches = ["*,P", "P,*,Q", "Q-P,*", "", "D,F,", ",D,F", ""]
5867
self._then_matches(given_regex, given_valid_matches)
@@ -120,15 +129,14 @@ def test_month_regex(self):
120129

121130
def test_day_of_week_regex(self):
122131
given_regex = validator.AWSCronExpressionValidator.day_of_week_regex()
123-
given_valid_matches = ["*", "1", "5", "7", "MON", "?", "L", "3#2", "MON-FRI"]
132+
given_valid_matches = ["*", "1", "5", "7", "MON", "?", "L", "5L", "MONL", "3#2", "MON-FRI"]
124133
given_invalid_matches = [
125134
"Monday",
126135
"0",
127136
"01",
128137
"8",
129138
"?5",
130139
"5?",
131-
"5L",
132140
"5-L",
133141
"L5",
134142
"#",
@@ -182,6 +190,7 @@ def test_validator(self):
182190
valid_expressions = [
183191
"0 18 ? * MON-FRI *",
184192
"0 18 ? * L *",
193+
"0 18 ? * SATL *",
185194
"0 18 L * ? *",
186195
"0 18 31W * ? *",
187196
"0 10 * * ? *",
@@ -215,6 +224,11 @@ def test_validator(self):
215224
"0 11-23/4 ? * 2-6 *",
216225
"0 11-23/2 * * ? *",
217226
"0 0 1 1-12/3 ? *",
227+
"0 1-7/2,11-23/2 * * ? *",
228+
"0 1-7/2,11-23/2,10 * * ? *",
229+
"30 0 1 JAN-APR,JUL-OCT/2,DEC ? *",
230+
"15 10 ? * L 2019-2022",
231+
"15 10 ? * 6L 2019-2022",
218232
]
219233

220234
invalid_expression_exceptions = [
@@ -231,6 +245,8 @@ def test_validator(self):
231245
("0,5 07/12 ? * 1 2000-2200", validator.AWSCronExpressionYearError),
232246
("15/30 10 * * ? 2400", validator.AWSCronExpressionYearError),
233247
("0 9 ? * ? *", validator.AWSCronExpressionError),
248+
("0 18 3L * ? *", validator.AWSCronExpressionDayOfMonthError),
249+
("0 1-7/2,11-23/2, * * ? *", validator.AWSCronExpressionHourError),
234250
]
235251

236252
for valid_expression in valid_expressions:

0 commit comments

Comments
 (0)