Building a renderer#
A renderer is a Python class. It inherits a complete, working rendering pipeline from
BaseRenderer (or from one of the built-in renderers) and changes only what is
different about its output format. This page gives you the mental model and a working
renderer; the pages after it go into each part.
The mental model#
your code ──run──▶ values
│ │
└──parse──▶ tree of nodes, each variable paired with its value
│
renderer.render() one handler per node type, plus rules around it
│
▼
render tree nested lists of strings (or anything you return)
│
renderer.complete() join into the final output
│
▼
output str, bytes, a workbook, a list of dicts...
You customize the two middle steps:
Handlers and rules decide what each node becomes. A handler renders one node type (
calc_line,name,div_op, …). A rule runs before or after a handler to adjust the node or its result: swappingalphaforα, wrapping a result in a tag. You register both with a decorator.complete()turns the tree of rendered pieces into the final output. For text formats it callsjoin(), whose per-item methodjoin_item()is where layout lives: newlines, indentation, wrapping blocks.
A first renderer#
This renderer produces plain text and changes one thing, how multiplication is shown:
from handcalcs.renderers import BaseRenderer
class TimesRenderer(BaseRenderer):
name = "times" # registers it, so %hc_default_renderer times works
def complete(self, tree, base_context):
return self.join(tree, base_context) # nested lists -> text
@TimesRenderer.register("mult_op:pre")
def times_sign(renderer, node, base_context):
node.symbol = "×"
return node
>>> from handcalcs import HandCalcs
>>> print(HandCalcs(TimesRenderer())("a = 2\nb = a * 3\n"))
a = 2
b = a × 3 = 2 × 3 = 6
Everything else (calculation lines, parameter grids, conditionals, comment commands,
number formats) comes from BaseRenderer.
Choosing a parent class#
Start from |
When your format is |
You get |
|---|---|---|
|
something new |
the full pipeline; Python-style symbols; plain text from |
|
text with Unicode math |
Greek letters, |
|
anything that embeds LaTeX math |
Greek, subscripts, fractions, operators; math groups |
|
a Markdown dialect |
the above, with Markdown structure |
|
HTML |
|
Inheriting from the closest built-in renderer means less code, and a renderer that is shown correctly in Jupyter (see Publishing).
Go deeper#
The full pipeline, step by step, with links to the source.
Every node type a renderer receives, and its attributes.
register, classifiers, signatures and order of execution.
Global and line settings, current_mode, and the pitfalls.
complete, join_item, create_context, the grid formatters, and when to override each.
The nested-list shapes join() understands.
Worked examples#
Rules only: Unicode subscripts and math signs on top of ASCIIRenderer.
Tokens, handlers and join_item on top of LatexRenderer.
Output that is not text: new handlers and a new complete().
Then: testing your renderer and publishing it.