Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
mod_blog/Lua/format.lua
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
executable file
742 lines (671 sloc)
27.2 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env lua | |
| -- ************************************************************************ | |
| -- Copyright 2019 by Sean Conner. All Rights Reserved. | |
| -- | |
| -- This program is free software; you can redistribute it and/or | |
| -- modify it under the terms of the GNU General Public License | |
| -- as published by the Free Software Foundation; either version 2 | |
| -- of the License, or (at your option) any later version. | |
| -- | |
| -- This program is distributed in the hope that it will be useful, | |
| -- but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| -- GNU General Public License for more details. | |
| -- | |
| -- You should have received a copy of the GNU General Public License | |
| -- along with this program; if not, write to the Free Software | |
| -- Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
| -- | |
| -- Comments, questions and criticisms can be sent to: sean@conman.org | |
| -- | |
| -- ************************************************************************ | |
| -- luacheck: ignore 611 | |
| local ENTITY = require "org.conman.const.entity" | |
| local abnf = require "org.conman.parsers.abnf" | |
| local url = require "org.conman.parsers.url" | |
| local uchar = require "org.conman.parsers.ascii.char" | |
| + require "org.conman.parsers.utf8.char" | |
| local allchar = require "org.conman.parsers.ascii" | |
| + require "org.conman.parsers.utf8.char" | |
| local lpeg = require "lpeg" | |
| local Carg = lpeg.Carg | |
| local Cmt = lpeg.Cmt | |
| local Cc = lpeg.Cc | |
| local Cs = lpeg.Cs | |
| local B = lpeg.B | |
| local C = lpeg.C | |
| local P = lpeg.P | |
| local R = lpeg.R | |
| local S = lpeg.S | |
| -- ******************************************************************** | |
| -- usage: for chunk,data in jpeg_sections(file) do ... end | |
| -- desc: Iterator to cycle through chunks in a JPEG file | |
| -- input: file (userdata/FILE*) already open JPEG file | |
| -- return: chunk (integer) JPEG chunk type | |
| -- data (bin) raw data for that chunk | |
| -- | |
| -- Note: Not all chunks are decoded propery---this is enough to | |
| -- get the image size though, which is all I wanted. | |
| -- ******************************************************************** | |
| local function jpeg_sections(file) | |
| file:seek('set',2) | |
| return function() | |
| local chunk = string.unpack(">I2",file:read(2) or "\255\217") | |
| if chunk == 0xFFD9 then return nil end | |
| local size = string.unpack(">I2",file:read(2)) | |
| if size == 0 then | |
| return chunk,"" | |
| else | |
| return chunk,file:read(size - 2) | |
| end | |
| end | |
| end | |
| -- ******************************************************************** | |
| -- usage: width,height = image_size(filename) | |
| -- desc: Return the image size in pixels | |
| -- input: filename (string) filename of image | |
| -- return: width (integer) width in pixels | |
| -- height (integer) height in pixels | |
| -- ******************************************************************** | |
| local function image_size(filename) | |
| local f = io.open(filename,"rb") | |
| local header = f:read(24) | |
| local width | |
| local height | |
| if header:sub(1,3) == "GIF" then | |
| width,height = string.unpack("<I2I2",header,7) | |
| elseif header:sub(1,8) == "\137PNG\r\n\26\n" then | |
| width,height = string.unpack(">I4I4",header,17) | |
| elseif header:sub(1,2) == "\255\216" then | |
| for chunk,data in jpeg_sections(f) do | |
| if chunk == 0xFFC0 then | |
| height,width = string.unpack(">I2I2",data,2) | |
| break | |
| end | |
| end | |
| else | |
| width = 0 | |
| height = 0 | |
| end | |
| f:close() | |
| return width,height | |
| end | |
| -- ******************************************************************** | |
| -- usage: mime = image_type(filename) | |
| -- desc: Return the image type | |
| -- input: filename (string) filename of image | |
| -- return: mime (string) MIME type of image | |
| -- ******************************************************************** | |
| local function image_type(filename) | |
| local f = io.open(filename,"r") | |
| local header = f:read(8) | |
| local mime | |
| if header:sub(1,3) == "GIF" then | |
| mime = "image/gif" | |
| elseif header:sub(1,8) == "\137PNG\r\n\26\n" then | |
| mime = "image/png" | |
| elseif header:sub(1,2) == "\255\216" then | |
| mime = "image/jpeg" | |
| else | |
| mime = "text/plain" | |
| end | |
| f:close() | |
| return mime | |
| end | |
| -- ******************************************************************** | |
| -- usage: class = url_class(href) | |
| -- desc: Return a class attribute for a link | |
| -- input: href (string) a URL | |
| -- return: class (string) a class attribute | |
| -- ******************************************************************** | |
| local function url_class(href) | |
| local u = url:match(href) | |
| if u.scheme then | |
| if not u.host then | |
| return "external" | |
| elseif u.host:match "conman.org" then | |
| return "site" | |
| else | |
| return "external" | |
| end | |
| else | |
| return "local" | |
| end | |
| end | |
| -- ******************************************************************** | |
| -- usage: f = stack(tag) | |
| -- desc: Return a function for lpeg.Cmt() to track generated | |
| -- HTML tags | |
| -- input: tag (string) HTML tag name | |
| -- return: f (function) a function suitable for lpeg.Cmt() to | |
| -- | generate an opening or closing tag. | |
| -- ******************************************************************** | |
| local function stack(tag) | |
| local st = "<" .. tag .. ">" | |
| local et = "</" .. tag .. ">" | |
| return function(_,position,state) | |
| if state.stack[#state.stack] == tag then | |
| table.remove(state.stack) | |
| return position,et | |
| else | |
| table.insert(state.stack,tag) | |
| return position,st | |
| end | |
| end | |
| end | |
| -- ******************************************************************** | |
| -- ---------------------------------------- | |
| -- acronym expansion into | |
| -- <abbr title="expansion">ABBR</abbr> | |
| -- ---------------------------------------- | |
| local abbrex = C("IPv4") | |
| + C("IPv6") | |
| + C("GHz") | |
| + C("MHz") | |
| + C("POP3") | |
| + C("D&D") | |
| + C("K&R") | |
| + C("NaNoWriMo") | |
| + C("NaNoGenMo") | |
| + P"D&D5" / "D&D5" | |
| + P"D&D" / "D&D" | |
| + P"K&R" / "K&R" | |
| + C(R"AZ" * ((R"ax" * #R"AZ") + R"AZ")^1) | |
| local abbr = Cmt( | |
| abbrex * Carg(1), | |
| function(_,pos,acronym,state) | |
| if state.abbr[acronym] then | |
| return pos,string.format('<abbr title="%s">%s</abbr>', | |
| state.abbr[acronym], | |
| acronym | |
| ) | |
| else | |
| return pos | |
| end | |
| end | |
| ) | |
| -- ------------------ | |
| -- simple substutions | |
| -- ------------------ | |
| local function fraction(text,name) | |
| return B(P(1) - R"09") * P(text) * #(P(1) - R"09") | |
| / ENTITY[name] | |
| end | |
| local tex = P"``" / ENTITY.ldquo | |
| + P"''" / ENTITY.rdquo | |
| + P"-----" / "<hr>" | |
| + P"---" / ENTITY.mdash | |
| + P"--" / ENTITY.ndash | |
| + P"(TM)" / ENTITY.trade | |
| + P"(C)" / ENTITY.copy | |
| + P"(R)" / ENTITY.reg | |
| + P"..." / "\226\128\166" | |
| + P".." / "\226\128\165" | |
| + P"<-" / "\226\134\144" | |
| + P"->" / "\226\134\146" | |
| + P"^st" / "<sup>st</sup>" | |
| + P"^nd" / "<sup>nd</sup>" | |
| + P"^rd" / "<sup>rd</sup>" | |
| + P"^th" / "<sup>th</sup>" | |
| + P"\\-" / ENTITY.shy | |
| + P"\\_" / ENTITY.nbsp | |
| + P"\\ " / "" | |
| + P" /\n" / "<br>\n" | |
| + P"\\&" / "&" | |
| + P"\\<" / "<" | |
| + P"\\/" / "/" | |
| + fraction("1/4",'frac12') | |
| + fraction("1/2",'frac12') | |
| + fraction("3/4",'frac34') | |
| + fraction("1/3",'frac13') | |
| + fraction("2/3",'frac23') | |
| + fraction("1/5",'frac15') | |
| + fraction("2/5",'frac25') | |
| + fraction("3/5",'frac35') | |
| + fraction("4/5",'frac45') | |
| + fraction("1/6",'frac16') | |
| + fraction("5/6",'frac56') | |
| + fraction("1/8",'frac18') | |
| + fraction("3/8",'frac38') | |
| + fraction("5/8",'frac58') | |
| + fraction("7/8",'frac78') | |
| + C"\\" * C(uchar) / "%2" | |
| -- ----------------------------------------- | |
| -- convert HTML entities to UTF-8 characters | |
| -- ----------------------------------------- | |
| local entity = P"&" | |
| + P"<" | |
| + P""" | |
| + P"'" | |
| + P"&#" * C(R"09"^1) * P";" / utf8.char | |
| + P"&" * C(R("az","AZ","09")^1) * P";" / ENTITY | |
| -- -------------------------------------------------------------- | |
| -- Redact data. Format: {{redacted data}} | |
| -- | |
| -- This will convert text between the {{ and }} with X's, with a | |
| -- soft-hypen every 5 characters. This text will be placed in | |
| -- a <SPAN> with a CLASS attribute of 'cut'. | |
| -- -------------------------------------------------------------- | |
| local cut_c = (allchar - P"}}") / "X" | |
| local cut_char = cut_c * cut_c^-4 * (#cut_c * Cc"\194\173")^-1 | |
| local cut = (P"{{" / '<span class="cut">') | |
| * Cs(cut_char^0) | |
| * (P"}}" / '</span>') | |
| -- --------------------------------------------------------- | |
| -- Various shorthand notations for common HTML styling tags | |
| -- --------------------------------------------------------- | |
| local style = Cmt(P"**" * Carg(1),stack "strong") | |
| + Cmt(P"*" * Carg(1),stack "em") | |
| + Cmt(P"`" * Carg(1),stack "code") | |
| + Cmt(P"++" * Carg(1),stack "del") | |
| -- ---------------------------------- | |
| -- Handle paragraphs automagically | |
| -- ---------------------------------- | |
| local paras = C"\n\n" * #(uchar - S"<#-*") / "%1<p>" | |
| local parae = C(uchar) * #(P'\n\n' + P'\n' * P(-1) + P(-1)) / "%1</p>" | |
| local para = paras + parae | |
| -- ------------------------------------------------------------ | |
| -- HTML links. Formats: | |
| -- {^href linktext} | |
| -- {href} | |
| -- | |
| -- NOTE: relative links get a CLASS attribute of 'local', | |
| -- URIs to 'conman.org' get a CLASS attribute of 'site' | |
| -- All others get a CLASS attribute of 'external' | |
| -- ------------------------------------------------------------ | |
| local SPACE = S" \t\r\n" | |
| local urlchar = P"&" / "&" | |
| + R"!~" - P'}' | |
| local urltext = Cs(urlchar^1) | |
| local totext = abbr + tex + entity + style | |
| + (abnf.HTAB + abnf.CRLF) / " " | |
| + (uchar - P"}") | |
| local linktext = Cs(totext^1) | |
| local link = P"{^" * urltext * SPACE^1 * linktext * P"}" | |
| / function(href,text) | |
| return string.format( | |
| [[<a class="%s" href="%s">%s</a>]], | |
| url_class(href), | |
| href, | |
| text | |
| ) | |
| end | |
| + P"{^" * urltext * P"}" | |
| / function(href) | |
| return string.format( | |
| [[<code><a class="%s" href="%s">%s</a></code>]], | |
| url_class(href), | |
| href, | |
| href | |
| ) | |
| end | |
| -- ------------------ | |
| -- Handle HTML tags | |
| -- ------------------ | |
| local tag = abnf.ALPHA * (abnf.ALPHA + abnf.DIGIT)^0 | |
| local aname = abnf.ALPHA * (abnf.ALPHA + P"-")^0 | |
| local EQ = SPACE^0 * P"=" * SPACE^0 | |
| local htmltext = tex + entity + (-SPACE * uchar) | |
| local htmlchard = tex + entity + (-P'"' * uchar) | |
| local htmlchars = tex + entity + (-P"'" * uchar) | |
| local htmltrans = P'"' * Cs(htmlchard^0) * P'"' | |
| + P"'" * Cs(htmlchars^0) * P"'" | |
| + Cs(htmltext^1) | |
| local value = P'"' * htmlchard^0 * P'"' | |
| + P"'" * htmlchars^0 * P"'" | |
| + htmltext^1 | |
| local attr = P"title" * EQ * htmltrans | |
| + P"alt" * EQ * htmltrans | |
| + aname * (EQ * value)^-1 | |
| local attrs = SPACE^0 * attr | |
| local htmltag = P"<" * tag * attrs^0 * SPACE^0 * P">" | |
| + P"<!--" * (P(1) - P"-->")^0 * P"-->" | |
| + P"</" * tag * P">" | |
| -- ----------------------------- | |
| -- Shorthand for <H1> .. <H5> | |
| -- ----------------------------- | |
| local hchar = cut + abbr + tex + entity + uchar | |
| local header = C"\n***** " * Cs(hchar^1) * #P"\n" / "\n<h5>%2</h5>" | |
| + C"\n**** " * Cs(hchar^1) * #P"\n" / "\n<h4>%2</h4>" | |
| + C"\n*** " * Cs(hchar^1) * #P"\n" / "\n<h3>%2</h3>" | |
| + C"\n** " * Cs(hchar^1) * #P"\n" / "\n<h2>%2</h2>" | |
| + C"\n* " * Cs(hchar^1) * #P"\n" / "\n<h1>%2</h1>" | |
| -- ******************************************************************** | |
| -- #+BEGIN blocks. We start with #+source | |
| -- | |
| -- <sol> = start-of-line | |
| -- <eol> = end-of-line | |
| -- <sp> = whitespace | |
| -- | |
| -- <sol> '#+source' <space> <language> <eol> | |
| -- <raw text> | |
| -- <sol> '#-source' <eol> | |
| -- | |
| -- ******************************************************************** | |
| local src_char = P"<" / "<" | |
| + P"&" / "&" | |
| + allchar - P"\n#-source\n" | |
| local begin_src = C(S" \t"^0) * C(R("AZ","az")^1) * C(P"\n") | |
| / '\n<pre class="language-%2" title="%2">\n' | |
| * Cs(src_char^0) | |
| * (P"\n#-source" * #P"\n" / "\n</pre>\n") | |
| -- ******************************************************************** | |
| -- #+table | |
| -- | |
| -- <sol> '#+table' <eol> | |
| -- [ <sol> '*' <text> 0(<ht> <text>) <eol> ] -- header row | |
| -- [ <sol> '**' <text> 0(<ht> <text>) <eol> ] -- footer row | |
| -- 1(<sol> <text> 0(<ht> <text>) <eol> ) -- data row | |
| -- <sol> '#-table' <eol> | |
| -- | |
| -- ******************************************************************** | |
| local int = P"0" + (R"19" * R"09"^0) | |
| local frac = P"." * R"09"^1 | |
| local exp = S"Ee" * S"+-"^-1 * R"09"^1 | |
| local number = (P"-"^-1 * int * frac^-1 * exp^-1) * #S"\t\n" | |
| local table_caption = C(S" \t"^0) * Cs(hchar^1) * C"\n" | |
| / " <caption>%2</caption>\n" | |
| + P"\n" / "" | |
| local th = Cc'<th>' * Cs(hchar^1) * Cc'</th>' * (P"\t" / " ")^-1 | |
| local table_header = P"*" / "" | |
| * Cc' <thead>\n <tr>' | |
| * th^1 | |
| * Cc'</tr>\n </thead>' | |
| * P"\n" | |
| local table_footer = P"**" / "" | |
| * Cc' <tfoot>\n <tr>' | |
| * th^1 | |
| * Cc'</tr>\n </tfoot>' | |
| * P'\n' | |
| local td = Cc'<td class="num">' * number * Cc'</td>' * (P"\t" / " ")^-1 | |
| + Cc'<td>' * Cs(hchar^1) * Cc'</td>' * (P"\t" / " ")^-1 | |
| local tr = -P"#-table" * Cc' <tr>' * td^1 * Cc'</tr>' * P"\n" | |
| local table_body = Cc' <tbody>\n' | |
| * tr^1 | |
| * Cc' </tbody>\n' | |
| local begin_table = Cc"\n<table>\n" * table_caption | |
| * table_header^-1 | |
| * table_footer^-1 | |
| * table_body | |
| * (P"#-table" * #P"\n" / "</table>\n") | |
| -- ******************************************************************** | |
| -- | |
| -- <sol> '#+email' [ <sp> 'all' <sp>0 ] <eol> | |
| -- <email headers> | |
| -- <eol> | |
| -- <email body> | |
| -- <sol> '#-email' <eol> | |
| -- | |
| -- ******************************************************************** | |
| local LWSP = (abnf.WSP + (abnf.CRLF * abnf.WSP) / " ") | |
| local email_opt = S" \t"^0 * P"all" * S" \t"^0 / "" | |
| * Cmt(Carg(1),function(_,pos,s) s.email_all = true return pos end) | |
| local hdr_name = (R("AZ","az","--","__","09") - P":")^1 | |
| local hdr_char = P"<" / "<" | |
| + P"&" / "&" | |
| + P">" / ">" | |
| local hdr_text = LWSP + hdr_char + abbr + cut + tex + entity + uchar | |
| local hdr_value = Cc'<dd>' * Cs(hdr_text^1) * Cc'</dd>' | |
| local hdr_generic = Cmt( | |
| Carg(1) * C(hdr_name) * P":" * Cs(hdr_text^1) * abnf.CRLF, | |
| function(_,pos,s,name,val) | |
| if s.email_all then | |
| return pos,string.format(" <dt>%s</dt><dd>%s</dd>\n",name,val) | |
| else | |
| return pos,"" | |
| end | |
| end | |
| ) | |
| local email_text = header + htmltag + abbr + tex + entity + link | |
| + cut + style | |
| + para | |
| + (allchar - P"#-email") | |
| local email_hdrs = (P"From:" / " <dt>From</dt>") * hdr_value * abnf.CRLF | |
| + (P"To:" / " <dt>To</dt>") * hdr_value * abnf.CRLF | |
| + (P"Subject:" / " <dt>Subject</dt>") * hdr_value * abnf.CRLF | |
| + (P"Date:" / " <dt>Date</dt>") * hdr_value * abnf.CRLF | |
| + hdr_generic | |
| local begin_email = Cc'\n<blockquote>\n <dl class="header">' * email_opt^-1 * P"\n" | |
| * email_hdrs^1 | |
| * Cc' </dl>\n' | |
| * abnf.CRLF * Cc'<p>' | |
| * email_text^0 | |
| * (P"#-email" * #P"\n" / "\n</blockquote>") | |
| -- ******************************************************************** | |
| -- #+quote | |
| -- | |
| -- <sol> #+quote | |
| -- <text> | |
| -- <sol> #-quote | |
| -- | |
| -- ******************************************************************** | |
| local title_char = P"&" / "&" | |
| + P"<" / "<" | |
| + P">" / ">" | |
| + P'"' / """ | |
| + allchar | |
| local title_text = Cs(title_char^1) | |
| local quote_text = header + htmltag + abbr + tex + entity + link | |
| + cut + style | |
| + para | |
| + (allchar - P"#-quote") | |
| local begin_quote = Cmt( | |
| Carg(1), | |
| function(_,pos,state) | |
| local htag = '\n<blockquote' | |
| if state.quote.cite then | |
| htag = htag .. string.format(" cite=%q",title_text:match(state.quote.cite)) | |
| end | |
| if state.quote.title then | |
| htag = htag .. string.format(" title=%q",title_text:match(state.quote.title)) | |
| end | |
| htag = htag .. ">" | |
| return pos,htag | |
| end | |
| ) | |
| * quote_text^0 | |
| * Cmt( | |
| P"#-quote" * #P"\n" * Carg(1), | |
| function(_,pos,state) | |
| local via | |
| local cite | |
| local par | |
| if state.quote.via_url and state.quote.via_title then | |
| via = string.format( | |
| [[Via <a class="%s" href="%s">%s</a>, ]], | |
| url_class(state.quote.via_url), | |
| state.quote.via_url, | |
| state.quote.via_title | |
| ) | |
| elseif state.quote.via_url and not state.quote.via_title then | |
| via = string.format( | |
| [[Via <code><a class="%s" href="%s">%s</a></code>, ]], | |
| url_class(state.quote.via_url), | |
| state.quote.via_url, | |
| state.quote.via_url | |
| ) | |
| elseif not state.quote.via_url and state.quote.via_title then | |
| via = string.format([[Via %s, ]],state.quote.via_title) | |
| end | |
| if state.quote.cite and state.quote.title then | |
| cite = string.format( | |
| [[<cite><a class="%s" href="%s">%s</a></cite>]], | |
| url_class(state.quote.cite), | |
| state.quote.cite, | |
| state.quote.title | |
| ) | |
| elseif state.quote.cite and not state.quote.title then | |
| cite = string.format( | |
| [[<cite><code><a class="%s" href="%s">%s</a></code></cite>]], | |
| url_class(state.quote.cite), | |
| state.quote.cite, | |
| state.quote.cite | |
| ) | |
| elseif not state.quote.cite and state.quote.title then | |
| cite = string.format([[<cite>%s</cite>]],state.quote.title) | |
| end | |
| if via or cite then | |
| par = string.format( | |
| '<p class="cite">%s%s</p>', | |
| via or "", | |
| cite or "" | |
| ) | |
| end | |
| state.quote.cite = nil | |
| state.quote.title = nil | |
| state.quote.via_url = nil | |
| state.quote.via_title = nil | |
| return pos,string.format("%s\n</blockquote>\n",par or "") | |
| end | |
| ) | |
| -- ******************************************************************** | |
| -- #+comment | |
| -- | |
| -- <sol> #-comment <eol> | |
| -- text | |
| -- <sol> #-comment <eol> | |
| -- | |
| -- NOTE: These blocks cannot nest. Bad things will happen. | |
| -- XXX: does not display for some reason | |
| -- ******************************************************************** | |
| local begin_comment = (P(1) - P"\n#-comment")^0 / "<!-- comment -->" | |
| * (P"\n#-comment" * #P"\n") / "" | |
| -- ******************************************************************** | |
| -- #+photo | |
| -- | |
| -- <sol> #+photo <eol> | |
| -- <sol> displayfile <sp> linkfile <sp> <text> | |
| -- <sol> #-photo | |
| -- | |
| -- NOTE: If linkfile is "-" then no link is generated. | |
| -- ******************************************************************** | |
| local pf_char = tex | |
| + entity | |
| + P'"' / '"' | |
| + P"&" / '&' | |
| + uchar | |
| local pf_image = (P"\n" - P"#-photo") / "" | |
| * C(uchar^1) * (S" \t"^1 / "") | |
| * C(uchar^1) * (S" \t"^1 / "") | |
| * Cs(pf_char^1) | |
| / function(_,display,_,target,_,title) | |
| local width,height = image_size(display) | |
| if target == '-' then | |
| return string.format( | |
| ' <img src="%s" width="%d" height="%d" alt="[%s]" title="%s">\n', | |
| display, | |
| width, | |
| height, | |
| title, | |
| title | |
| ) | |
| else | |
| local mime = image_type(target) | |
| return string.format( | |
| ' <a type="%s" class="notype" href="%s"><img src="%s" width="%d" height="%d" alt="[%s]" title="%s"></a>\n', -- luacheck: ignore | |
| mime, | |
| target, | |
| display, | |
| width, | |
| height, | |
| title, | |
| title | |
| ) | |
| end | |
| end | |
| local begin_pf = #P"\n" * Cc'\n<div class="pf">\n' | |
| * pf_image^1 | |
| * (P"\n#-photo" * #P"\n" / "</div>") | |
| -- ******************************************************************** | |
| -- #+ATTR_QUOTE | |
| -- | |
| -- <sol> '#+ATTR_QUOTE:' <sp> [<cite> | <title> | <via-url> | <via-title> ] <eol> | |
| -- | |
| -- <cite> -> ':cite' <sp> <url> | |
| -- <title> -> ':title' <sp> <text> | |
| -- <via-url> -> ':via-url' <sp> <url> | |
| -- <via-title> -> ':via-title' <sp> <text> | |
| -- | |
| -- NOTE: Only one attribute per #+ATTR_QUOTE is supported. | |
| -- Multiple #+ATTR_QUOTE lines can be specified | |
| -- ******************************************************************** | |
| local quote_attrs = P":cite" / "cite" | |
| + P":title" / "title" | |
| + P":via-url" / "via_url" | |
| + P":via-title" / "via_title" | |
| local attr_quote = S" \t"^1 / "" | |
| * Cmt(Carg(1) * quote_attrs * C(S" \t"^1) * C((allchar - P"\n")^1), | |
| function(_,pos,state,cite,_,text) | |
| state.quote[cite] = text | |
| return pos,"" | |
| end) | |
| -- ******************************************************************** | |
| -- Top level #+BEGIN blocks definition | |
| -- ******************************************************************** | |
| local begin = P"source" / "" * begin_src | |
| + P"table" / "" * begin_table | |
| + P"email" / "" * begin_email | |
| + P"quote" / "" * begin_quote | |
| + P"photo" / "" * begin_pf | |
| + P"comment" / "" * begin_comment | |
| local battr = P"_QUOTE:" / "" * attr_quote | |
| local blocks = P"\n#+" / "" * begin | |
| + P"\n#+ATTR" / "" * battr | |
| -- ******************************************************************** | |
| -- Entry header | |
| -- ******************************************************************** | |
| local entry_header do | |
| local sol = P"\n"^-1 | |
| local echar = tex + entity + uchar | |
| local acronyms = (P"\n"^-1 * S" \t"^1) / "" | |
| * Cmt( | |
| abbrex | |
| * C(S" \t"^1) | |
| * C(tex + entity + uchar^1) | |
| * Carg(1), | |
| function(_,pos,abrev,_,expansion,state) | |
| state.abbr[abrev] = expansion | |
| return pos,"" | |
| end) | |
| local abbrh = (sol * P"abbr:") / "" * acronyms^1 | |
| local author = sol * P"author:" * Cs(uchar^0) | |
| local title = sol * P"title:" * Cs(echar^0) | |
| local class = sol * P"class:" * Cs(echar^0) | |
| local status = sol * P"status:" * Cs(echar^0) | |
| local date = sol * P"date:" * uchar^0 | |
| local adtag = sol * P"adtag:" * Cs(echar^0) | |
| local email = sol * P"email:" * uchar^0 | |
| local filter = sol * P"filter:" * uchar^0 | |
| entry_header = ( | |
| author | |
| + title | |
| + class | |
| + status | |
| + date | |
| + adtag | |
| + filter | |
| + abbrh | |
| )^0 | |
| end | |
| -- ******************************************************************** | |
| local char = blocks | |
| + header | |
| + htmltag | |
| + abbr + tex + entity | |
| + link | |
| + cut + style | |
| + para | |
| + allchar | |
| local text = Cs(entry_header * char^0) | |
| -- ******************************************************************** | |
| local state = | |
| { | |
| email_all = false, | |
| stack = {}, | |
| quote = {}, | |
| abbr = {}, | |
| } | |
| local data = io.stdin:read("*a") | |
| io.stdout:write(text:match(data,1,state)) |