233 lines
6.5 KiB
Python
233 lines
6.5 KiB
Python
#!/usr/bin/env python
|
|
# *****************************************************************************
|
|
# Copyright (C) 2023 Thomas Touhey <thomas@touhey.fr>
|
|
# This file is part of the textoutpc project, which is MIT-licensed.
|
|
# *****************************************************************************
|
|
"""Parser tests for textoutpc."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from itertools import chain
|
|
from typing import Iterable, Sequence
|
|
|
|
from docutils.nodes import (
|
|
Element,
|
|
Node,
|
|
Text,
|
|
container,
|
|
emphasis,
|
|
literal,
|
|
reference,
|
|
strong,
|
|
)
|
|
from docutils.utils import new_document
|
|
import pytest
|
|
|
|
from textoutpc.nodes import progress, spoiler
|
|
from textoutpc.parser import Parser
|
|
|
|
|
|
def represent_node(node: Node) -> str:
|
|
"""Get the representation string for a node.
|
|
|
|
:param node: The node to get the representation for.
|
|
:return: The string representation.
|
|
"""
|
|
base = repr(node)
|
|
if isinstance(node, Element):
|
|
base += (
|
|
" ["
|
|
+ ", ".join(
|
|
f"{key}={value!r}" for key, value in node.attributes.items()
|
|
)
|
|
+ "]"
|
|
)
|
|
|
|
return base
|
|
|
|
|
|
def compare_nodes(
|
|
first_node_iterable: Iterable[Node],
|
|
second_node_iterable: Iterable[Node],
|
|
/,
|
|
*,
|
|
path: str = "",
|
|
) -> bool:
|
|
"""Compare node sequences.
|
|
|
|
:param first_node_iterable: The first node iterable.
|
|
:param second_node_iterable: The second node iterable.
|
|
:param path: The path.
|
|
:return: Whether the comparison works or not.
|
|
"""
|
|
first_node_iterator = iter(first_node_iterable)
|
|
second_node_iterator = iter(second_node_iterable)
|
|
result = True
|
|
|
|
for i, (first_node, second_node) in enumerate(
|
|
zip(first_node_iterator, second_node_iterator),
|
|
):
|
|
similar_nodes = True
|
|
if type(first_node) is not type(second_node):
|
|
similar_nodes = False
|
|
|
|
if isinstance(first_node, Text) and isinstance(second_node, Text):
|
|
similar_nodes = str(first_node) == str(second_node) and result
|
|
elif isinstance(first_node, Element) and isinstance(
|
|
second_node,
|
|
Element,
|
|
):
|
|
if isinstance(first_node, progress) and isinstance(
|
|
second_node,
|
|
progress,
|
|
):
|
|
similar_nodes = (
|
|
first_node.value == second_node.value and similar_nodes
|
|
)
|
|
|
|
if first_node.attributes != second_node.attributes:
|
|
similar_nodes = False
|
|
|
|
if not similar_nodes:
|
|
result = False
|
|
|
|
result = (
|
|
compare_nodes(first_node, second_node, path=path + f"[{i}]")
|
|
and result
|
|
)
|
|
|
|
if not similar_nodes:
|
|
print(f"Different nodes at {path or 'root'}:")
|
|
print(f" in input: {represent_node(first_node)}")
|
|
print(f" in output: {represent_node(second_node)}")
|
|
result = False
|
|
|
|
try:
|
|
first_node = next(first_node_iterator)
|
|
except StopIteration:
|
|
pass
|
|
else:
|
|
result = False
|
|
print(f"Additional nodes in the input at {path or 'root'}:")
|
|
for node in chain((first_node,), first_node_iterator):
|
|
print(f" {represent_node(node)}")
|
|
|
|
try:
|
|
second_node = next(second_node_iterator)
|
|
except StopIteration:
|
|
pass
|
|
else:
|
|
result = False
|
|
print(f"Additional nodes in the output at {path or 'root'}:")
|
|
for node in chain((second_node,), second_node_iterator):
|
|
print(f" {represent_node(node)}")
|
|
|
|
return result
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"inputstring,expected",
|
|
(
|
|
("hello\nworld", [Text("hello\nworld")]),
|
|
(
|
|
"[b][i]hello[/i]",
|
|
[strong("", emphasis("", Text("hello")))],
|
|
),
|
|
(
|
|
"[b][i]hello[/b]omg",
|
|
[strong("", emphasis("", Text("hello"))), Text("omg")],
|
|
),
|
|
(
|
|
"[c=#abc;font-size: 12pt]hello",
|
|
[
|
|
container(
|
|
"",
|
|
Text("hello"),
|
|
style="color: #AABBCC; font-size: 12pt",
|
|
),
|
|
],
|
|
),
|
|
(
|
|
"[b=unexpected]wow",
|
|
[Text("[b=unexpected]wow")],
|
|
),
|
|
(
|
|
"[center]hello",
|
|
[container("", Text("hello"), **{"class": "align-center"})],
|
|
),
|
|
(
|
|
"[progress=55]My super progress bar",
|
|
[progress("", Text("My super progress bar"), value=55)],
|
|
),
|
|
(
|
|
"[hello]world[/hello]",
|
|
[Text("[hello]world[/hello]")],
|
|
),
|
|
(
|
|
"[noeval][hello]world[/hello][/noeval]",
|
|
[Text("[hello]world[/hello]")],
|
|
),
|
|
(
|
|
"the message is: [rot=13]uryyb[/rot] - the - [rot13]jbeyq",
|
|
[Text("the message is: hello - the - world")],
|
|
),
|
|
(
|
|
"[rot=25]ibm[/]9000",
|
|
[Text("hal9000")],
|
|
),
|
|
(
|
|
"a`[code]`b",
|
|
[
|
|
Text("a"),
|
|
literal("", Text("[code]"), **{"class": "inline-code"}),
|
|
Text("b"),
|
|
],
|
|
),
|
|
(
|
|
"a[code]`",
|
|
[Text("a"), container("", Text("`"), **{"class": "code"})],
|
|
),
|
|
(
|
|
"[spoiler=should open|should close]spoiler [b]content[/b]!",
|
|
[
|
|
spoiler(
|
|
"",
|
|
Text("spoiler "),
|
|
strong("", Text("content")),
|
|
Text("!"),
|
|
closed_title="should open",
|
|
opened_title="should close",
|
|
),
|
|
],
|
|
),
|
|
(
|
|
"[code=c]int main() { return 0; }",
|
|
[
|
|
container(
|
|
"",
|
|
Text("int main() { return 0; }"),
|
|
**{"class": "code"},
|
|
),
|
|
],
|
|
),
|
|
(
|
|
"Bonjour [url=https://planet-casio.com/Fr/]le monde[/] !",
|
|
[
|
|
Text("Bonjour "),
|
|
reference(
|
|
"",
|
|
Text("le monde"),
|
|
refuri="https://planet-casio.com/Fr/",
|
|
),
|
|
Text(" !"),
|
|
],
|
|
),
|
|
),
|
|
)
|
|
def test_parser(inputstring: str, expected: Sequence[Node]) -> None:
|
|
"""Test that the parser works correctly."""
|
|
doc = new_document("/tmp/fake-source.bbcode") # noqa: S108
|
|
parser = Parser()
|
|
parser.parse(inputstring, doc)
|
|
assert compare_nodes(doc, expected)
|