Release govoplan-templates v0.1.22: bound rendering and improve dialog layout
This commit is contained in:
@@ -362,22 +362,49 @@ def _render_payload(
|
||||
) -> tuple[bytes, str, int]:
|
||||
if request.output_format == "text":
|
||||
body = revision.content_text or _html_to_text(revision.content_html or "")
|
||||
rendered = [
|
||||
_substitute(body, _render_context(request.parameters, item, index), html=False)
|
||||
for index, item in enumerate(items)
|
||||
]
|
||||
separator = "\n\n---\n\n" if revision.template_type != "list_layout" else "\n"
|
||||
rendered = _render_items(body, request.parameters, items, html=False, separator=separator)
|
||||
payload = separator.join(rendered).encode("utf-8")
|
||||
return payload, "text/plain; charset=utf-8", _page_count(revision, len(items))
|
||||
|
||||
body = revision.content_html or f"<pre>{escape(revision.content_text or '')}</pre>"
|
||||
rendered = [
|
||||
_substitute(body, _render_context(request.parameters, item, index), html=True)
|
||||
for index, item in enumerate(items)
|
||||
]
|
||||
rendered = _render_items(body, request.parameters, items, html=True)
|
||||
page_count = _page_count(revision, len(items))
|
||||
document = _html_document(definition, revision, rendered)
|
||||
return document.encode("utf-8"), "text/html; charset=utf-8", page_count
|
||||
payload = document.encode("utf-8")
|
||||
if len(payload) > MAX_OUTPUT_BYTES:
|
||||
_output_limit_exceeded()
|
||||
return payload, "text/html; charset=utf-8", page_count
|
||||
|
||||
|
||||
def _output_limit_exceeded() -> None:
|
||||
raise TemplateRenderError(
|
||||
f"Rendered output exceeds the {MAX_OUTPUT_BYTES} byte bounded-download limit."
|
||||
)
|
||||
|
||||
|
||||
def _render_items(
|
||||
body: str,
|
||||
parameters: Mapping[str, object],
|
||||
items: Sequence[Mapping[str, object]],
|
||||
*,
|
||||
html: bool,
|
||||
separator: str = "",
|
||||
) -> list[str]:
|
||||
# Reject as soon as the same existing output budget is exhausted; never
|
||||
# build thousands of oversized documents and only then measure the join.
|
||||
remaining = MAX_OUTPUT_BYTES
|
||||
separator_bytes = len(separator.encode("utf-8"))
|
||||
rendered: list[str] = []
|
||||
for index, item in enumerate(items):
|
||||
if index:
|
||||
remaining -= separator_bytes
|
||||
if remaining < 0:
|
||||
_output_limit_exceeded()
|
||||
value = _substitute(body, _render_context(parameters, item, index), html=html, max_bytes=remaining)
|
||||
remaining -= len(value.encode("utf-8"))
|
||||
rendered.append(value)
|
||||
return rendered
|
||||
|
||||
|
||||
def _html_document(
|
||||
@@ -552,15 +579,32 @@ def _render_context(
|
||||
}
|
||||
|
||||
|
||||
def _substitute(template: str, context: Mapping[str, object], *, html: bool) -> str:
|
||||
def replacement(match: re.Match[str]) -> str:
|
||||
value, present = _resolve_path(context, match.group(1))
|
||||
if not present or value is None:
|
||||
return ""
|
||||
rendered = _display_value(value)
|
||||
return escape(rendered, quote=True) if html else rendered
|
||||
def _substitute(template: str, context: Mapping[str, object], *, html: bool, max_bytes: int | None = None) -> str:
|
||||
remaining = MAX_OUTPUT_BYTES if max_bytes is None else max_bytes
|
||||
pieces: list[str] = []
|
||||
|
||||
return _TOKEN_PATTERN.sub(replacement, template)
|
||||
def append(value: str) -> None:
|
||||
nonlocal remaining
|
||||
# Character count is a cheap lower bound before allocating UTF-8 bytes.
|
||||
if len(value) > remaining:
|
||||
_output_limit_exceeded()
|
||||
remaining -= len(value.encode("utf-8"))
|
||||
if remaining < 0:
|
||||
_output_limit_exceeded()
|
||||
pieces.append(value)
|
||||
|
||||
previous = 0
|
||||
for match in _TOKEN_PATTERN.finditer(template):
|
||||
append(template[previous:match.start()])
|
||||
value, present = _resolve_path(context, match.group(1))
|
||||
if present and value is not None:
|
||||
rendered = _display_value(value)
|
||||
if len(rendered) > remaining:
|
||||
_output_limit_exceeded()
|
||||
append(escape(rendered, quote=True) if html else rendered)
|
||||
previous = match.end()
|
||||
append(template[previous:])
|
||||
return "".join(pieces)
|
||||
|
||||
|
||||
def _resolve_path(context: Mapping[str, object], path: str) -> tuple[object | None, bool]:
|
||||
|
||||
Reference in New Issue
Block a user