API#

Every entry links to its source with [source].

Interfaces#

class handcalcs.HandCalcs(renderer=None)[source]#
__call__(source_or_path, supplied_globals=None, supplied_locals=None, *, inputs=None, strict=True, allow_computed=False, destination=None)[source]#

Renders ‘source_or_path’ and returns what the renderer produces: a str for the text renderers, a document or workbook for the Word and Excel renderers.

If ‘destination’ is given, the render is written to that file instead (with the renderer’s ‘to_file’: a complete LaTeX or HTML document, or the saved .docx or .xlsx) and its path is returned as a pathlib.Path.

If ‘supplied_globals’ and ‘supplied_locals’ are each None, then the source code will be executed to create the globals and locals dicts. If either dictionary are supplied, code execution will not occur. Empty dictionaries are acceptable but will produce numerical substitutions of ‘NoValue()’.

‘inputs’ maps names to values that replace the values the source assigns them (see handcalcs.parsing.inputs.bind_inputs, which also describes ‘strict’ and ‘allow_computed’); the source is then executed with them. A number replaces only the number of a value with units, so ‘L = 6 * m’ with inputs={“L”: 8} renders and runs as ‘L = 8 * m’. A quantity is converted to the units of such a line, so with inputs={“L”: 8000 * mm} it runs and renders as ‘L = 8 * m’ too. Other objects (arrays, quantities on a line without units) are used as they are.

Parameters:
Return type:

Any

demo(destination=None)[source]#

Renders the demo with the current renderer; with ‘destination’, writes it to that file and returns the path, as calling the object does.

A renderer that cannot render the general demo (e.g. the ExcelRenderer, which has no worksheet form for loops or collections) provides its own source as a demo_source attribute, which is used instead.

Parameters:

destination (str | PathLike | None)

handcalcs.handcalc(renderer=None, render_context=None)[source]#

Decorate a calculation function so that a call returns (return_value, rendered). Without a renderer, the default_renderer option’s is used (the ASCIIRenderer unless set). Usable as @handcalc(renderer), @handcalc() or @handcalc.

Parameters:
handcalcs.find_inputs(source_or_path)[source]#

The candidate inputs of a script: each name whose first top-level assignment is a literal or a number times units, with its value in the script (only the number, for a number times units). The result is a template for inputs.

Parameters:

source_or_path (str | Path)

Return type:

dict

The Jupyter magics (%%render, %hc_default_renderer, %hc_format_code) are described in the Jupyter interface.

Configuration#

handcalcs.set_option(option, value)[source]#

Set the value of option to value in the global configuration.

This affects the in-memory configuration only; call save_config to make the change permanent. Base options are type-checked; any other option name is accepted with any value, so custom renderers can store their own settings.

The following base options are recognised (arbitrary extra options may also be set for use by custom renderers):

default_renderer: str (default = ‘ascii’) jupyter_renderer: str (default = ‘markdown’) format_code: str (default = ‘.5g’) line_values: bool (default = True)

Parameters:
Return type:

None

handcalcs.get_option(option, default=None)[source]#

Return the value of option in the current configuration.

Parameters:
Return type:

Any

handcalcs.get_config()[source]#

Return a copy of the current in-memory configuration.

Return type:

dict[str, Any]

handcalcs.reset_option(option)[source]#

Reset option to its built-in default, or remove it if it is a custom field.

This affects the in-memory configuration only; call save_config to persist.

Parameters:

option (str)

Return type:

None

handcalcs.reset_config()[source]#

Reset the entire in-memory configuration to the built-in defaults.

Return type:

None

handcalcs.save_config()[source]#

Persist the current in-memory configuration to disk.

The saved configuration becomes the new default, loaded automatically the next time handcalcs is imported. The config directory is created if necessary.

Return type:

None

handcalcs.config.config_path()[source]#

Return the path to the config JSON file.

Honors the HANDCALCS_CONFIG environment variable if set; otherwise resolves the per-user config directory via platformdirs.

Return type:

Path

Renderers#

handcalcs.renderers.get_renderer(name)[source]#

Return the renderer class registered under ‘name’, or None if no renderer with that name has been defined/imported.

Parameters:

name (str)

Return type:

type[BaseRenderer] | None

class handcalcs.renderers.BaseRenderer[source]#
render(node, base_context=None)[source]#

Render a node.

If ‘context’ is None, a new context is created.

Parameters:
Return type:

str

render_root(root, base_context)[source]#

Render an HcSequence (root node) into the master list.

The sequence-level pre/post renderers are registered against the pseudo node type ‘root’ (i.e. ‘root:pre’ and ‘root:post’); they run before and after the sequence body and contribute leading/trailing items.

Parameters:
Return type:

list

complete(tree, base_context)[source]#

A function called at the completion of every render. This function is intended to be over-ridden by individual renderers in order to “complete” the rendering.

The BaseRenderer that all renderers inherit from includes a special “.join” method with the same function signature as this one. For many renderers that return a str, calling the .join() method will be sufficient as the implementation for this function.

Parameters:
Return type:

Any

to_file(rendered, path)[source]#

Write rendered, the result of complete(), to the file at path and return the path. Text is written as UTF-8 between preamble and postamble. A renderer whose complete() returns another kind of object (a workbook, a document) overrides this to save it.

Parameters:
Return type:

Path

join(tree, base_context=None)[source]#

Join a rendered nested-list structure into final text.

This is the post-render pass that inserts the spacing, indentation and newlines that the node handlers deliberately leave out:

  • a string item is a whole line (heading, comment, block header);

  • an all-string list is a rendered line whose components are joined by a single space;

  • a [header_string, body_list] list is a block: the header is a line at the current depth and the body lines are rendered one level deeper.

Falsy items (None, "", []) render nothing (they are command/ignored lines), so they never produce a stray blank line.

base_context is optional: callers that just want text from a tree (tests, demo() post-processing) can omit it and get a default context.

Parameters:
Return type:

str

render_node(node, base_context)[source]#

Render one node with an existing context.

Rules registered against this node type run in category order: ‘pre’ (always), then ‘sym’ or ‘num’ gated on context.current_mode, then the node handler, then ‘post’ on the rendered result. Multiple rules in a category run in registration order.

Parameters:
Return type:

str

render_header(node, base_context)[source]#

Render the introductory/context-establishing header line for a block node.

Dispatches to the handler registered under ‘header:{node.type}’. Block nodes with no registered header handler render no header (empty string), letting a renderer opt in per block type.

Parameters:
Return type:

str

classmethod register(node_classifier)[source]#

Register a render handler (or a pre/sym/num/post/header rule) for a given node classifier, at the class level. See _route_registration.

Parameters:

node_classifier (str)

Return type:

Callable[[Callable], Callable]

register_handler(node_classifier, handler)[source]#

Instance-scoped counterpart of register. See _route_registration.

Parameters:
Return type:

None

format_param_grid(rows, base_context)[source]#

Format a params grid (rows of cells, each cell already rendered to a list of components like ["f", "=", "1"], or None for padding) into the final text block.

The base implementation is plain-text oriented: cell components are joined by the context space, columns are left-justified to a shared per-column width, columns are separated by a gap and rows by the context newline. Renderers that emit tabular markup (LaTeX array, HTML table) override this.

Parameters:
Return type:

str

format_calc_grid(rows, base_context)[source]#

Format a multi-line (“long”) calc. rows are [id, eq, value] triples: the first carries the assignment target, the rest an empty id so every row aligns on eq. A row with an empty eq continues the value of the row above (a line continuation in the source) and is indented by CONTINUATION_INDENT.

The base implementation is plain-text oriented: the id column is padded to a common width so the = signs line up, and rows are joined by the context newline. Renderers that emit tabular markup (HTML table, LaTeX aligned) override this.

Parameters:
Return type:

str

class handcalcs.renderers.ASCIIRenderer[source]#

Renders a calculation as monospaced text for terminals, logs and docstrings.

Headings are underlined in the setext/reStructuredText style and set off by blank lines. Calc lines are aligned on their first =: the left-hand sides are right-aligned to the widest one between two headings.

complete(tree, base_context)[source]#

A function called at the completion of every render. This function is intended to be over-ridden by individual renderers in order to “complete” the rendering.

The BaseRenderer that all renderers inherit from includes a special “.join” method with the same function signature as this one. For many renderers that return a str, calling the .join() method will be sufficient as the implementation for this function.

Parameters:
Return type:

str

class handcalcs.renderers.LatexRenderer[source]#

Renders a HandCalcs sequence as a LaTeX fragment (requires amsmath) that can be pasted into the body of a .tex document.

Prose (headings, comment paragraphs, imports, block headers) is emitted as text-mode paragraphs. Each run of calc lines (a CalcsBlock) becomes one flush-left display-style aligned group, aligned on the first = of every row. The body of a nested block (if/for/def) is indented with a list environment.

The class attributes below are the structural tokens; override them in a subclass to change the document wrappers.

complete(tree, base_context)[source]#

A function called at the completion of every render. This function is intended to be over-ridden by individual renderers in order to “complete” the rendering.

The BaseRenderer that all renderers inherit from includes a special “.join” method with the same function signature as this one. For many renderers that return a str, calling the .join() method will be sufficient as the implementation for this function.

Parameters:
join_item(item, depth, base_context)[source]#

LaTeX counterpart of BaseRenderer.join_item. Master-list shapes:

  • bare string -> a text paragraph (headings, the params grid, which is already a complete math group); a one-shot line break -> \medskip;

  • headerless block whose body is opened by MATH_OPEN (a CalcsBlock) -> one aligned math group, rows separated by \\, no group at all when every row is empty (e.g. all lines # hc: -i);

  • other headerless block (a CommentsBlock) -> its body at this level;

  • [header, body] -> the header paragraph, then the body indented in a list environment;

  • all-string list -> a text paragraph whose parts join with a space.

Parameters:
Return type:

list[str]

paragraph(text)[source]#

Prefix a text paragraph with PARAGRAPH (\noindent) unless it is already a complete structural command (a heading or a math group).

Parameters:

text (str)

Return type:

str

join_math_group(rows, depth, base_context)[source]#

Join the rows of a CalcsBlock into a single aligned math group.

Parameters:
Return type:

list[str]

math_group(rows)[source]#

Wrap pre-joined aligned rows in MATH_OPEN/MATH_CLOSE, with MATH_SKIP paragraphs before and after.

Parameters:

rows (list[str])

Return type:

str

join_math_row(row, base_context)[source]#

Join one calc line’s components into an aligned row, placing the alignment point & before its first assignment symbol. A string row (a pre-formatted multiline grid, or a one-shot line break) passes through unchanged; a line break is dropped since rows are already separated.

The inline comment of a calc line that shows substitution columns (more than target = value) moves to its own row, at the alignment point, so it does not lengthen an already long row.

Parameters:

base_context (BaseRenderContext)

Return type:

str

format_param_grid(rows, base_context)[source]#

A params grid is its own aligned group, laid out like the v1 params cell: each id = value [comment] cell is an r l column pair and cells are joined by a bare &. As in v1, a thin \; after each value is the gap between cells. In a grid with comments, a comment sits one \; after its value (its leading \quad is dropped) and every non-last cell is followed by a \qquad, so the gap to the next assignment is wider than the gap inside a cell, whether or not that cell has a comment.

Parameters:
Return type:

str

format_calc_grid(rows, base_context)[source]#

A multiline (# hc: -m) calc: one aligned row per [id, eq, value] triple, all aligned on eq. Returned as a single pre-joined string that sits as one row inside its CalcsBlock’s math group.

A row with an empty eq continues the row above (a line continuation in the source) and is indented by CONTINUATION_INDENT. An empty group {} closes the row before it and opens the continuation, so an operator at the break keeps its binary spacing.

Parameters:
Return type:

str

class handcalcs.renderers.MarkdownRenderer[source]#

Renders a HandCalcs sequence as Markdown prose with LaTeX math, for Jupyter-type front-ends that typeset $...$ / $$...$$ with MathJax or KaTeX.

It is the default renderer of the %%render cell magic (the jupyter_renderer config option); its output is shown with IPython.display.Markdown.

Math is inherited from LatexRenderer, with a few changes for MathJax and KaTeX: comparisons use \lt/\gt, and text inside math (inline comments, strings) goes through math_text. The text-mode structure is replaced:

  • headings are # headings, comment lines are plain paragraphs and imports are code spans, all Markdown-escaped (markdown_prose);

  • each calc group is one $$\begin{aligned} ... \end{aligned}$$ on a single physical line, with \\ between the rows;

  • a # hc: -b line break is an   spacer paragraph;

  • the body of an if/for/def block is a blockquote, with one > per nesting level. Blank lines inside a body are quoted too, and every quote ends with an unquoted blank line, so Markdown’s lazy continuation never pulls the following text into a deeper quote (see quote).

See markdown_renderer_plan.md for the reasoning behind each rule.

join_item(item, depth, base_context)[source]#

As LatexRenderer.join_item, except that:

  • the body of a [header, body] block is a blockquote (see quote) rather than a list environment;

  • an all-string line made of complete math groups (consecutive params grids in a block body arrive this way) emits each group as its own paragraph, since two $$ groups cannot share a line.

Parameters:
Return type:

list[str]

quote(chunks)[source]#

Quote a joined block body: every line gets one BLOCK_PREFIX, so a block nested in a quoted body ends up quoted twice. Blank lines between the body’s paragraphs are quoted too (a bare >), which keeps the quote continuous and stops a > text line after a > > text line from being absorbed into the inner quote by lazy continuation. The quote ends with an unquoted blank line, so the following text is not absorbed either.

Parameters:

chunks (list[str])

Return type:

list[str]

paragraph(text)[source]#

Prefix a text paragraph with PARAGRAPH (\noindent) unless it is already a complete structural command (a heading or a math group).

Parameters:

text (str)

Return type:

str

math_group(rows)[source]#

Wrap pre-joined aligned rows in MATH_OPEN/MATH_CLOSE, on one line.

Parameters:

rows (list[str])

Return type:

str

class handcalcs.renderers.HTMLRenderer(theme_color='#0B5F66')[source]#

Renders a calculation as a self-contained HTML fragment for Jupyter, VS Code or any web page.

Fonts, text colour and headings are left to the host page. On top of that, one theme colour (theme_color, default petrol #0B5F66) gives the calculation a quiet visual structure: each result is highlighted, inline comments are muted, imports sit in small code chips, block headers (Since ... is True:, Iterating over ...) are tinted callouts, params grids sit on a tinted panel, multi-line calcs carry an accent bar, and nested block bodies get coloured indent guides. Backgrounds are translucent tints of the theme, so they work on light and dark pages alike.

Every colour is a CSS custom property on .handcalcs (--hc-accent, --hc-guide-1 to --hc-guide-3, --hc-tint, --hc-muted, …), so a host stylesheet can restyle the output without changing the renderer.

Parameters:

theme_color (str)

complete(tree, base_context)[source]#

A function called at the completion of every render. This function is intended to be over-ridden by individual renderers in order to “complete” the rendering.

The BaseRenderer that all renderers inherit from includes a special “.join” method with the same function signature as this one. For many renderers that return a str, calling the .join() method will be sufficient as the implementation for this function.

Parameters:
Return type:

str

format_param_grid(rows, base_context)[source]#

Format a params grid (rows of cells, each cell already rendered to a list of components like ["f", "=", "1"], or None for padding) into the final text block.

The base implementation is plain-text oriented: cell components are joined by the context space, columns are left-justified to a shared per-column width, columns are separated by a gap and rows by the context newline. Renderers that emit tabular markup (LaTeX array, HTML table) override this.

Parameters:
Return type:

str

format_calc_grid(rows, base_context)[source]#

Format a multi-line (“long”) calc. rows are [id, eq, value] triples: the first carries the assignment target, the rest an empty id so every row aligns on eq. A row with an empty eq continues the value of the row above (a line continuation in the source) and is indented by CONTINUATION_INDENT.

The base implementation is plain-text oriented: the id column is padded to a common width so the = signs line up, and rows are joined by the context newline. Renderers that emit tabular markup (HTML table, LaTeX aligned) override this.

Parameters:
Return type:

str

class handcalcs.renderers.ExcelRenderer(sheet_name='Calculations', value_format=None, formula_format=None, filepath=None, general_format='General')[source]#

Renders a HandCalcs sequence as a live, formula-based Excel worksheet.

Every calculation becomes a real Excel formula that references other rows through worksheet-scoped defined names, so editing an input in Excel recalculates the sheet.

Options (all validated here, at instantiation):

  • sheet_name: the worksheet name (Excel’s naming rules apply).

  • value_format / formula_format: how the value column (E) is styled; see “Cell formats” below.

  • filepath: if given, complete() also saves the workbook there.

  • general_format: the Excel number format used for format codes that have no exact Excel equivalent (e.g. the default .5g).

Cell formats#

value_format and formula_format style the two kinds of cell in the value column, so a reader can tell at a glance which cells to edit:

  • value_format styles input cells: literal assignments such as a = 2 or L = 300*mm. These hold plain numbers and are the cells a user edits.

  • formula_format styles formula cells: every calculation such as x = (-b + sqrt(b**2 - 4*a*c)) / (2*a). A calculation forced into the input layout with # hc: -p still holds a formula, so it also takes formula_format.

Both are dicts with any of these keys:

  • "bold": True or False.

  • "text_color": the font colour.

  • "cell_color": the cell’s fill colour; None for no fill.

A colour is "#RRGGBB", "RRGGBB" or an (r, g, b) tuple of ints from 0 to 255. Every key is optional: the keys you give are merged over the defaults, and the keys you leave out keep their default. The defaults are:

value_format   = {"bold": False, "text_color": "#000000", "cell_color": "#FFF2CC"}
formula_format = {"bold": True,  "text_color": "#000000", "cell_color": None}

That is, inputs sit on a light-yellow fill (the common engineering convention for “edit me”) and formulas are bold with no fill. An unknown key, a non-bool "bold" or a malformed colour raises ValueError when the renderer is created. The merged result is available as the value_format and formula_format attributes.

Examples:

from handcalcs import HandCalcs
from handcalcs.renderers.excel import ExcelRenderer

# Defaults: yellow inputs, bold formulas.
renderer = ExcelRenderer()

# Blue text, bold text for inputs, keeping the default yellow fill; for
renderer = ExcelRenderer(value_format={"text_color": "#0000FF", 'bold': True})

# No fill on inputs; formulas on a light-green fill, not bold.
renderer = ExcelRenderer(
    sheet_name='Calculations',
    value_format={"cell_color": None},
    formula_format={"bold": False, "cell_color": (226, 239, 218)},
    filepath='my_calc_sheet.xlsx'
)

# Plain sheet: no highlighting at all.
renderer = ExcelRenderer(
    value_format={"cell_color": None},
    formula_format={"bold": False},
)

workbook = HandCalcs(renderer=renderer)(source)
workbook.save("calcs.xlsx")

functions maps Python callables to formulas.ExcelFunction translations; extend a renderer’s copy to support more functions.

render_root(root, base_context)[source]#

Render every statement into one flat list of rows.

Parameters:

base_context (BaseRenderContext)

Return type:

list[CalcRow | ParamRow | HeadingRow | CommentRow | BlankRow | PageBreakRow]

complete(tree, base_context)[source]#

Lay the rows out as a one-sheet workbook; save it if filepath is set.

Parameters:
  • tree (list[CalcRow | ParamRow | HeadingRow | CommentRow | BlankRow | PageBreakRow])

  • base_context (BaseRenderContext)

Return type:

ExcelWorkbook

write_sheet(workbook, rows, sheet_name=None)[source]#

Write rows (from render()) as a new sheet of an open xlsxwriter.Workbook, for several cells in one workbook. Names are worksheet-scoped, so each sheet may reuse the same variables.

Parameters:
  • rows (list[CalcRow | ParamRow | HeadingRow | CommentRow | BlankRow | PageBreakRow])

  • sheet_name (str | None)

Parameters:
class handcalcs.renderers.WordRenderer(filepath=None, layout='aligned', template=None, document=None)[source]#

Renders a HandCalcs sequence as a Word document (.docx) whose calculations are native, editable Word equations (Office Math Markup, OMML).

Like the LaTeX renderer, the node handlers return math as strings, here OMML fragments (see omml), and the base renderer’s line and block machinery works on them unchanged. complete() writes the render tree as a python-docx Document; python-docx (pip install handcalcs[word]) is imported only then.

Options:

  • filepath: if given, complete() also saves the document there.

  • layout: how a run of calc lines is set out. "aligned" (the default) is one equation array per run, aligned on the first = of each row, as in the LaTeX renderer. "lines" makes each row an equation of its own with no alignment; use it for Google Docs and LibreOffice, which don’t show Word’s equation arrays. A # hc: layout = "lines" comment changes it for the calcs that follow.

  • template: a .docx to start each new document from (a company template: its styles, page setup and any content it already has).

  • document: a python-docx Document to append to instead, so several calcs build one report. Give a template or a document, not both.

The renderer adds two styles when the document doesn’t define them: HC Math for equation paragraphs and HC Code for imports. Define them in a template to restyle both. Equations take their paragraph’s font size.

Examples:

HandCalcs(WordRenderer(filepath="calc.docx"))(source)

report = docx.Document("company.docx")
calc = HandCalcs(WordRenderer(document=report))
calc(beam_source)
calc(column_source)
report.save("report.docx")
Parameters:
complete(tree, base_context)[source]#

Write the render tree as a Word document and return it; save it if filepath is set. The calc is appended to document if one was given, or else to a new document (from template, if given).

Parameters:
Return type:

Any

write_item(writer, item, depth, base_context)[source]#

Word counterpart of BaseRenderer.join_item. Master-list shapes:

  • a Heading, Code or ParamsTable item -> its paragraph or table;

  • a string -> a text paragraph; a one-shot line break -> an empty one;

  • a headerless block whose body opens with a MathGroup (a CalcsBlock) -> its equations (see DocumentWriter.math_group);

  • another headerless block (a CommentsBlock) -> its body at this level;

  • [header, body] -> the header paragraph, then the body indented one level;

  • a calc row (a list of strings) or a CalcGrid on its own -> its equations; a list of plain strings -> a text paragraph.

Parameters:
  • writer (DocumentWriter)

  • depth (int)

Return type:

None

join_math_row(parts, base_context)[source]#

One calc line’s components as an equation row, split before its first assignment symbol (the alignment point). A line with no math (a docstring) is prose; an ignored line is no row.

As in the LaTeX renderer, the inline comment of a line that shows substitution columns (more than target = value) moves to its own row, at the alignment point, so it does not lengthen a long row.

Parameters:

parts (list)

Return type:

list[Row | Prose]

format_param_grid(rows, base_context)[source]#

Format a params grid (rows of cells, each cell already rendered to a list of components like ["f", "=", "1"], or None for padding) into the final text block.

The base implementation is plain-text oriented: cell components are joined by the context space, columns are left-justified to a shared per-column width, columns are separated by a gap and rows by the context newline. Renderers that emit tabular markup (LaTeX array, HTML table) override this.

Parameters:

rows (list)

Return type:

ParamsTable

format_calc_grid(rows, base_context)[source]#

Format a multi-line (“long”) calc. rows are [id, eq, value] triples: the first carries the assignment target, the rest an empty id so every row aligns on eq. A row with an empty eq continues the value of the row above (a line continuation in the source) and is indented by CONTINUATION_INDENT.

The base implementation is plain-text oriented: the id column is padded to a common width so the = signs line up, and rows are joined by the context newline. Renderers that emit tabular markup (HTML table, LaTeX aligned) override this.

Parameters:

rows (list)

Return type:

CalcGrid

Render context#

class handcalcs.renderers.base.RenderContext(space=' ', newline='\n', indent='    ', assign='=', mode='full', format_code='.5g', param_line=False, lpar='(', rpar=')', greek_prefixes=True, subscripts='comma', max_items=10, edge_items=3, **kwargs)[source]#
Parameters:
  • space (str)

  • newline (str)

  • indent (str)

  • assign (str)

  • mode (str)

  • format_code (str)

  • param_line (bool)

  • greek_prefixes (bool)

  • subscripts (str)

  • max_items (Optional[int])

  • edge_items (int)

classmethod sparse(**kwargs)[source]#

Build a context that carries only the given attributes, with no defaults.

This is the line-scoped layer of a BaseRenderContext: because it holds only explicitly-set overrides, BaseRenderContext.current (a ChainMap of line over global) falls through to the global context for every key the line does not override. A full RenderContext() here would instead shadow the global context with its own defaults.

Return type:

RenderContext

class handcalcs.renderers.base.BaseRenderContext(global_context, line_context)[source]#
Parameters:

Helpers#

handcalcs.renderers.latex.latex_identifier(identifier, options=None)[source]#

Typeset a Python identifier as a LaTeX math symbol (see identifiers.parse_identifier).

  • Single underscores are subscripts: N_pl_Rd -> N_{\mathrm{pl,Rd}} in the comma style (the default), x_a_b -> x_{a_{b}} in the nested style.

  • Double underscores are spaces and triple underscores literal underscores.

  • A component that names a greek letter becomes that letter (omega_2 -> \omega_{2}), and so does a Unicode Greek letter (σ_R -> \sigma_{R}, ΔH -> \Delta H) and, with greek_prefixes, a leading Greek name followed by an uppercase letter or a digit (DeltaH -> \Delta H). Each __-separated word gets the same lookup (Delta__H -> \Delta\ H).

  • hat, bar, prime and star decorate the component before them (x_hat -> \hat{x}, M_star -> M^{*}).

  • Any other component longer than one character is a word, set upright (class_section -> \mathrm{class}_{\mathrm{section}}).

  • An identifier in options.symbols is typeset from its spec.

Text that isn’t an identifier (it already holds LaTeX, from a name:pre rule or an earlier pass) is returned unchanged, so the swap is idempotent.

Parameters:
Return type:

str

handcalcs.renderers.identifiers.parse_identifier(identifier, options=IdentifierOptions(greek_prefixes=True, subscripts='comma', symbols={}))[source]#

Parse an identifier into a Symbol:

  • ___ is a literal underscore and __ a space between words;

  • _ starts a subscript, in the options.subscripts style;

  • hat, bar, dot and ddot put an accent over the component before them, prime adds a prime to it and star a superscript * (M_star -> M*, x_hat_max -> x̂ with subscript max);

  • every word is split into Greek and text pieces (split_greek).

An identifier in options.symbols is parsed from its spec instead (see parse_spec). A leading or trailing underscore makes the whole identifier literal text (_x).

Parameters:
Return type:

Symbol

handcalcs.renderers.identifiers.parse_spec(spec, greek_prefixes=True)[source]#

Parse a display spec from the symbols option into a Symbol. The syntax is TeX-like but renderer-neutral:

  • N_{pl,Rd}: subscripts in braces, siblings separated by commas; N_Rd takes the run of letters and digits after _;

  • M^{*}, x^2: superscripts, the same way;

  • x_{a_{b}}: a subscript can have its own subscripts;

  • spaces separate words (Delta H); ' after a base adds a prime;

  • hat(x), bar(x), dot(V), ddot(x) put an accent over a base (bar(x)_{m}: the subscript stays outside);

  • Greek letters as names or Unicode, as in identifiers (Delta H, ΔH, sigma_{max}).

Parameters:
Return type:

Symbol

handcalcs.renderers.identifiers.identifier_options(renderer, base_context=None)[source]#

The identifier options in effect: the greek_prefixes, subscripts and symbols render-context fields (set with set_option or a # hc: comment), defaulting to Greek prefixes on, comma subscripts and no symbols.

Parameters:
  • renderer (Any)

  • base_context (Any)

Return type:

IdentifierOptions

handcalcs.renderers.identifiers.split_greek(word, prefixes=True)[source]#

A word as (is_greek, text) pieces, where a Greek piece’s text is its canonical name (a key of GREEK_UNICODE):

  • "sigma" -> [(True, "sigma")]

  • "ΔHvap" -> [(True, "Delta"), (False, "Hvap")]

  • "DeltaH" -> [(True, "Delta"), (False, "H")] (only with prefixes)

  • "metal" -> [(False, "metal")]

A word with no Greek letter is always one text piece, so a renderer can keep its usual formatting for it.

Parameters:
Return type:

list[tuple[bool, str]]

class handcalcs.renderers.identifiers.Symbol(words, accents=<factory>, primes=0, subscripts=<factory>, superscripts=<factory>)[source]#

One displayed symbol: its base (__-separated words, each a list of split_greek pieces), the accents over the base, its primes, and the symbols set as its subscripts and superscripts. Several subscripts are siblings, shown separated by commas.

Parameters:
class handcalcs.renderers.identifiers.IdentifierOptions(greek_prefixes=True, subscripts='comma', symbols=<factory>)[source]#

How identifiers are parsed: see identifier_options.

Parameters:
handcalcs.renderers.latex.latex_prose(text)[source]#

Escape a comment written as prose (headings, comment paragraphs, inline comments) and typeset its light markup: a pair of backticks becomes \texttt{} and an opening quote becomes a left quote (TeX otherwise sets 'a' as two right quotes). An unpaired backtick is kept literally.

Parameters:

text (str)

Return type:

str

handcalcs.renderers.latex.format_latex_number(value, format_code)[source]#

Format a real number with format_code and typeset any exponent as `` imes 10^{n}`` (e.g. 2e+05 -> 2    imes 10^{5}).

Parameters:

format_code (str)

Return type:

str

handcalcs.renderers.base.infix_binop(node, renderer, allow_spaces=True, base_context=None)[source]#
Parameters:
Return type:

str

handcalcs.renderers.base.render_condition(renderer, condition, base_context, mode)[source]#

Render an if/elif condition in the given mode (‘sym’ or ‘num’) and return the joined string. Shared by the if/elif header handlers.

condition may be the whole test node or a bare deque of comparison components. A Compare is unwrapped to its comparison deque (whose elements are joined tightly, matching the relational-operator symbols); any other test node (e.g. a boolean and/or op, or a lone Name) is rendered directly.

Parameters:
Return type:

str

handcalcs.renderers.base.render_block_body(renderer, node, base_context)[source]#

Render a block as [header_string, body_list]: the header line as a string (the first element), followed by the block’s body lines gathered into their own nested sublist. Indentation is applied by the join step, according to nesting depth, not embedded here.

The header is produced by the handler registered under ‘header:{node.type}’ (empty string if none is registered), so a renderer customizes a block’s intro line simply by registering its own ‘header:…’ handler.

Parameters:
Return type:

list[str, list]