Merge branch 'main' into feature/question-search

This commit is contained in:
Matthias Jung
2026-04-05 23:15:54 +02:00
committed by GitHub
8 changed files with 191 additions and 0 deletions
+1
View File
@@ -15,3 +15,4 @@ bd.build_edition("NEA")
bd.build_assets() bd.build_assets()
bd.build_solutions() bd.build_solutions()
bd.build_question_index() bd.build_question_index()
bd.build_zip()
+84
View File
@@ -1087,6 +1087,28 @@
\pgfcirc@activate@bipole@simple{l}{channelcoder} \pgfcirc@activate@bipole@simple{l}{channelcoder}
\makeatother \makeatother
% Channel Decoder
\makeatletter
\ctikzset{bipoles/channeldecoder/width/.initial=.7}
\pgfcirc@define@twoports{blocks}
{}
{\ctikzvalof{bipoles/channeldecoder/width}}
{channeldecoder}
{\ctikzvalof{bipoles/channeldecoder/width}}
{\ctikzvalof{bipoles/channeldecoder/width}}
{
\pgfcirc@twoport@draw@splitline
%
\pgf@circ@text@strokecolor
\pgftext[center,x=0.25\pgf@circ@res@left,y=0.6\pgf@circ@res@up]{\tiny 01+01}
\pgftext[center,x=0.55\pgf@circ@res@right,y=0.6\pgf@circ@res@down]{\tiny 01}
}
\pgfcirc@activate@bipole@simple{l}{channeldecoder}
\makeatother
% Source Coder % Source Coder
\makeatletter \makeatletter
\ctikzset{bipoles/sourcecoder/width/.initial=.7} \ctikzset{bipoles/sourcecoder/width/.initial=.7}
@@ -1108,6 +1130,27 @@
\pgfcirc@activate@bipole@simple{l}{sourcecoder} \pgfcirc@activate@bipole@simple{l}{sourcecoder}
\makeatother \makeatother
% Source Decoder
\makeatletter
\ctikzset{bipoles/sourcedecoder/width/.initial=.7}
\pgfcirc@define@twoports{blocks}
{}
{\ctikzvalof{bipoles/sourcedecoder/width}}
{sourcedecoder}
{\ctikzvalof{bipoles/sourcedecoder/width}}
{\ctikzvalof{bipoles/sourcedecoder/width}}
{
\pgfcirc@twoport@draw@splitline
%
\pgf@circ@text@strokecolor
\pgftext[center,x=0.55\pgf@circ@res@left,y=0.6\pgf@circ@res@up]{\tiny 01}
\pgftext[center,x=0.30\pgf@circ@res@right,y=0.6\pgf@circ@res@down]{\tiny 0101}
}
\pgfcirc@activate@bipole@simple{l}{sourcedecoder}
\makeatother
% Mapper % Mapper
\makeatletter \makeatletter
\ctikzset{bipoles/mapper/width/.initial=.7} \ctikzset{bipoles/mapper/width/.initial=.7}
@@ -1148,3 +1191,44 @@
\pgfcirc@activate@bipole@simple{l}{mapper} \pgfcirc@activate@bipole@simple{l}{mapper}
\makeatother \makeatother
% Demapper
\makeatletter
\ctikzset{bipoles/demapper/width/.initial=.7}
\pgfcirc@define@twoports{blocks}
{}
{\ctikzvalof{bipoles/demapper/width}}
{demapper}
{\ctikzvalof{bipoles/demapper/width}}
{\ctikzvalof{bipoles/demapper/width}}
{
\pgfcirc@twoport@draw@splitline
%
\pgf@circ@text@strokecolor
%
\pgfpathmoveto{\pgfpoint{0.50\pgf@circ@res@left}{+0.25\pgf@circ@res@up}}
\pgfpathlineto{\pgfpoint{0.50\pgf@circ@res@left}{+0.75\pgf@circ@res@up}}
\pgfusepath{draw}
%
\pgfpathmoveto{\pgfpoint{0.25\pgf@circ@res@left}{+0.50\pgf@circ@res@up}}
\pgfpathlineto{\pgfpoint{0.75\pgf@circ@res@left}{+0.50\pgf@circ@res@up}}
\pgfusepath{draw}
%
\pgfpathcircle{\pgfpoint{0.65\pgf@circ@res@left}{+0.65\pgf@circ@res@up}}{0.05\pgf@circ@res@up}
\pgfusepath{fill}
%
\pgfpathcircle{\pgfpoint{0.35\pgf@circ@res@left}{+0.65\pgf@circ@res@up}}{0.05\pgf@circ@res@up}
\pgfusepath{fill}
%
\pgfpathcircle{\pgfpoint{0.65\pgf@circ@res@left}{+0.35\pgf@circ@res@up}}{0.05\pgf@circ@res@up}
\pgfusepath{fill}
%
\pgfpathcircle{\pgfpoint{0.35\pgf@circ@res@left}{+0.35\pgf@circ@res@up}}{0.05\pgf@circ@res@up}
\pgfusepath{fill}
%
\pgftext[center,x=0.55\pgf@circ@res@right,y=-0.6\pgf@circ@res@up]{\tiny 01}
}
\pgfcirc@activate@bipole@simple{l}{demapper}
\makeatother
+5
View File
@@ -23,6 +23,7 @@ def main(
output: Annotated[str | None, typer.Option("--output", "-o", help="Destination directory to build to.")] = None, output: Annotated[str | None, typer.Option("--output", "-o", help="Destination directory to build to.")] = None,
render_editions: Annotated[bool, typer.Option(help="Skip building editions.")] = True, render_editions: Annotated[bool, typer.Option(help="Skip building editions.")] = True,
render_solutions: Annotated[bool, typer.Option(help="Skip building solutions.")] = True, render_solutions: Annotated[bool, typer.Option(help="Skip building solutions.")] = True,
build_zip: Annotated[bool, typer.Option(help="Whether to build a zip file of the output.")] = False,
) -> None: ) -> None:
conf = config.Config(content_path=input, build_path=output) conf = config.Config(content_path=input, build_path=output)
bd = build.Build(conf) bd = build.Build(conf)
@@ -43,6 +44,10 @@ def main(
bd.build_assets() bd.build_assets()
bd.build_question_index() bd.build_question_index()
if build_zip:
# Build zip file of output
bd.build_zip()
if __name__ == "__main__": if __name__ == "__main__":
app() app()
+6
View File
@@ -6,6 +6,7 @@ from .dash import Dash
from .formula import Formula from .formula import Formula
from .halfwidth_spaces import HalfwidthSpaces from .halfwidth_spaces import HalfwidthSpaces
from .include import Include from .include import Include
from .index import Index
from .morse import Morse from .morse import Morse
from .nonbreaking_spaces import NonbreakingSpaces, NonbreakingSpacesDots from .nonbreaking_spaces import NonbreakingSpaces, NonbreakingSpacesDots
from .photo import Photo from .photo import Photo
@@ -64,6 +65,7 @@ class FiftyOhmHtmlRenderer(HtmlRenderer):
Qso, Qso,
Include, Include,
Formula, Formula,
Index,
*extras, *extras,
**kwargs, **kwargs,
) )
@@ -332,3 +334,7 @@ class FiftyOhmHtmlRenderer(HtmlRenderer):
def render_formula(self, token): def render_formula(self, token):
return f"\n$${token.formula}$$\n" return f"\n$${token.formula}$$\n"
def render_index(self, token):
# For HTML just ignore the index
return ""
+8
View File
@@ -3,6 +3,7 @@ from mistletoe.latex_renderer import LaTeXRenderer
from .comment import BlockComment from .comment import BlockComment
from .dash import Dash from .dash import Dash
from .halfwidth_spaces import HalfwidthSpaces from .halfwidth_spaces import HalfwidthSpaces
from .index import Index
from .nonbreaking_spaces import NonbreakingSpaces, NonbreakingSpacesDots from .nonbreaking_spaces import NonbreakingSpaces, NonbreakingSpacesDots
from .photo import Photo from .photo import Photo
from .picture import Picture from .picture import Picture
@@ -32,6 +33,7 @@ class FiftyOhmLaTeXRenderer(LaTeXRenderer):
TableRow, TableRow,
TableHeader, TableHeader,
TableCell, TableCell,
Index,
) )
self.question_renderer = question_renderer self.question_renderer = question_renderer
@@ -148,3 +150,9 @@ class FiftyOhmLaTeXRenderer(LaTeXRenderer):
def render_table_cell(self, token: TableCell): def render_table_cell(self, token: TableCell):
return self.render_inner(token) return self.render_inner(token)
def render_index(self, token):
if token.second:
return f"\\index{{{token.first}!{token.second}}}"
else:
return f"\\index{{{token.first}}}"
+16
View File
@@ -0,0 +1,16 @@
import re
from mistletoe.span_token import SpanToken
class Index(SpanToken):
"""
Resolve index commands
Identifies "[index:term:subterm]" or "[index:term]"
"""
pattern = re.compile(r"[ \t]*\[index:([^:\]\s](?:[^:\]\s]| )*)(?::([^:\]\s](?:[^:\]\s]| )*))?\]")
def __init__(self, match_object):
self.first = match_object.group(1)
self.second = match_object.group(2)
+37
View File
@@ -1,7 +1,9 @@
import json import json
import os
import random import random
import re import re
import shutil import shutil
import zipfile
from pathlib import Path from pathlib import Path
from jinja2 import Environment, FileSystemLoader from jinja2 import Environment, FileSystemLoader
@@ -561,3 +563,38 @@ class Build:
with (self.config.p_build_assets / "question_index.json").open("w", encoding="utf-8") as file: with (self.config.p_build_assets / "question_index.json").open("w", encoding="utf-8") as file:
json.dump(self.question_index, file, ensure_ascii=False, indent=2, sort_keys=True) json.dump(self.question_index, file, ensure_ascii=False, indent=2, sort_keys=True)
file.write("\n") file.write("\n")
def build_zip(self, zip_name: str | None = None) -> Path:
"""Create a zip archive of the complete build output directory.
The zip file is written into the build directory itself and is excluded
from the archive contents.
"""
build_dir = self.config.p_build
build_dir.mkdir(parents=True, exist_ok=True)
zip_path = build_dir / (zip_name if zip_name is not None else f"{build_dir.name}.zip")
zip_path = zip_path.resolve()
# If a previous archive exists, remove it first to avoid zipping stale data.
if zip_path.exists():
zip_path.unlink()
tqdm.write(f"Creating zip archive: {zip_path}")
with zipfile.ZipFile(zip_path, "w", compression=zipfile.ZIP_DEFLATED) as zf:
# os.walk includes dotfiles; sort for stable archives.
for root, dirs, files in os.walk(build_dir):
dirs.sort()
files.sort()
root_path = Path(root)
for filename in files:
file_path = (root_path / filename).resolve()
if file_path == zip_path:
continue
arcname = file_path.relative_to(build_dir.resolve())
zf.write(file_path, arcname=arcname)
return zip_path
+34
View File
@@ -0,0 +1,34 @@
import mistletoe
import pytest
from renderer.fifty_ohm_html_renderer import FiftyOhmHtmlRenderer
from renderer.fifty_ohm_latex_renderer import FiftyOhmLaTeXRenderer
from test.util import paragraph
@pytest.mark.html
def test_index_html():
assertions = {
"Im Betrieb werden Yagi-Antennen [index:Antenne:Yagi-Antenne] oft gedreht": "Im Betrieb werden Yagi-Antennen oft gedreht", # noqa: E501
"Im Betrieb werden Yagi-Antennen[index:Antenne:Yagi-Antenne] oft gedreht": "Im Betrieb werden Yagi-Antennen oft gedreht", # noqa: E501
"Im Betrieb werden Yagi-Antennen [index:Antenne] oft gedreht": "Im Betrieb werden Yagi-Antennen oft gedreht",
"Im Betrieb werden Yagi-Antennen[index:Antenne] oft gedreht": "Im Betrieb werden Yagi-Antennen oft gedreht",
"Im Betrieb werden Yagi-Antennen[index:Test Antenne] oft gedreht": "Im Betrieb werden Yagi-Antennen oft gedreht", # noqa: E501
}
for key, value in assertions.items():
assert mistletoe.markdown(key, FiftyOhmHtmlRenderer) == paragraph(value)
@pytest.mark.latex
def test_index_latex():
assertions = {
"Im Betrieb werden Yagi-Antennen [index:Antenne:Yagi-Antenne] oft gedreht": "Im Betrieb werden Yagi-Antennen\\index{Antenne!Yagi-Antenne} oft gedreht", # noqa: E501
"Im Betrieb werden Yagi-Antennen[index:Antenne:Yagi-Antenne] oft gedreht": "Im Betrieb werden Yagi-Antennen\\index{Antenne!Yagi-Antenne} oft gedreht", # noqa: E501
"Im Betrieb werden Yagi-Antennen [index:Antenne] oft gedreht": "Im Betrieb werden Yagi-Antennen\\index{Antenne} oft gedreht", # noqa: E501
"Im Betrieb werden Yagi-Antennen[index:Antenne] oft gedreht": "Im Betrieb werden Yagi-Antennen\\index{Antenne} oft gedreht", # noqa: E501
"Im Betrieb werden Yagi-Antennen [index:Test Antenne] oft gedreht": "Im Betrieb werden Yagi-Antennen\\index{Test Antenne} oft gedreht", # noqa: E501
}
for key, value in assertions.items():
assert mistletoe.markdown(key, FiftyOhmLaTeXRenderer) == "\n" + value + "\n"