Overridable methods#

Handlers and rules decide what each node becomes. The methods on this page decide everything around them: the starting settings, the layout of lines and blocks, and the final output. They are listed roughly in the order you are likely to need them.

Method

Override it to

Built-in overrides

complete

produce the final output

every built-in renderer

to_file

write the output to a file

Word, Excel

join_item

lay out lines and blocks

LaTeX, Markdown, HTML

create_context

set default settings

LaTeX, HTML

format_param_grid

lay out parameter grids

LaTeX, HTML

format_calc_grid

lay out multi-line (-m) calculations

LaTeX, HTML

render_root

change how the top level is walked

Excel

join, join_items

change the joining walk itself

(none)

All are defined on BaseRenderer.

complete(tree, base_context)#

Called once, after rendering, with the whole render tree. Its return value is what the interface returns. BaseRenderer.complete returns the tree unchanged.

For a text format, join and finish:

def complete(self, tree, base_context):
    body = self.join(tree, base_context)
    return f"<section class='calc'>{body}</section>"

For anything else, walk the tree yourself: the Excel renderer turns its rows into a workbook (ExcelRenderer.complete), the Word renderer writes its tree into a .docx (WordRenderer.complete), and the structured data example flattens the tree into dicts.

Built-in versions: ASCIIRenderer.complete, LatexRenderer.complete, HTMLRenderer.complete.

join_item(item, depth, base_context)#

Called by join for each item of the tree, with its nesting depth. Returns a list of strings that are concatenated into the output. This is where a renderer’s layout lives.

The base version handles the tree shapes: a string is a whole line, a list of strings is one line’s parts (joined with context.space), and [header, body] is a block (header at this depth, body one level deeper, indented with context.indent). Every line ends with context.newline.

Override it to change how lines or blocks are written, and hand everything else back to the parent:

def join_item(self, item, depth, base_context):
    if item and isinstance(item, list) and isinstance(item[-1], list):
        header, body = item[0], item[-1]
        if isinstance(header, str) and header.strip():
            inner = "".join(self.join_items(body, depth + 1, base_context))
            return [f"<details><summary>{header}</summary>{inner}</details>"]
    return super().join_item(item, depth, base_context)

Built-in versions show three styles: HTML (nested <div>s), LaTeX (paragraphs, math groups, list environments), and Markdown (blockquotes).

create_context(**kwargs)#

Called once per render with the configuration as keyword arguments. Returns the BaseRenderContext. Override it to give your renderer its own defaults; merge them under kwargs so the configuration can still override them:

from handcalcs.renderers.base import BaseRenderContext, RenderContext

def create_context(self, **kwargs):
    defaults = {"lpar": "\\left(", "rpar": "\\right)", "indent": ""}
    return BaseRenderContext(RenderContext(**(defaults | kwargs)), RenderContext.sparse())

See The render context.

format_param_grid(rows, base_context)#

Called by the params_block handler. rows is a list of rows; each cell is the rendered parameter as a list of parts, [identifier, "=", value] plus a comment for # hc: -h grids, or None to pad the last row. Return the grid’s rendered form, usually one string.

The base version pads columns to line up the = signs in plain text. The LaTeX version builds an aligned group (LatexRenderer.format_param_grid), the HTML version a <table> (HTMLRenderer.format_param_grid).

format_calc_grid(rows, base_context)#

Called by the calc_line handler for a multi-line (# hc: -m) calculation. rows is a list of [identifier, "=", value] triples: the first has the target, the rest an empty identifier. Return the rendered calculation, usually one string.

render_root(root, base_context)#

Walks the top-level nodes, running root:pre and root:post rules around them, and returns the tree as a list. Override it when the top level needs different handling, such as flattening: the Excel renderer flattens every statement into one list of rows (ExcelRenderer.render_root). For adding a title or a footer, a root:pre or root:post rule is simpler.

to_file(rendered, path)#

Called by HandCalcs when a destination is given, with what complete returned. It writes the file and returns its pathlib.Path.

The base version writes a string as UTF-8, between the renderer’s preamble and postamble attributes (both empty unless you set them), and raises TypeError for anything else. So a text renderer usually only sets the two attributes, as the LaTeX and HTML renderers do:

class RstRenderer(LatexRenderer):
    preamble = ".. Generated by handcalcs\n\n"
    postamble = ""

Override to_file when complete returns an object that saves itself. The Word and Excel renderers do:

def to_file(self, rendered, path):
    path = pathlib.Path(path)
    rendered.save(path)
    return path

Source: BaseRenderer.to_file, WordRenderer.to_file.

join(tree, base_context) and join_items(items, depth, base_context)#

join calls join_items(tree, 0, ...) and concatenates the result; join_items calls join_item for each item. You rarely need to override them; call them from your own complete and join_item.

Renderer-specific hooks#

The LaTeX family splits its layout into smaller methods you can override instead of the whole join_item:

Method

Does

paragraph(text)

Prefixes a prose paragraph (\noindent )

math_group(rows)

Wraps joined rows in MATH_OPEN / MATH_CLOSE

join_math_group(rows, depth, base_context)

Joins a CalcsBlock’s rows into one group

join_math_row(row, base_context)

Joins one line’s parts into an aligned row, placing &

quote(chunks) (Markdown)

Blockquotes a block body

The reStructuredText example overrides math_group and paragraph.

Class attributes#

Attribute

Purpose

name

Registers the renderer for selection by name (config, %hc_default_renderer).

demo_source

Source for HandCalcs.demo() when the general demo doesn’t suit the renderer.

preamble, postamble

Text to_file writes before and after the output, so the file is a complete document.

node_handlers, header_handlers, rule_handlers

The registration tables.

Next: The render tree.