From 7d0171af8b7370909d4c14b6d4d279f1eafb7606 Mon Sep 17 00:00:00 2001 From: Conor Bronsdon Date: Tue, 7 Jul 2026 20:02:09 -0700 Subject: [PATCH 1/2] style: commit canonical mojo format output --- src/markdown/block.mojo | 24 +++++++++---- src/markdown/common.mojo | 55 ++++++++++++++++++++-------- src/markdown/inline.mojo | 77 +++++++++++++++++++++++++++++++--------- src/markdown/render.mojo | 21 +++++++---- test/conformance.mojo | 5 ++- test/test_markdown.mojo | 13 +++---- 6 files changed, 141 insertions(+), 54 deletions(-) diff --git a/src/markdown/block.mojo b/src/markdown/block.mojo index 5a39071..b1280c8 100644 --- a/src/markdown/block.mojo +++ b/src/markdown/block.mojo @@ -395,14 +395,19 @@ def _html_block_type(line: String) -> Int: name += byte_char(to_lower_byte(bytes[p])) p += 1 var terminated = p >= n or bytes[p] == SPACE or bytes[p] == GT - if not terminated and bytes[p] == SLASH and p + 1 < n and ( - bytes[p + 1] == GT + if ( + not terminated + and bytes[p] == SLASH + and p + 1 < n + and (bytes[p + 1] == GT) ): terminated = True if not terminated: return 0 if not closing and ( - name == "script" or name == "pre" or name == "style" + name == "script" + or name == "pre" + or name == "style" or name == "textarea" ): return 1 @@ -410,8 +415,11 @@ def _html_block_type(line: String) -> Int: return 6 # Type 7: a complete tag, alone on its line (cannot interrupt a # paragraph — callers treat 7 specially). - if name == "script" or name == "pre" or name == "style" or ( - name == "textarea" + if ( + name == "script" + or name == "pre" + or name == "style" + or (name == "textarea") ): return 0 var tag_len = scan_html_tag(line, i) @@ -588,8 +596,10 @@ def _parse_defs(mut refs: RefMap, content: String) -> String: var got_title = False var title = String() var after_title = 0 - if had_ws and t < n and ( - bytes[t] == QUOTE or bytes[t] == SQUOTE or bytes[t] == LPAREN + if ( + had_ws + and t < n + and (bytes[t] == QUOTE or bytes[t] == SQUOTE or bytes[t] == LPAREN) ): var closer = bytes[t] if closer == LPAREN: diff --git a/src/markdown/common.mojo b/src/markdown/common.mojo index 9051ed2..dfe067e 100644 --- a/src/markdown/common.mojo +++ b/src/markdown/common.mojo @@ -38,7 +38,11 @@ comptime PERIOD = UInt8(0x2E) def is_space_byte(b: UInt8) -> Bool: return ( - b == 0x20 or b == 0x09 or b == 0x0A or b == 0x0B or b == 0x0C + b == 0x20 + or b == 0x09 + or b == 0x0A + or b == 0x0B + or b == 0x0C or b == 0x0D ) @@ -359,13 +363,18 @@ def scan_html_tag(s: String, start: Int) -> Int: if i >= n: return 0 # Comment: (also permits and ) - if i + 2 < n and bytes[i] == BANG and bytes[i + 1] == DASH and ( - bytes[i + 2] == DASH + if ( + i + 2 < n + and bytes[i] == BANG + and bytes[i + 1] == DASH + and (bytes[i + 2] == DASH) ): var j = i + 1 while j + 2 < n: - if bytes[j] == DASH and bytes[j + 1] == DASH and ( - bytes[j + 2] == GT + if ( + bytes[j] == DASH + and bytes[j + 1] == DASH + and (bytes[j + 2] == GT) ): return j + 3 - start j += 1 @@ -374,8 +383,10 @@ def scan_html_tag(s: String, start: Int) -> Int: if _match_at(s, i, "![CDATA["): var j = i + 8 while j + 2 < n: - if bytes[j] == UInt8(0x5D) and bytes[j + 1] == UInt8(0x5D) and ( - bytes[j + 2] == GT + if ( + bytes[j] == UInt8(0x5D) + and bytes[j + 1] == UInt8(0x5D) + and (bytes[j + 2] == GT) ): return j + 3 - start j += 1 @@ -436,9 +447,13 @@ def scan_html_tag(s: String, start: Int) -> Int: i += 1 while i < n: b = bytes[i] - if is_alnum_byte(b) or b == UNDERSCORE or b == COLON or ( - b == PERIOD - ) or b == DASH: + if ( + is_alnum_byte(b) + or b == UNDERSCORE + or b == COLON + or (b == PERIOD) + or b == DASH + ): i += 1 else: break @@ -464,9 +479,15 @@ def scan_html_tag(s: String, start: Int) -> Int: var vlen = 0 while i < n: b = bytes[i] - if is_space_byte(b) or b == QUOTE or b == SQUOTE or ( - b == EQUALS - ) or b == LT or b == GT or b == BACKTICK: + if ( + is_space_byte(b) + or b == QUOTE + or b == SQUOTE + or (b == EQUALS) + or b == LT + or b == GT + or b == BACKTICK + ): break i += 1 vlen += 1 @@ -527,8 +548,12 @@ def is_uni_space(cp: Int) -> Bool: return True if cp >= 0x2000 and cp <= 0x200A: return True - return cp == 0x2028 or cp == 0x2029 or cp == 0x202F or cp == 0x205F or ( - cp == 0x3000 + return ( + cp == 0x2028 + or cp == 0x2029 + or cp == 0x202F + or cp == 0x205F + or (cp == 0x3000) ) diff --git a/src/markdown/inline.mojo b/src/markdown/inline.mojo index 021d745..21fff92 100644 --- a/src/markdown/inline.mojo +++ b/src/markdown/inline.mojo @@ -75,17 +75,37 @@ struct _Chunk(Copyable, Movable): @staticmethod def text(var html: String) -> _Chunk: return _Chunk( - html^, False, UInt8(0), 0, 0, False, False, List[Int](), - List[Int](), False, False, False, 0, + html^, + False, + UInt8(0), + 0, + 0, + False, + False, + List[Int](), + List[Int](), + False, + False, + False, + 0, ) @staticmethod - def delim( - ch: UInt8, count: Int, can_open: Bool, can_close: Bool - ) -> _Chunk: + def delim(ch: UInt8, count: Int, can_open: Bool, can_close: Bool) -> _Chunk: return _Chunk( - String(), True, ch, count, count, can_open, can_close, - List[Int](), List[Int](), False, False, False, 0, + String(), + True, + ch, + count, + count, + can_open, + can_close, + List[Int](), + List[Int](), + False, + False, + False, + 0, ) @staticmethod @@ -94,8 +114,19 @@ struct _Chunk(Copyable, Movable): if is_image: lit = String("![") return _Chunk( - lit^, False, UInt8(0), 0, 0, False, False, List[Int](), - List[Int](), True, True, is_image, pos, + lit^, + False, + UInt8(0), + 0, + 0, + False, + False, + List[Int](), + List[Int](), + True, + True, + is_image, + pos, ) @@ -202,8 +233,11 @@ def _try_autolink_uri(s: String, start: Int) -> Int: i += 1 while i < n and scheme_len < 32: var b = bytes[i] - if is_alnum_byte(b) or b == UInt8(0x2B) or b == UInt8(0x2D) or ( - b == UInt8(0x2E) + if ( + is_alnum_byte(b) + or b == UInt8(0x2B) + or b == UInt8(0x2D) + or (b == UInt8(0x2E)) ): scheme_len += 1 i += 1 @@ -442,7 +476,9 @@ def _strip_tags(html: String) -> String: var e = alt_start while e < j and bytes[e] != QUOTE: e += 1 - out += String(StringSlice(unsafe_from_utf8=bytes[alt_start:e])) + out += String( + StringSlice(unsafe_from_utf8=bytes[alt_start:e]) + ) i = j + 1 else: out += byte_char(b) @@ -671,11 +707,18 @@ def _tokenize(s: String, refs: RefMap) raises -> List[_Chunk]: var run = i while i < n: b = bytes[i] - if b == BSLASH or b == NEWLINE or b == BACKTICK or ( - b == STAR - ) or b == UNDERSCORE or b == LT or b == AMP or ( - b == LBRACKET - ) or b == RBRACKET or b == BANG: + if ( + b == BSLASH + or b == NEWLINE + or b == BACKTICK + or (b == STAR) + or b == UNDERSCORE + or b == LT + or b == AMP + or (b == LBRACKET) + or b == RBRACKET + or b == BANG + ): break i += 1 if i == run: diff --git a/src/markdown/render.mojo b/src/markdown/render.mojo index b6e2481..1f2f290 100644 --- a/src/markdown/render.mojo +++ b/src/markdown/render.mojo @@ -48,14 +48,20 @@ def _render_block(tree: BlockTree, idx: Int, tight: Bool) raises -> String: if tight: return render_inlines(tree.nodes[idx].text, tree.refs) return ( - String("

") + render_inlines(tree.nodes[idx].text, tree.refs) + "

\n" + String("

") + + render_inlines(tree.nodes[idx].text, tree.refs) + + "

\n" ) if kind == B_HEADING: var level = String(tree.nodes[idx].level) return ( - String("" + String("" + render_inlines(tree.nodes[idx].text, tree.refs) - + "\n" + + "\n" ) if kind == B_CODE: var out = String("
 String:
         if info.byte_length() > 0:
             var ib = info.as_bytes()
             var word_end = 0
-            while word_end < len(ib) and ib[word_end] != SPACE and (
-                ib[word_end] != TAB
+            while (
+                word_end < len(ib)
+                and ib[word_end] != SPACE
+                and (ib[word_end] != TAB)
             ):
                 word_end += 1
             out += ' class="language-'
@@ -94,7 +102,8 @@ def _render_block(tree: BlockTree, idx: Int, tight: Bool) raises -> String:
             var open_tag = String("
    \n") if tree.nodes[idx].level != 1: open_tag = ( - String('
      \n' ) return open_tag + items + "
    \n" diff --git a/test/conformance.mojo b/test/conformance.mojo index 4a37e47..81b2079 100644 --- a/test/conformance.mojo +++ b/test/conformance.mojo @@ -70,7 +70,10 @@ def main() raises: print("-----------------------------") for k in range(len(section_names)): print( - section_names[k] + ": " + String(section_pass[k]) + "/" + section_names[k] + + ": " + + String(section_pass[k]) + + "/" + String(section_total[k]) ) print("-----------------------------") diff --git a/test/test_markdown.mojo b/test/test_markdown.mojo index 30ea8b8..1b941b8 100644 --- a/test/test_markdown.mojo +++ b/test/test_markdown.mojo @@ -23,9 +23,7 @@ def test_paragraph() raises: def test_paragraph_separation() raises: - assert_equal( - render_html("aaa\n\nbbb"), "

    aaa

    \n

    bbb

    \n" - ) + assert_equal(render_html("aaa\n\nbbb"), "

    aaa

    \n

    bbb

    \n") def test_setext_heading() raises: @@ -125,14 +123,13 @@ def test_loose_list() raises: def test_code_span() raises: - assert_equal(render_html("use `x = 1` here"), - "

    use x = 1 here

    \n") + assert_equal( + render_html("use `x = 1` here"), "

    use x = 1 here

    \n" + ) def test_double_backtick_code_span() raises: - assert_equal( - render_html("`` a`b ``"), "

    a`b

    \n" - ) + assert_equal(render_html("`` a`b ``"), "

    a`b

    \n") def test_emphasis() raises: From b604d1f5580f5d9104e218ad5d9abb8d4e45342c Mon Sep 17 00:00:00 2001 From: Conor Bronsdon Date: Tue, 7 Jul 2026 20:04:47 -0700 Subject: [PATCH 2/2] Add API-doc site, format gate, recipe.yaml, bench (suite-wide quick wins) --- .github/workflows/docs.yaml | 49 +++++++++++ .github/workflows/test.yml | 5 ++ .gitignore | 4 + bench/bench_parse.mojo | 44 ++++++++++ docs/render_api.py | 165 ++++++++++++++++++++++++++++++++++++ pixi.toml | 3 + recipe.yaml | 39 +++++++++ src/markdown/__init__.mojo | 2 +- 8 files changed, 310 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/docs.yaml create mode 100644 bench/bench_parse.mojo create mode 100644 docs/render_api.py create mode 100644 recipe.yaml diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml new file mode 100644 index 0000000..7c8a80c --- /dev/null +++ b/.github/workflows/docs.yaml @@ -0,0 +1,49 @@ +name: docs + +on: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: true + +jobs: + build-deploy: + runs-on: ubuntu-latest + timeout-minutes: 15 + environment: + name: github-pages + url: ${{ steps.deploy.outputs.page_url }} + steps: + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v5 + + - name: Install Mojo nightly + run: | + uv venv + uv pip install mojo \ + --index https://whl.modular.com/nightly/simple/ \ + --prerelease allow + + - name: Generate API reference + run: | + .venv/bin/mojo doc -o docs/api.json -I src src/markdown + python3 docs/render_api.py docs/api.json docs/site/index.html + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v3 + with: + path: docs/site + + - name: Deploy to GitHub Pages + id: deploy + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 42199f4..0ae9aac 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,5 +23,10 @@ jobs: --prerelease allow .venv/bin/mojo --version + - name: Format check + run: | + .venv/bin/mojo format src/ test/ bench/ + git diff --exit-code || (echo "::error::Run 'pixi run fmt' — sources are not mojo-format clean" && exit 1) + - name: Tests run: .venv/bin/mojo run -I src test/test_markdown.mojo diff --git a/.gitignore b/.gitignore index fc1e371..b0df644 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,7 @@ pixi.lock *.mojopkg __pycache__/ +.bench_parse +docs/api.json +docs/api.html +docs/site/ diff --git a/bench/bench_parse.mojo b/bench/bench_parse.mojo new file mode 100644 index 0000000..7832429 --- /dev/null +++ b/bench/bench_parse.mojo @@ -0,0 +1,44 @@ +"""Throughput benchmark for `render_html` over the CommonMark spec corpus. + +Reports wall-clock per full-corpus pass and MB/s. Run compiled for meaningful +numbers: `mojo build -I src bench/bench_parse.mojo -o .bench_parse` then +`./.bench_parse` (or `pixi run bench`). The corpus is the same spec example +set the conformance scoreboard uses, so the benchmark measures the real +parse+render path. +""" +from std.time import perf_counter_ns + +from markdown import render_html + + +def main() raises: + var raw = open("test/data/spec_cases.txt", "r").read() + var records = raw.split("\x1e") + var docs = List[String]() + var total_bytes = 0 + for r in range(len(records)): + var fields = records[r].split("\x1f") + if len(fields) != 4: + continue + var md = String(fields[2]) + total_bytes += md.byte_length() + docs.append(md^) + var size_mb = Float64(total_bytes) / (1024.0 * 1024.0) + # Warmup + correctness anchor: rendered output size must stay stable. + var anchor = 0 + for i in range(len(docs)): + anchor += render_html(docs[i]).byte_length() + var iterations = 20 + var start = perf_counter_ns() + for _ in range(iterations): + var rendered = 0 + for i in range(len(docs)): + rendered += render_html(docs[i]).byte_length() + if rendered != anchor: + raise Error("inconsistent render") + var elapsed_ns = perf_counter_ns() - start + var per_pass_ms = Float64(elapsed_ns) / Float64(iterations) / 1e6 + var mb_per_s = size_mb / (per_pass_ms / 1000.0) + print("test/data/spec_cases.txt") + print(t" {len(docs)} documents, {total_bytes} bytes of markdown:") + print(t" {per_pass_ms} ms/corpus pass, {mb_per_s} MB/s") diff --git a/docs/render_api.py b/docs/render_api.py new file mode 100644 index 0000000..ee8fdc1 --- /dev/null +++ b/docs/render_api.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python3 +"""Render `mojo doc` JSON into a single self-contained HTML API reference. + +`mojo doc` emits JSON, not HTML; this turns that JSON into a searchable, +theme-aware, dependency-free page. Reusable across the pure-Mojo suite — it +reads the package name/version out of the JSON, nothing is hardcoded. + + mojo doc -o docs/api.json -I src src/ + python3 docs/render_api.py docs/api.json docs/api.html +""" +import html +import json +import os +import sys + + +def esc(s): + return html.escape(s or "") + + +def code(s): + return f'{esc(s)}' + + +def render_overload(o): + sig = o.get("signature", "") + doc = o.get("summary") or o.get("description") or "" + raises = o.get("raisesDoc") or "" + parts = [f'
    {esc(sig)}
    '] + if doc: + parts.append(f'

    {esc(doc)}

    ') + if raises: + parts.append(f'

    raises {esc(raises)}

    ') + return "".join(parts) + + +def render_function(f): + name = f.get("name", "") + body = "".join(render_overload(o) for o in f.get("overloads", [])) + return (f'
    ' + f'

    {esc(name)}fn

    {body}
    ') + + +def render_field(fl): + name = fl.get("name", "") + typ = fl.get("type", "") or fl.get("signature", "") + doc = fl.get("summary") or fl.get("description") or "" + d = f' — {esc(doc)}' if doc else "" + label = f'{code(name)}: {code(typ)}' if typ else code(name) + return f'
  1. {label}{d}
  2. ' + + +def render_struct(s): + name = s.get("name", "") + summary = s.get("summary") or s.get("description") or "" + traits = s.get("parentTraits") or [] + fields = s.get("fields", []) + methods = s.get("functions", []) + out = [f'
    '] + out.append(f'

    {esc(name)}struct

    ') + if traits: + names = [t.get("name", "") if isinstance(t, dict) else str(t) for t in traits] + out.append('

    ' + " · ".join(code(n) for n in names if n) + "

    ") + if summary: + out.append(f'

    {esc(summary)}

    ') + if fields: + out.append('
    Fields
      ') + out.extend(render_field(f) for f in fields) + out.append("
    ") + if methods: + out.append('
    Methods
    ') + out.extend(render_function(m) for m in methods) + out.append("
    ") + return "".join(out) + + +def render_alias(a): + name = a.get("name", "") + sig = a.get("signature", "") + doc = a.get("summary") or a.get("description") or "" + d = f' — {esc(doc)}' if doc else "" + return f'
  3. {code(name)} = {code(sig)}{d}
  4. ' if sig else f'
  5. {code(name)}{d}
  6. ' + + +def render_module(m): + name = m.get("name", "") + summary = m.get("summary") or m.get("description") or "" + aliases = m.get("aliases", []) + functions = m.get("functions", []) + structs = m.get("structs", []) + if not (aliases or functions or structs): + return "" + out = [f'

    {esc(name)}

    '] + if summary: + out.append(f'

    {esc(summary)}

    ') + if aliases: + out.append('
    Aliases
      ') + out.extend(render_alias(a) for a in aliases) + out.append("
    ") + if structs: + out.extend(render_struct(s) for s in structs) + if functions: + out.append('
    Functions
    ') + out.extend(render_function(f) for f in functions) + out.append("
    ") + return "".join(out) + + +CSS = """ +:root{color-scheme:light dark;--bg:#fcfcfb;--fg:#0b0b0b;--muted:#57564f;--panel:#f3f3ef;--border:rgba(0,0,0,.1);--accent:#2a78d6;--code:#0b7285} +@media(prefers-color-scheme:dark){:root{--bg:#0d1117;--fg:#e6edf3;--muted:#8b949e;--panel:#161b22;--border:rgba(255,255,255,.1);--accent:#57c5bb;--code:#7ee0d6}} +*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--fg);font:16px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif} +.wrap{max-width:860px;margin:0 auto;padding:32px 20px 80px} +h1{font-size:1.9rem;margin:0 0 4px}.ver{color:var(--muted);margin:0 0 20px} +code{font-family:"JetBrains Mono",ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.85em;color:var(--code)} +#q{width:100%;padding:10px 12px;border:1px solid var(--border);border-radius:8px;background:var(--panel);color:var(--fg);font-size:1rem;margin-bottom:24px} +.module{border-top:1px solid var(--border);padding-top:8px;margin-top:24px} +h2{font-size:1.3rem;color:var(--accent)} +.item{background:var(--panel);border:1px solid var(--border);border-radius:10px;padding:14px 16px;margin:12px 0} +.item h3,.item h4{margin:0 0 8px;font-size:1.05rem} +.kind{font-size:.7rem;font-weight:600;color:var(--muted);background:var(--bg);border:1px solid var(--border);border-radius:5px;padding:1px 6px;margin-left:8px;vertical-align:middle} +.sig{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:.82rem;background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:8px 10px;overflow-x:auto;white-space:pre;margin:6px 0} +.doc{color:var(--fg);margin:6px 0}.sub{font-size:.75rem;letter-spacing:.05em;text-transform:uppercase;color:var(--muted);margin:14px 0 4px} +.traits{color:var(--muted);margin:2px 0 8px}.raises{color:var(--muted);font-size:.9em;margin:4px 0}.raises span{color:#d03b3b;font-weight:600} +ul{margin:4px 0;padding-left:20px}li{margin:3px 0}.hidden{display:none} +footer{margin-top:40px;color:var(--muted);font-size:.85rem;border-top:1px solid var(--border);padding-top:16px} +a{color:var(--accent)} +""" + +JS = """ +const q=document.getElementById('q'); +q.addEventListener('input',()=>{const v=q.value.toLowerCase(); +document.querySelectorAll('.item').forEach(el=>{ + el.classList.toggle('hidden', v && !(el.dataset.name||'').includes(v) && !el.textContent.toLowerCase().includes(v));}); +document.querySelectorAll('.module').forEach(m=>{ + const any=[...m.querySelectorAll('.item')].some(i=>!i.classList.contains('hidden')); + m.style.display=any||!v?'':'none';});}); +""" + + +def main(): + src, out = sys.argv[1], sys.argv[2] + d = json.load(open(src)) + decl = d.get("decl", {}) + pkg = decl.get("name", "package") + version = d.get("version", "") + summary = decl.get("summary") or decl.get("description") or "" + if os.path.dirname(out): + os.makedirs(os.path.dirname(out), exist_ok=True) + modules = "".join(render_module(m) for m in decl.get("modules", [])) + page = f""" + +{esc(pkg)} — API reference
    +

    {esc(pkg)}

    API reference{f' · {esc(version)}' if version else ''}

    +{f'

    {esc(summary)}

    ' if summary else ''} + +{modules} +
    Generated from mojo doc JSON by docs/render_api.py. No hand-written HTML.
    +
    """ + open(out, "w").write(page) + print(f"wrote {out} ({len(page)} bytes)") + + +if __name__ == "__main__": + main() diff --git a/pixi.toml b/pixi.toml index c518ea0..7f4b2f7 100644 --- a/pixi.toml +++ b/pixi.toml @@ -10,6 +10,9 @@ version = "0.1.0" test = "mojo run -I src test/test_markdown.mojo" conformance = "mojo run -I src test/conformance.mojo" fuzz = "mojo run -I src test/fuzz_runner.mojo" +bench = "mojo build -I src bench/bench_parse.mojo -o .bench_parse && ./.bench_parse" +fmt = "mojo format src/ test/ bench/" +docs = "mojo doc -o docs/api.json -I src src/markdown && python3 docs/render_api.py docs/api.json docs/api.html" [dependencies] mojo = ">=1.0.0b3.dev2026070506,<2" diff --git a/recipe.yaml b/recipe.yaml new file mode 100644 index 0000000..4aa7c30 --- /dev/null +++ b/recipe.yaml @@ -0,0 +1,39 @@ +# rattler-build recipe for publishing mojo-markdown to a conda channel +# (e.g. modular-community). Not wired to CI yet — this documents the intended +# distribution shape and is ready to build the day we decide to publish. +# +# Distributes the pure-Mojo SOURCE TREE, not a compiled `.mojopkg`: a .mojopkg +# embeds the exact compiler version and refuses to load against any other, so a +# benign Mojo nightly bump would break every consumer. Shipping source + a +# runtime `mojo` pin gets reproducibility without ABI lock-in. (Pattern follows +# ehsanmok/flare's recipe.) +# +# Build locally with: rattler-build build --recipe recipe.yaml + +context: + version: "0.1.0" + +package: + name: mojo-markdown + version: ${{ version }} + +source: + path: . + +build: + number: 0 + noarch: generic + script: + - mkdir -p ${{ PREFIX }}/lib/mojo + - cp -r src/markdown ${{ PREFIX }}/lib/mojo/markdown + +requirements: + run: + - mojo >=1.0.0b3.dev2026070506,<2 + +about: + homepage: https://github.com/conorbronsdon/mojo-markdown + repository: https://github.com/conorbronsdon/mojo-markdown + license: MIT + license_file: LICENSE + summary: CommonMark markdown parser and HTML renderer in pure Mojo diff --git a/src/markdown/__init__.mojo b/src/markdown/__init__.mojo index f7f7a80..9b6d4ab 100644 --- a/src/markdown/__init__.mojo +++ b/src/markdown/__init__.mojo @@ -1,4 +1,4 @@ -"""mojo-markdown: CommonMark parsing and HTML rendering in pure Mojo.""" +"""CommonMark parsing and HTML rendering in pure Mojo (mojo-markdown).""" from markdown.block import ( Block,