diff --git a/docs/api_reference.rst b/docs/api_reference.rst index 077e41c..b20cffd 100644 --- a/docs/api_reference.rst +++ b/docs/api_reference.rst @@ -74,6 +74,7 @@ Color Level 5 .. module:: tinycss2.nth .. autofunction:: parse_nth +.. autofunction:: serialize_nth AST nodes diff --git a/tests/test_tinycss2.py b/tests/test_tinycss2.py index fb1130a..24d3369 100644 --- a/tests/test_tinycss2.py +++ b/tests/test_tinycss2.py @@ -21,7 +21,7 @@ from tinycss2.color4 import Color # isort:skip from tinycss2.color4 import parse_color as parse_color4 # isort:skip from tinycss2.color5 import parse_color as parse_color5 # isort:skip -from tinycss2.nth import parse_nth # isort:skip +from tinycss2.nth import parse_nth, serialize_nth # isort:skip def generic(func): @@ -153,6 +153,22 @@ def test_nth(input): return parse_nth(input) +@pytest.mark.parametrize(('a', 'b', 'expected'), [ + (0, 0, '0'), (0, 1, '1'), (0, -2, '-2'), (1, 0, 'n'), (1, 3, 'n+3'), (1, -3, 'n-3'), + (-1, 0, '-n'), (-1, 3, '-n+3'), (-1, -3, '-n-3'), (2, 0, '2n'), (2, 1, '2n+1'), + (2, -4, '2n-4'), (-2, 0, '-2n'), (-2, 1, '-2n+1'), (-2, -4, '-2n-4'), +]) +def test_serialize_nth(a, b, expected): + assert serialize_nth(a, b) == expected + assert parse_nth(expected) == (a, b) + + +@json_test(filename='An+B.json') +def test_round_trip_nth(input): + if numbers := parse_nth(input): + return parse_nth(serialize_nth(*numbers)) + + @pytest.mark.parametrize('invalid', ['+', '+/**/', 'n+', 'n +', '-n-', '2n +']) def test_nth_invalid_does_not_crash(invalid): # Truncated/invalid An+B fragments must return None per parse_nth's diff --git a/tinycss2/nth.py b/tinycss2/nth.py index 225a3ac..0d2650c 100644 --- a/tinycss2/nth.py +++ b/tinycss2/nth.py @@ -74,6 +74,20 @@ def parse_nth(input): return parse_end(tokens, 1, int(match.group(1))) + +def serialize_nth(a, b): + """Serialize ` `_. + + :param int a: The step coefficient. + :param int b: The offset. + :returns: An ``an+b`` string. + + """ + an = 'n' if a == 1 else '-n' if a == -1 else f'{a}n' if a else '' + b = f'{b}' if not a else f'{b:+}' if b else '' + return an + b + + def parse_b(tokens, a): token = _next_significant(tokens) if token is None: