From 2fd71e902959806178510b43b82e2c5bbee84b16 Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Fri, 24 Apr 2026 12:53:53 -0400 Subject: [PATCH] feat: map user-facing file extensions to pandoc format names in export URIs Users hitting /api/v1/export/threads/{id}.md got 'Unsupported format: md' because pandoc's internal name is 'markdown'. Add EXTENSION_ALIASES + resolve_format() so common extensions map to the right pandoc format: .md -> markdown .html -> html5 .tex -> latex .txt -> plain .epub -> epub3 .wiki -> mediawiki .adoc -> asciidoc ... Pandoc's own format names pass through unchanged (commonmark_x, docbook5, fb2, etc.) via fallback. --- remarkbox/api/export.py | 11 ++++++++--- remarkbox/lib/pandoc.py | 44 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/remarkbox/api/export.py b/remarkbox/api/export.py index cdf0416..9d9b805 100644 --- a/remarkbox/api/export.py +++ b/remarkbox/api/export.py @@ -24,6 +24,7 @@ from remarkbox.lib.pandoc import ( node_tree_to_markdown, namespace_to_markdown, get_available_output_formats, + resolve_format, CONTENT_TYPES, FILE_EXTENSIONS, BINARY_FORMATS, @@ -35,10 +36,14 @@ log = logging.getLogger(__name__) def _parse_format_from_subpath(subpath): - """Extract format from the subpath (e.g. 'abc-123.epub' -> ('abc-123', 'epub')).""" + """Extract format from the subpath (e.g. 'abc-123.md' -> ('abc-123', 'markdown')). + + Extensions are resolved through EXTENSION_ALIASES so users can hit common + file extensions (.md, .html, .tex) instead of pandoc's internal names. + """ if "." in subpath: - parts = subpath.rsplit(".", 1) - return parts[0], parts[1] + stem, ext = subpath.rsplit(".", 1) + return stem, resolve_format(ext) return subpath, "markdown" diff --git a/remarkbox/lib/pandoc.py b/remarkbox/lib/pandoc.py index 551a74c..e0433be 100644 --- a/remarkbox/lib/pandoc.py +++ b/remarkbox/lib/pandoc.py @@ -111,6 +111,50 @@ FILE_EXTENSIONS = { } +# URL extension → pandoc format name. Lets users hit intuitive extensions +# (`thread.md`, `thread.html`, `thread.tex`) instead of pandoc's internal +# names (`markdown`, `html5`, `latex`). +EXTENSION_ALIASES = { + "md": "markdown", + "markdown": "markdown", + "htm": "html5", + "html": "html5", + "tex": "latex", + "txt": "plain", + "1": "man", + "wiki": "mediawiki", + "adoc": "asciidoc", + "asciidoc": "asciidoc", + "rst": "rst", + "org": "org", + "rtf": "rtf", + "docx": "docx", + "odt": "odt", + "epub": "epub3", + "pdf": "pdf", + "pptx": "pptx", + "fb2": "fb2", + "jira": "jira", + "typ": "typst", + "texi": "texinfo", + "opml": "opml", + "icml": "icml", + "json": "json", + "xml": "docbook5", + "ipynb": "ipynb", +} + + +def resolve_format(ext): + """Map a URL extension (lowercase) to its pandoc format name. + + Falls back to the raw extension so pandoc's own format names + (`commonmark_x`, `docbook5`, etc.) pass through unchanged. + """ + ext = ext.lower() + return EXTENSION_ALIASES.get(ext, ext) + + def get_available_output_formats(): """Return the set of output formats pandoc supports on this system.""" try: