summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--examples/range_check.py93
-rw-r--r--tests/test_examples.py3
2 files changed, 73 insertions, 23 deletions
diff --git a/examples/range_check.py b/examples/range_check.py
index 3182243..046fe79 100644
--- a/examples/range_check.py
+++ b/examples/range_check.py
@@ -9,56 +9,103 @@
#
import pyparsing as pp
-from datetime import datetime
+from datetime import date
+from typing import Any
-def ranged_value(expr, minval=None, maxval=None):
+def ranged_value(
+ expr: pp.ParserElement,
+ min_val: Any = None,
+ max_val: Any = None,
+ label: str = ""
+) -> pp.ParserElement:
+
# have to specify at least one range boundary
- if minval is None and maxval is None:
- raise ValueError("minval or maxval must be specified")
+ if (min_val, max_val) == (None, None):
+ raise ValueError("min_val or max_val must be specified")
+
+ expr_label = label or "value"
# set range testing function and error message depending on
# whether either or both min and max values are given
in_range_condition = {
- (False, True): lambda s, l, t: t[0] <= maxval,
- (True, False): lambda s, l, t: minval <= t[0],
- (True, True): lambda s, l, t: minval <= t[0] <= maxval,
- }[minval is not None, maxval is not None]
+ (False, True): lambda s, l, t: t[0] <= max_val,
+ (True, False): lambda s, l, t: min_val <= t[0],
+ (True, True): lambda s, l, t: min_val <= t[0] <= max_val,
+ }[min_val is not None, max_val is not None]
+
out_of_range_message = {
- (False, True): f"value is greater than {maxval}",
- (True, False): f"value is less than {minval}",
- (True, True): f"value is not in the range ({minval} to {maxval})",
- }[minval is not None, maxval is not None]
+ (False, True): f"{expr_label} is greater than {max_val}",
+ (True, False): f"{expr_label} is less than {min_val}",
+ (True, True): f"{expr_label} is not in the range ({min_val} to {max_val})",
+ }[min_val is not None, max_val is not None]
+
+ ret = expr().add_condition(in_range_condition, message=out_of_range_message)
+
+ if label:
+ ret.set_name(label)
- return expr().add_condition(in_range_condition, message=out_of_range_message)
+ return ret
# define the expressions for a date of the form YYYY/MM/DD or YYYY/MM (assumes YYYY/MM/01)
integer = pp.Word(pp.nums).set_name("integer")
integer.set_parse_action(lambda t: int(t[0]))
-month = ranged_value(integer, 1, 12)
-day = ranged_value(integer, 1, 31)
-year = ranged_value(integer, 2000, None)
+month = ranged_value(integer, 1, 12, "month")
+day = ranged_value(integer, 1, 31, "day")
+year = ranged_value(integer, 2000, None, "year")
SLASH = pp.Suppress("/")
dateExpr = year("year") + SLASH + month("month") + pp.Opt(SLASH + day("day"))
dateExpr.set_name("date")
# convert date fields to datetime (also validates dates as truly valid dates)
-dateExpr.set_parse_action(lambda t: datetime(t.year, t.month, t.day or 1).date())
+dateExpr.set_parse_action(lambda t: date(t.year, t.month, t.day or 1))
# add range checking on dates
-mindate = datetime(2002, 1, 1).date()
-maxdate = datetime.now().date()
-dateExpr = ranged_value(dateExpr, mindate, maxdate)
+min_date = date(2002, 1, 1)
+max_date = date.today()
+date_expr = ranged_value(dateExpr, min_date, max_date, "date")
+date_expr.create_diagram("range_check.html")
-dateExpr.run_tests(
+# tests of valid dates
+success_valid_tests, _ = date_expr.run_tests(
"""
+ # valid date
2011/5/8
- 2001/1/1
+
+ # leap day
2004/2/29
+
+ # default day of month to 1
2004/2
- 1999/12/31"""
+ """
+)
+
+# tests of invalid dates
+success_invalid_tests, _ = date_expr.run_tests(
+ """
+ # all values are in range, but date is too early
+ 2001/1/1
+
+ # not a leap day
+ 2005/2/29
+
+ # year number is < 2000
+ 1999/12/31
+
+ # bad year field
+ XXXX/1/1
+
+ # bad month field
+ 2010/XX/1
+
+ # bad day field
+ 2010/11/XX
+ """,
+ failure_tests=True
)
+
+assert (success_valid_tests and success_invalid_tests)
diff --git a/tests/test_examples.py b/tests/test_examples.py
index 8e63beb..8137816 100644
--- a/tests/test_examples.py
+++ b/tests/test_examples.py
@@ -45,3 +45,6 @@ class TestExamples(unittest.TestCase):
def test_lucene_grammar(self):
self._run("lucene_grammar")
+
+ def test_range_check(self):
+ self._run("range_check")