234 trailer: str =
"", fill: str = RULE_FILL) -> list[tuple[str, str]]:
235 """Compose a rule line as tagged segments.
237 Geometry (plan 218 P1 element 4, read off the operator's mock): the
238 *label* block is centred over the full *width* exactly as the pre-218
239 ``rich.rule.Rule`` centred its title, and the *trailer* block is then
240 centred within the right-hand fill run that remains. Each block is
241 padded with two spaces either side, matching the two-space gutter
242 ``Rule`` already produced for a label that itself carried one space.
244 *label* is either a plain string or a list of ``(text, tag)`` pairs
245 (see :func:`turn_end_rule_segments`) when parts of the label must
246 keep distinct styles.
248 Returns a list of ``(text, tag)`` pairs where *tag* is ``"fill"``,
249 ``"trailer"``, or whichever tag the caller supplied for a label part
250 (``"label"`` for a plain-string label). Callers map tags onto their
251 own style tokens; this function makes no colour decision.
253 label_parts: list[tuple[str, str]]
254 if isinstance(label, str):
255 label_parts = [(label,
"label")]
if label
else []
257 label_parts = [part
for part
in label
if part[0]]
261 label_parts = list(label_parts)
262 label_parts[0] = (
" " + label_parts[0][0], label_parts[0][1])
263 label_parts[-1] = (label_parts[-1][0] +
" ", label_parts[-1][1])
264 label_len = sum(len(text)
for text, _tag
in label_parts)
265 trailer_block = f
" {trailer} " if trailer
else ""
267 if label_len >= width:
272 left = (width - label_len) // 2
273 right_run = width - left - label_len
275 segments: list[tuple[str, str]] = []
277 segments.append((fill * left,
"fill"))
278 segments.extend(label_parts)
282 if trailer_block
and right_run >= len(trailer_block) + 2:
283 gap = (right_run - len(trailer_block)) // 2
284 tail = right_run - gap - len(trailer_block)
285 segments.append((fill * gap,
"fill"))
286 segments.append((trailer_block,
"trailer"))
288 segments.append((fill * tail,
"fill"))
290 segments.append((fill * right_run,
"fill"))
294def rule_line(label: str | list[tuple[str, str]], width: int, trailer: str =
"",
295 fill: str = RULE_FILL) -> str:
296 """Return :func:`compose_rule`'s segments joined into one plain string."""
297 return "".join(text
for text, _tag
in compose_rule(label, width, trailer, fill))
327 indent: str = TURN_INDENT) -> str:
328 """ASCII opening marker for the degraded answer box.
330 Renders ``" --- Assistant Answer: -----"`` — the same information
331 the rich box carries in its top border, using only characters a dumb
332 terminal is guaranteed to draw (W5).
334 inner = max(0, width - len(indent))
335 head = f
"{PLAIN_BOX_FILL * 3} {title} "
336 pad = max(0, inner - len(head))
337 return indent + head + PLAIN_BOX_FILL * pad
341 indent: str = TURN_INDENT) -> str:
342 """ASCII closing marker for the degraded answer box.
344 Carries the iteration metadata in the same place the rich box's
345 bottom border does. Always emitted — the plain presenter closes its
346 box on every path too (W2).
348 inner = max(0, width - len(indent))
349 head = PLAIN_BOX_FILL * 3
351 head += f
" {subtitle} "
354 pad = max(0, inner - len(head))
355 return indent + head + PLAIN_BOX_FILL * pad