mirror of
				https://forge.chapril.org/tykayn/orgmode-to-gemini-blog
				synced 2025-10-09 17:02:45 +02:00 
			
		
		
		
	
		
			
	
	
		
			371 lines
		
	
	
	
		
			9 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
		
		
			
		
	
	
			371 lines
		
	
	
	
		
			9 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
|   | -- This is a gross hack of the sample custom writer for pandoc. | ||
|  | -- It produces output | ||
|  | -- that is very similar to that of pandoc's HTML writer. | ||
|  | -- There is one new feature: code blocks marked with class 'dot' | ||
|  | -- are piped through graphviz and images are included in the HTML | ||
|  | -- output using 'data:' URLs. The image format can be controlled | ||
|  | -- via the `image_format` metadata field. | ||
|  | -- | ||
|  | -- Invoke with: pandoc -t gmi.lua | ||
|  | -- | ||
|  | -- Note:  you need not have lua installed on your system to use this | ||
|  | -- custom writer.  However, if you do have lua installed, you can | ||
|  | -- use it to test changes to the script.  'lua sample.lua' will | ||
|  | -- produce informative error messages if your code contains | ||
|  | -- syntax errors. | ||
|  | 
 | ||
|  | local pipe = pandoc.pipe | ||
|  | local stringify = (require "pandoc.utils").stringify | ||
|  | 
 | ||
|  | -- The global variable PANDOC_DOCUMENT contains the full AST of | ||
|  | -- the document which is going to be written. It can be used to | ||
|  | -- configure the writer. | ||
|  | local meta = PANDOC_DOCUMENT.meta | ||
|  | 
 | ||
|  | -- Choose the image format based on the value of the | ||
|  | -- `image_format` meta value. | ||
|  | local image_format = meta.image_format and stringify(meta.image_format) or "png" | ||
|  | local image_mime_type = | ||
|  |     ({ | ||
|  |     jpeg = "image/jpeg", | ||
|  |     jpg = "image/jpeg", | ||
|  |     gif = "image/gif", | ||
|  |     png = "image/png", | ||
|  |     svg = "image/svg+xml" | ||
|  | })[image_format] or error("unsupported image format `" .. image_format .. "`") | ||
|  | 
 | ||
|  | -- Character escaping | ||
|  | local function escape(s, in_attribute) | ||
|  |     return s:gsub( | ||
|  |         '[<>&"\']', | ||
|  |         function(x) | ||
|  |             if x == "<" then | ||
|  |                 return "<" | ||
|  |             elseif x == ">" then | ||
|  |                 return ">" | ||
|  |             elseif x == "&" then | ||
|  |                 return "&" | ||
|  |             elseif in_attribute and x == '"' then | ||
|  |                 return """ | ||
|  |             elseif in_attribute and x == "'" then | ||
|  |                 return "'" | ||
|  |             else | ||
|  |                 return x | ||
|  |             end | ||
|  |         end | ||
|  |     ) | ||
|  | end | ||
|  | 
 | ||
|  | -- Helper function to convert an attributes table into | ||
|  | -- a string that can be put into HTML tags. | ||
|  | local function attributes(attr) | ||
|  |     local attr_table = {} | ||
|  |     for x, y in pairs(attr) do | ||
|  |         if y and y ~= "" then | ||
|  |             table.insert(attr_table, " " .. x .. '="' .. escape(y, true) .. '"') | ||
|  |         end | ||
|  |     end | ||
|  |     return table.concat(attr_table) | ||
|  | end | ||
|  | 
 | ||
|  | -- Table to store footnotes, so they can be included at the end. | ||
|  | local notes = {} | ||
|  | local links = {} | ||
|  | 
 | ||
|  | -- Blocksep is used to separate block elements. | ||
|  | function Blocksep() | ||
|  |     return "\n\n" | ||
|  | end | ||
|  | 
 | ||
|  | -- This function is called once for the whole document. Parameters: | ||
|  | -- body is a string, metadata is a table, variables is a table. | ||
|  | -- This gives you a fragment.  You could use the metadata table to | ||
|  | -- fill variables in a custom lua template.  Or, pass `--template=...` | ||
|  | -- to pandoc, and pandoc will do the template processing as usual. | ||
|  | function Doc(body, metadata, variables) | ||
|  |     local buffer = {} | ||
|  |     local function add(s) | ||
|  |         table.insert(buffer, s) | ||
|  |     end | ||
|  |     add(body) | ||
|  |     if #notes > 0 then | ||
|  |         add("### footnotes") | ||
|  |         for _, note in pairs(notes) do | ||
|  |             add(note) | ||
|  |         end | ||
|  |     end | ||
|  | 
 | ||
|  |     if #links > 0 then | ||
|  |         add("### links") | ||
|  |         for _, link in pairs(links) do | ||
|  |             add(link) | ||
|  |         end | ||
|  |     end | ||
|  |     return table.concat(buffer, "\n") .. "\n" | ||
|  | end | ||
|  | 
 | ||
|  | -- The functions that follow render corresponding pandoc elements. | ||
|  | -- s is always a string, attr is always a table of attributes, and | ||
|  | -- items is always an array of strings (the items in a list). | ||
|  | -- Comments indicate the types of other variables. | ||
|  | 
 | ||
|  | function Str(s) | ||
|  |     return s | ||
|  | end | ||
|  | 
 | ||
|  | function Space() | ||
|  |     return " " | ||
|  | end | ||
|  | 
 | ||
|  | function SoftBreak() | ||
|  |     return " " | ||
|  | end | ||
|  | 
 | ||
|  | function LineBreak() | ||
|  |     return "\n" | ||
|  | end | ||
|  | 
 | ||
|  | function Emph(s) | ||
|  |     return "/" .. s .. "/" | ||
|  | end | ||
|  | 
 | ||
|  | function Strong(s) | ||
|  |     return "*" .. s .. "*" | ||
|  | end | ||
|  | 
 | ||
|  | function Subscript(s) | ||
|  |     return "_" .. s | ||
|  | end | ||
|  | 
 | ||
|  | function Superscript(s) | ||
|  |     return "^" .. s | ||
|  | end | ||
|  | 
 | ||
|  | function SmallCaps(s) | ||
|  |     return '<span style="font-variant: small-caps;">' .. s .. "</span>" | ||
|  | end | ||
|  | 
 | ||
|  | function Strikeout(s) | ||
|  |     return " --- " .. s .. " --- " | ||
|  | end | ||
|  | 
 | ||
|  | function Link(s, tgt, tit, attr) | ||
|  |     local text = s | ||
|  |     local textIsLink = false | ||
|  |     if string.find(s, "=>", 1, true) ~= nil then | ||
|  |         textIsLink = true | ||
|  |         s = string.sub(s, 2) | ||
|  |     end | ||
|  |     if not string.len(tit) == 0 then | ||
|  |         text = s .. "(" .. tit .. ")" | ||
|  |     end | ||
|  |     local num = #links + 1 | ||
|  |     -- add link to the link table. | ||
|  |     table.insert(links, "=> " .. escape(tgt, true) .. " [" .. num .. "] " .. text) | ||
|  |     -- return the link text and ref number. | ||
|  |     -- sthing not quite right here | ||
|  |     if textIsLink then | ||
|  |         return text .. " [" .. num .. "]" | ||
|  |     end | ||
|  | 
 | ||
|  |     return "<" .. text .. "> [" .. num .. "]" | ||
|  | end | ||
|  | 
 | ||
|  | function Image(s, src, tit, attr) | ||
|  |     return "=> " .. src .. " " .. tit .. "\n" | ||
|  | end | ||
|  | 
 | ||
|  | function Code(s, attr) | ||
|  |     return "`" .. s .. "`" | ||
|  | end | ||
|  | 
 | ||
|  | function InlineMath(s) | ||
|  |     return "$" .. s .. "$" | ||
|  | end | ||
|  | 
 | ||
|  | function DisplayMath(s) | ||
|  |     return "$" .. s + "\n$\n" | ||
|  |     --return "\\[" .. escape(s) .. "\\]" | ||
|  | end | ||
|  | 
 | ||
|  | function SingleQuoted(s) | ||
|  |     return '"' .. s .. '"' | ||
|  | end | ||
|  | 
 | ||
|  | function DoubleQuoted(s) | ||
|  |     return "'" .. s .. "'" | ||
|  | end | ||
|  | 
 | ||
|  | function Note(s) | ||
|  |     local num = #notes + 1 | ||
|  |     -- insert the back reference right before the final closing tag. | ||
|  | 
 | ||
|  |     -- add note to the note table. | ||
|  |     table.insert(notes, "[" .. num .. "] " .. s) | ||
|  |     -- return the footnote reference, linked to the note. | ||
|  |     return "[fn " .. num .. "]" | ||
|  | end | ||
|  | 
 | ||
|  | function Span(s, attr) | ||
|  |     return s | ||
|  | end | ||
|  | 
 | ||
|  | function RawInline(format, str) | ||
|  |     if format == "html" then | ||
|  |         return str | ||
|  |     else | ||
|  |         return "" | ||
|  |     end | ||
|  | end | ||
|  | 
 | ||
|  | function Cite(s, cs) | ||
|  |     return "\nsorry cite not implemented\n" | ||
|  | end | ||
|  | 
 | ||
|  | function Plain(s) | ||
|  |     return s | ||
|  | end | ||
|  | 
 | ||
|  | function Para(s) | ||
|  |     return s .. "\n" | ||
|  | end | ||
|  | 
 | ||
|  | -- lev is an integer, the header level. | ||
|  | function Header(lev, s, attr) | ||
|  |     if lev == 1 then | ||
|  |         return "# " .. s .. "\n" | ||
|  |     elseif lev == 2 then | ||
|  |         return "## " .. s .. "\n" | ||
|  |     elseif lev == 3 then | ||
|  |         return "### " .. s .. "\n" | ||
|  |     else | ||
|  |         return s .. "\n" | ||
|  |     end | ||
|  | end | ||
|  | 
 | ||
|  | function BlockQuote(s) | ||
|  |     return "> " .. s .. "\n" | ||
|  | end | ||
|  | 
 | ||
|  | function HorizontalRule() | ||
|  |     return "```\n----------------------------------------------------------- <hr>\n```\n" | ||
|  | end | ||
|  | 
 | ||
|  | function LineBlock(ls) | ||
|  |     return "LineBlock not implemented" | ||
|  |     --return '<div style="white-space: pre-line;">' .. table.concat(ls, '\n') .. '</div>' | ||
|  | end | ||
|  | 
 | ||
|  | function CodeBlock(s, attr) | ||
|  |     return "```\n" .. s .. "\n```" | ||
|  | end | ||
|  | 
 | ||
|  | function BulletList(items) | ||
|  |     local buffer = {} | ||
|  |     for _, item in pairs(items) do | ||
|  |         table.insert(buffer, "* " .. item) | ||
|  |     end | ||
|  |     return table.concat(buffer, "\n") | ||
|  | end | ||
|  | 
 | ||
|  | function OrderedList(items) | ||
|  |     local buffer = {} | ||
|  |     for _, item in pairs(items) do | ||
|  |         table.insert(buffer, "* " .. item) | ||
|  |     end | ||
|  |     return table.concat(buffer, "\n") | ||
|  | end | ||
|  | 
 | ||
|  | function DefinitionList(items) | ||
|  |     local buffer = {} | ||
|  |     for _, item in pairs(items) do | ||
|  |         local k, v = next(item) | ||
|  |         table.insert(buffer, "### " .. k .. "\n" .. table.concat(v, "\n")) | ||
|  |     end | ||
|  |     return table.concat(buffer, "\n") | ||
|  | end | ||
|  | 
 | ||
|  | -- Convert pandoc alignment to something HTML can use. | ||
|  | -- align is AlignLeft, AlignRight, AlignCenter, or AlignDefault. | ||
|  | local function html_align(align) | ||
|  |     if align == "AlignLeft" then | ||
|  |         return "left" | ||
|  |     elseif align == "AlignRight" then | ||
|  |         return "right" | ||
|  |     elseif align == "AlignCenter" then | ||
|  |         return "center" | ||
|  |     else | ||
|  |         return "left" | ||
|  |     end | ||
|  | end | ||
|  | 
 | ||
|  | function CaptionedImage(src, tit, caption, attr) | ||
|  |     return '<div class="figure">\n<img src="' .. | ||
|  |         escape(src, true) .. | ||
|  |             '" title="' .. escape(tit, true) .. '"/>\n' .. '<p class="caption">' .. escape(caption) .. "</p>\n</div>" | ||
|  | end | ||
|  | 
 | ||
|  | -- Caption is a string, aligns is an array of strings, | ||
|  | -- widths is an array of floats, headers is an array of | ||
|  | -- strings, rows is an array of arrays of strings. | ||
|  | function Table(caption, aligns, widths, headers, rows) | ||
|  |     -- finding out more about lua than I wanted to here... | ||
|  |     local function dQuote(value) | ||
|  |         if value == nil then | ||
|  |             return nil | ||
|  |         end | ||
|  |         return '"' .. value .. '"' | ||
|  |     end | ||
|  | 
 | ||
|  |     -- accumulate table csv in string res | ||
|  |     local res = "```" .. caption .. "\n" | ||
|  |     -- get the top line | ||
|  |     for i, col in pairs(headers) do | ||
|  |         if i > 1 then | ||
|  |             res = res .. "," | ||
|  |         end | ||
|  |         res = res .. dQuote(col) | ||
|  |     end | ||
|  |     res = res .. "\n" | ||
|  | 
 | ||
|  |     -- get the rest of the table | ||
|  |     for i, curRow in pairs(rows) do | ||
|  |         for i, col in pairs(curRow) do | ||
|  |             if i > 1 then | ||
|  |                 res = res .. "," | ||
|  |             end | ||
|  |             res = res .. dQuote(col) | ||
|  |         end | ||
|  |         res = res .. "\n" | ||
|  |     end | ||
|  |     res = res .. "``` " | ||
|  |      | ||
|  |     -- send it to pandoc | ||
|  |     return res | ||
|  | end | ||
|  | 
 | ||
|  | function RawBlock(format, str) | ||
|  |     if format == "html" then | ||
|  |         return str | ||
|  |     else | ||
|  |         return "" | ||
|  |     end | ||
|  | end | ||
|  | 
 | ||
|  | function Div(s, attr) | ||
|  |     return s | ||
|  |     --return "<div" .. attributes(attr) .. ">\n" .. s .. "</div>" | ||
|  | end | ||
|  | 
 | ||
|  | -- The following code will produce runtime warnings when you haven't defined | ||
|  | -- all of the functions you need for the custom writer, so it's useful | ||
|  | -- to include when you're working on a writer. | ||
|  | local meta = {} | ||
|  | meta.__index = function(_, key) | ||
|  |     io.stderr:write(string.format("WARNING: Undefined function '%s'\n", key)) | ||
|  |     return function() | ||
|  |         return "" | ||
|  |     end | ||
|  | end | ||
|  | setmetatable(_G, meta) |