diff --git a/README.md b/README.md index 1cd9eb8..c87e7ad 100644 --- a/README.md +++ b/README.md @@ -331,7 +331,7 @@ Cross-references become plain text ("See Section 1.(a)"), with the same `reftype Inline constructs are recognized at any nesting depth with the parser's own grammar, so code spans, links, and escapes are honored, and `use {braces} freely` stays literal. Block constructs are rewritten wherever their lines carry no container marker, which includes fenced divs; a heading or table caption inside a blockquote or list passes through unchanged, with a warning when it needed numbering or stripping. -`fill_tokens(src, values, classify, templates)` is the companion filler: it resolves template tokens from a plain dict and touches nothing else, so the result is still-symbolic Markdown ready for any exporter. The `classify` callable defines the grammar, mapping a token's `(body, syntax)` to `('var', name)`, `('open', name, inverted)`, or `('close', name)`. Variables take `str(values[name])`; sections keep or drop their span by the value's truthiness (kept sections just lose their markers; no iteration). By default a field missing in either direction raises; with `strict=False` the mismatches land in `.warnings` and unfilled variables stay in place, so a document can be filled in stages. `mdhtml.mustache.fill_md` and `mdhtml.jinja.fill_md` are the shipped instantiations - mustache's classifier reads `#`/`^`/`/` sigils from bodies, jinja's discriminates by delimiter pair (`{% if x %}`/`{% if not x %}`/`{% endif %}`) - and `examples/filldemo.py` shows the mustache one in use. +`fill_tokens(src, values, classify, templates)` is the companion filler: it resolves template tokens from a plain dict and touches nothing else, so the result is still-symbolic Markdown ready for any exporter. The `classify` callable defines the grammar, mapping a token's `(body, syntax)` to `('var', name)`, `('open', name, inverted)` or `('open', name, inverted, bind)`, or `('close', name)`. Variables take `str(values[name])`, with names as dotted paths resolved innermost-first through the enclosing sections' frames; sections keep or drop their span by the value's truthiness. `bind` says what a kept section pushes as the innermost frame: nothing for a pure conditional (jinja's `if`), `'.'` for the section's own value (mustache, so its fields are visible and `{{.}}` names it), or a name for `{bind: value}` (jinja's `for bind in name`). Under a binding open, a list value repeats the span once per item with that item's frame pushed, and an empty list drops it like any other falsy value. By default a field missing in either direction raises; with `strict=False` the mismatches land in `.warnings` and unfilled variables stay in place, so a document can be filled in stages. `mdhtml.mustache.fill_md` and `mdhtml.jinja.fill_md` are the shipped instantiations - mustache's classifier reads `#`/`^`/`/` sigils from bodies, jinja's discriminates by delimiter pair (`{% if x %}`/`{% if not x %}`/`{% endif %}` and `{% for x in xs %}`/`{% endfor %}`) - and `examples/filldemo.py` shows the mustache one in use, iterating a grant table and a list of contingencies. Command-line usage (the `mdhtml` script is installed with the package): diff --git a/examples/README.md b/examples/README.md index 332d1eb..98ef9dd 100644 --- a/examples/README.md +++ b/examples/README.md @@ -10,8 +10,12 @@ notes in an `.ipynb`). It exercises the dialect features that matter across conv - Headings with ids (`## Compensation {#sec-comp}`) referenced from *other* notes: single refs (`[@sec-offer]`), a group (`[@sec-comp; @sec-equity; @sec-atwill]`), and custom text (`[your cash compensation @sec-comp]`). -- Mustache template tokens: inline variables (`{{base_salary}}`) and block section markers - (`{{#equity.options}}` ... `{{/equity.options}}`). +- Mustache template tokens: inline variables (`{{base_salary}}`), conditional section markers + (`{{#equity.options}}` ... `{{/equity.options}}`), and list sections that repeat their span once + per item: a table of `{{#grants}}` rows, and `{{#contingencies}}` bullets naming each item with + `{{.}}`. Names inside a repeated span resolve innermost-first, so a row sees the grant's own + fields, falls back to the letter-wide `{{vesting_schedule}}`, and a grant carrying its own + `class_of_stock` shadows the outer one. - A footnote, for id-namespacing to exercise. ## The build script diff --git a/examples/filldemo.py b/examples/filldemo.py index ed53ef3..8aa3101 100644 --- a/examples/filldemo.py +++ b/examples/filldemo.py @@ -8,8 +8,13 @@ from mdhtml.mustache import MUSTACHE, fill_md values = {'company_common_name': 'Acme Robotics, Inc.', 'candidate_name': 'Alex Rivera', 'job_title': 'Senior Research Engineer', - 'base_salary': '$185,000', 'equity.options': True, 'shares_subject_to_option': '25,000', 'class_of_stock': 'Common Stock', - 'vesting_schedule': 'four years, with a one-year cliff', 'equity.restricted_stock': False, + 'base_salary': '$185,000', 'equity': {'options': True, 'restricted_stock': False}, + 'shares_subject_to_option': '25,000', 'class_of_stock': 'Common Stock', + 'vesting_schedule': 'four years, with a one-year cliff', + 'grants': [{'grant_date': 'September 1, 2026', 'shares': '25,000'}, + {'grant_date': 'March 1, 2027', 'shares': '5,000', 'class_of_stock': 'Series A Preferred'}], + 'contingencies': ['satisfactory completion of a background check', 'your signed confidentiality agreement', + 'documentation of your eligibility to work'], 'offer_expiration_date': 'August 1, 2026', 'hiring_manager_name': 'Sam Devlin', 'offer_date': 'July 23, 2026'} d = Path(__file__).parent diff --git a/examples/legal_demo-bound.docx b/examples/legal_demo-bound.docx index 1675de4..18cd396 100644 Binary files a/examples/legal_demo-bound.docx and b/examples/legal_demo-bound.docx differ diff --git a/examples/legal_demo-filled.md b/examples/legal_demo-filled.md index 5022840..3ce536c 100644 --- a/examples/legal_demo-filled.md +++ b/examples/legal_demo-filled.md @@ -12,8 +12,23 @@ Your base salary will be $185,000 per year, paid on Acme Robotics, Inc.'s normal Subject to approval by Acme Robotics, Inc.'s Board of Directors, you will be granted an option to purchase 25,000 shares of Common Stock at a strike price equal to fair market value on the date of grant. The option will vest over four years, with a one-year cliff. +Your grants under this offer are: + +| Grant date | Shares | Class | Vesting | +|---|---|---|---| +| September 1, 2026 | 25,000 | Common Stock | four years, with a one-year cliff | +| March 1, 2027 | 5,000 | Series A Preferred | four years, with a one-year cliff | + Tax treatment is your responsibility; see also [your cash compensation @sec-comp]. +## Contingencies {#sec-conting} + +This offer is contingent on each of the following: + +- satisfactory completion of a background check +- your signed confidentiality agreement +- documentation of your eligibility to work + ## At-Will Employment {#sec-atwill} Your employment with Acme Robotics, Inc. is at will: either you or the Company may end it at any time, with or without cause. Nothing in [@sec-comp] or [@sec-equity] changes that. diff --git a/examples/legal_demo-filled.pdf b/examples/legal_demo-filled.pdf index 7e5a8cb..b686c36 100644 Binary files a/examples/legal_demo-filled.pdf and b/examples/legal_demo-filled.pdf differ diff --git a/examples/legal_demo-form.docx b/examples/legal_demo-form.docx index 1dac919..ee30a61 100644 Binary files a/examples/legal_demo-form.docx and b/examples/legal_demo-form.docx differ diff --git a/examples/legal_demo-render.md b/examples/legal_demo-render.md index c265a1b..0440725 100644 --- a/examples/legal_demo-render.md +++ b/examples/legal_demo-render.md @@ -1,6 +1,6 @@ # 1. Offer of Employment -`{{company_common_name}}` (the "Company") is pleased to offer `{{candidate_name}}` the position of `{{job_title}}`. This letter summarizes the key terms: Sections 1.(a), 1.(b) and 1.(c). +`{{company_common_name}}` (the "Company") is pleased to offer `{{candidate_name}}` the position of `{{job_title}}`. This letter summarizes the key terms: Sections 1.(a), 1.(b) and 1.(d). ## (a) Compensation @@ -18,9 +18,25 @@ Subject to approval by `{{company_common_name}}`'s Board of Directors, you will Subject to Board approval, you will be granted the right to purchase `{{number_shares}}` shares of `{{class_of_stock}}` under a Restricted Stock Purchase Agreement, vesting over `{{vesting_schedule}}`. `{{/equity.restricted_stock}}` +Your grants under this offer are: + +| Grant date | Shares | Class | Vesting | +|---|---|---|---| +`{{#grants}}` +| `{{grant_date}}` | `{{shares}}` | `{{class_of_stock}}` | `{{vesting_schedule}}` | +`{{/grants}}` + Tax treatment is your responsibility; see also your cash compensation 1.(a). -## (c) At-Will Employment +## (c) Contingencies + +This offer is contingent on each of the following: + +`{{#contingencies}}` +- `{{.}}` +`{{/contingencies}}` + +## (d) At-Will Employment Your employment with `{{company_common_name}}` is at will: either you or the Company may end it at any time, with or without cause. Nothing in Section 1.(a) or Section 1.(b) changes that. @@ -36,4 +52,4 @@ To accept, sign below by `{{offer_expiration_date}}`.
(the "Company") is pleased to offer the position of . This letter summarizes the key terms: Sections 1.(a), 1.(b) and 1.(c).
+(the "Company") is pleased to offer the position of . This letter summarizes the key terms: Sections 1.(a), 1.(b) and 1.(d).
Your base salary will be per year, paid on 's normal payroll schedule and subject to all withholdings required by law.1 Salary is reviewed annually as part of the process described in Section 1..
{{#equity.options}}Subject to approval by 's Board of Directors, you will be granted an option to purchase shares of at a strike price equal to fair market value on the date of grant. The option will vest over .
{{/equity.options}}{{#equity.restricted_stock}}Subject to Board approval, you will be granted the right to purchase shares of under a Restricted Stock Purchase Agreement, vesting over .
-{{/equity.restricted_stock}}Tax treatment is your responsibility; see also your cash compensation 1.(a).
-{{/equity.restricted_stock}}Your grants under this offer are:
+| Grant date | Shares | Class | Vesting |
|---|---|---|---|
{{#grants}} | |||
{{/grants}} |
Tax treatment is your responsibility; see also your cash compensation 1.(a).
+This offer is contingent on each of the following:
+{{#contingencies}}{{/contingencies}}Your employment with is at will: either you or the Company may end it at any time, with or without cause. Nothing in Section 1.(a) or Section 1.(b) changes that.
To accept, sign below by .
@@ -17,7 +34,7 @@Please retain a copy of this letter for your records; the terms in Sections 1.(a), 1.(b) and 1.(c) are the entire agreement.
+Please retain a copy of this letter for your records; the terms in Sections 1.(a), 1.(b) and 1.(d) are the entire agreement.