#!/bin/lua --[[ # encoding: utf-8 # # usage: ptt [-h] foo.tt # # ptt: coded by Kenji Arisawa # version 4.0 # date: 2014/10/05 # Email: arisawa@aichi-u.ac.jp # --]] version="4.0a" -- version No. used in comment of generated text sub=string.sub find=string.find gsub=string.gsub match=string.match gmatch=string.gmatch format=string.format concat=table.concat int=tonumber dbg=print push=table.insert str=require("str") trim=str.trim xfind=str.xfind rfind=str.rfind split=str.split xsplit=str.xsplit trans=str.trans -- constant values to make codes easy to read -- don't change dquote='"' -- double quote tab="\t" quof=false h1opt={l=true,t=true} -- ptt option -- l=true -- logo print -- t=true -- toc print -- e=false -- english -- q=false -- quote -- our preference encoding="utf-8" -- encoding in tt text lang="ja" -- default language; japanese mode; not "jp"! -- initial values hdr={} -- HTML header rdic={} udic={[" & "]='&', [" < "]='<', [" > "]='>'} -- user's dictionary ldic={} -- label dictionary label=nil ritmode=false toc={n=0} -- table of contents seccnt={0,0,0} -- section counter initial value h1p=false -- H1 is printed nline=0 -- next line to be read dline=1 -- debug point. last block command dd=nil -- controls DD+: part -- end of initial values -- start of some generic functions -- function printf(fmt,...) io.write(format(fmt,...)) end function pline(fmt,...) if dd == nil then io.write(format(fmt.."\n",...)) else dd = dd .. format(fmt,...) -- ??? end end function errf(fmt,...) -- error with format io.stderr:write(format(fmt,...)) end function sysfatal(s) errf("error: %s\n",s) os.exit() end function merge(...) -- merge tables t={} u={...} for n=1,#u do for k,v in pairs(u[n]) do t[k] = v end end return t end -- end generic functions -- start ptt5 special functions -- function rquote(s) -- replace double quote to "“" and "”" pair -- s=gsub(s,"(.*) \"([^\"]*)\"","%1 “%2”") s=gsub(s," \"([^\"]*)\""," “%1”") return s end function gline() -- global nline nline=nline+1 return lines[nline] end function ugline() -- global nline if nline>1 then nline=nline-1 end end function subline(s) -- global nline lines[nline] = s end function logo() ---- change as you like ---- pline('Logo') pline('address') ---- end of change ------ end function head(title) local bo if ritmode == true then pline(hdr[1]) table.remove(hdr,1) end pline('') pline('',lang) pline("\n",version) pline('',encoding) pline("%s",title) -- additional header for i,v in ipairs(hdr) do m,f,o = pmatch(v,pat4) if m then f(trim(o)) else pline("%s",v) end end pline("") --errf(#hdr,lines[#hdr]) if bopt then pline("",bopt) else pline("") end end function inc_seccnt(h) -- increase section counter seccnt, h is one of "H2:","H3:","H4:" -- global seccnt local n,k n=int(sub(h,2,2)) if n<2 then return nil end n=n-1 seccnt[n]= seccnt[n]+1 for k=n+1,3 do seccnt[k]=0 end return format("%d.%d.%d",seccnt[1],seccnt[2],seccnt[3]) end function opt(s) -- s is : [opt] [term] [class] local v,o,u,a,t,c a={} c=nil o=nil t=nil o,u=match(s,"^%s*-([^%s]+)%s*(.*)$") --dbg("##1 opt:",s,o,u) s=u or s --dbg("##2 opt:",s) t,c=match(s,"^%s*([^%s]+)%s*(.*)$") --dbg("##3 opt:",s,o,t,c) if c=="" then c=nil end --dbg("##4 opt:",s,o,t,c) return o,t,c end --[[ dbg(opt("")) dbg(opt("-abc")) dbg(opt("!!")) dbg(opt('class="solid"')) dbg(opt("-abc !!")) dbg(opt('-abc class="solid"')) dbg(opt('!! class="solid"')) dbg(opt('-abc !! class="solid"')) --]] function h1(s) -- s should be trimmed -- global seccnt, h1p, quof, lang local lp,tp,v,title,n,k local op={} -- options in H1 --dbg(s) -- H1: [-xx] title -- options: -- e: english -- l: not show lego -- q: quote -- t: not print table of contents if sub(s,1,1)=="-" then m1,m2=match(s,"^-([lteq]+)%s*(.-)$") for n=1,#m1 do op[sub(m1,n,n)]=true end title=m2 else title=s end if op.e then lang = "en" end --dbg(title) head(title) pline('
'); if not op.l then logo() end if op.q then quof = true end pline('

%s

',title) t="目次" if op.e then t="Contents" end if not op.t then ptoc(t) end pline('
'); seccnt={0,0,0} h1p=true end function h2(s) local sid sid=inc_seccnt("H2:") pline('

%s

',sid,repl(s)) end function h3(s) sid=inc_seccnt("H3:") pline('

%s

',sid,repl(s)) end function h4(s) sid=inc_seccnt("H4:") pline('

%s

',sid,repl(s)) end function hr(s) pline('
',s) end function code(s) -- code block -- P: ! local o,term,a,t,line o,term,a=opt(s) printf('
')
	line=gline()
	while line ~= term do
		-- dbg(line)
		pline("%s", t2h(line))
		line=gline()
	end
	pline("
") end function ul(s) -- unordered list pline("") end function ol(s) -- ordered list pline( "
    ") list(s) pline( "
") end function dl(s) -- definition list pline( "
") deflist() pline( "
") end function eol(s) -- end of list if s==nil or s=="" or s=="-" then return true end return false end --[[ # rditem reads data of the following syntax # - item # lines # ... --]] function rditem() local m,line local t={} line=gline() --dbg(line) -- this line should begin with "- ", simple "-" is end of list if line==nil then return nil end m=match(line,"^- %s*(.*)$") if m==nil then --ugline() return nil end push(t,m) line = gline() while line~=nil and line ~= "" and sub(line,1,1) ~= "-" do push(t,trim(line)) line = gline() end ugline() return t end function list(o) -- o is option local line,s,t --dbg("### list o:",o) if o ~= "" then -- o is "-a" or "+a" actlist(o) return end t=rditem() while t do printf("
  • ") for n,k in ipairs(t) do pline("%s
    ",repl(k)) end t=rditem() end end function actlist(o) -- action list local s,h,t t=rditem() while t do h,s=match(t[1],"^([^%s]+)%s+(.*)$") s=repl(s) if o=="-a" then r=format('%s',h,s) else -- +a r=format('%s %s',h,h,s) end pline("
  • %s",r) for n=2,#t do pline("%s
    ",repl(t[n])) end t=rditem() end end function deflist() -- definition list local line,d,t t=rditem() while t do pline("
    %s\n
    ",repl(t[1])) for n=2,#t do pline("%s
    ",repl(t[n])) end t=rditem() end end function actm(s) -- -a: local v v=split(s) if len(v)==0 then sysfatal('no term in "-a:"') end h=v[0] -- the first field in s t=join(sub(v,2)) t=repl(t) pline( '

    %s

    ' , h,t) end function actp(s) -- +a: local v v=split(s) if #v==0 then sysfatal('# no term in "-a:"') end h=v[1] -- the first field in s t=concat(v," ",2) t=repl(t) pline('

    %s %s

    ' , h,h,t) end function item(s) ugline() ul("") end function cent(s) pline('

    %s

    ',repl(s)) end function bcent(s) -- center block o,term,a = opt(s) if a then block(term,format('
    ',a),'
    ') else block(term,'
    ','
    ') end end function kw(s) pline('',s) end function inc(f) -- include, f is file name or path, already trimmed -- include style sheet, javascript local b -- base local e -- extention b,e=match(f,"^(.+)%.([^.]+)$") if b==nil then sysfatal("no file name after 'In:'") end if e=="css" then pline('') return end if e=="js" then pline('') return end end function img(s) -- image inclusion, -- I: foo.jpg [options] -- we asume, s is trimmed -- file [alt [size]] # ptt3 -- file [size [alt]] -- size is 640 or 66% for example local f -- file local r -- rest, alt="...", style="..." etc f,r=match(s,"^([^%s]+)(.*)$") if f==nil then sysfatal("no file name after 'I:'") end pline("
    ") -- svg file if match(f,"^[^.]+%.svg") then if r=="" then pline('', f) else pline('', f, r) end else if r=="" then pline('', f) else pline('', f,r) end end line = gline() if line and sub(line,1,2) == "C:" then pline("
    ") print(sub(line,3)) pline("
    ") pline("
    ") else pline("") ugline() end end function bimg(s) -- image array local o,term,a,line,cap o,term,a=opt(s) line=gline() cap=nil if sub(line,1,3)=="Ca:" then cap = ''..sub(line,3)..'\n' line=gline() end imglist={} while line ~= term do v=xsplit(line) w=nil if #v>2 then w=concat(v," ",3) end --dbg(v[1],v[2],w) push(imglist,{v[1],v[2],w}) line=gline() end n=#imglist if a then pline('',a) else if n == 1 then pline('
    ') else pline('
    ') end end if cap then pline("%s",cap) end w=format('width:%d%%',(100/n)) for key,im in pairs(imglist) do --dbg(im[1],im[2],im[3]) if im[3]==nil then im[3]=w end end pline("\n") for key,im in pairs(imglist) do -- im[1]: name, im[2]:file, im[3]:option fn=im[2] n=rfind(fn,".",true) bn=sub(fn,1,n-1) pline('\n' ,im[3],fn,fn) end pline("") for key,im in pairs(imglist) do pline('' , im[1]) end pline("
    \ %s\
    %s
    ") end function com(s) -- comment -- pass end function bcom(s) -- block comment -- global dline dline=nline-1 o,term,a=opt(s) line=gline() while line ~= term do line=gline() end end function bcom1(s) -- skip for DD:, DD+: -- global line,dline local d,term dline=nline-1 d = split(s) term = d[2] line=gline() while line ~= term do -- dbg("bcom1:",line) line=gline() end end function block(term,b,e) local line pline("%s", b) parse(term,pat1) pline("%s
    \n", e) end function note(s) -- note -- ... isn't defined in HTML4.0 -- but I want to use because this is a note pline('
    %s
    ',repl(s)) end function bnote(s) -- block note o,term,a=opt(s) if a then block(term,format('
    \n',a),'
    ') else block(term,'
    \n','
    ') end end function quote(s) pline("
    %s
    ",repl(s)) end function bquote(s) -- block quote o,t,a=opt(s) if a then block(t,"
    \n","
    ") else block(t,"
    \n","
    ") end end function raw(s) pline(s) end function braw(term) line=gline() while line ~= term do pline("%s", line) line=gline() end end function tablth(line,cl,sep) local args,tr,hs,v,t,n,n1,n2,n3 --dbg("### tablth:",line) --dbg("## tablth:",sep) args=split(line,sep) -- header -- for k,v in pairs(args) do dbg("## tablth",k,v) end --[[ # format of table header: # name:<[width] # left align # name:>[width] # right align # name:[width] # center align # name:-[width] # hidden # name # center align # where [ ] is a meta symbol that denotes option # width must be in unit of pixel --]] tr={["<"]="left",[">"]="right",[""]="center",["-"]="hidden"} hs={} --dbg("## tablth",#args) for n=1,#args do v={} -- default v.align="center" -- alignment v.width="0" -- width, string t=trim(args[n]) v.name=t n1,n2,n3=match(t,"(.*):([<>-]?)([%d]*)") --dbg("## tablth",n,n1,n2,n3) if n1 then v.name=n1 v.align=tr[n2] v.width=n3 end hs[n]=v end -- count emty names and put them to colspan -- "hidden" must be skipped m=0 for n=1,#args do --dbg(n,hs[n].name,hs[n].align) if hs[n].name ~= "" then m = 1 break end end if m > 0 then pline("") for n=1,#args do --dbg(n,hs[n].name,hs[n].align) if hs[n].align ~= "hidden" then pline('%s',cl,hs[n].name) end end pline("") end return hs,v end function tabltd(cl,sep,hs,v) local args,r line=gline() --dbg("### tabltd:",line) while not eol(line) do --dbg("### tabltd",sep,line) args=split(line,sep) --dbg("### args:",#args,args[1],args[2],args[3],args[4]) pline("") r=repl(args[1]) if hs[1].align ~= "hidden" then pline('%s', cl,hs[1].width,hs[1].align,r) end for n=2,#args do --dbg(args[n],hs[n].width,hs[n].align) if hs[n].align ~= "hidden" then r=repl(args[n]) pline('%s', cl,hs[n].width,hs[n].align,r) end end pline("") line=gline() end end function tabl(s) -- format of Ta tag -- Ta: [-x] [sep] [class] -- Ta: | class=noborder # example -- local m1,m2,cl,args,hs,n,sep,v,o --dbg("## tabl",s) o,sep,cl=opt(s) if sep ~= nil and #sep ~= 1 then cl = sep sep = nil end --dbg("## tabl",o,sep,cl) if cl==nil then cl="class=solid" end cl=" "..cl pline("",cl) line=gline() if sub(line,1,3)=="Ca:" then pline('%s', sub(line,4)) line=gline() end pline("") hs,v=tablth(line,cl,sep) tabltd(cl,sep,hs,v) pline("") end function define(s) -- simple definition local a,b a,b=match(s,"([^%s]+)%s+(.*)") --dbg(s,a,b) if a==nil then a=match(s,"([^%s]+)") udic[a] = nil else udic[a] = b end end function bpdef(s) -- block plus defintion -- DD+: key term local a a=split(s) if #a==1 then udic[a[1]] = nil return end term=a[2] dd="" parse(term,pdic1) udic[a[1]]=dd dd=nil end function bsdef(s) -- block simple defintion -- DD: key term local k,t,d k,t = match(s,"^([^%s]+)%s*(.*)$") d='' line=gline() while line ~= t do d=d.."\n" .. line line=gline() end udic[k]=d end function pre(s) -- preformat; s is "" or others pline('
    \t%s', t2h(s))
    	line=gline()
    	while line and sub(line,1,1)==tab do
    		pline("\t%s", t2h(sub(line,2)))
    		line=gline()
    	end
    	pline("
    ") ugline() end function urll(s) -- url link -- translate http://HOST to: -- http://HOST -- http://HOST must be at the beginning of line -- Lua does not have '|', so '(%s|^)' is not allowed s = gsub(s,'%s(https?://[^%s"{}()%[%]\\|^`]+)', ' %1') s = gsub(s,'^(https?://[^%s"{}()%[%]\\|^`]+)', '%1') return s end function repl(s) if s == nil then return s end s=trans(s,udic) -- s=gsub(s,rdic) -- s=replace(s,k,udic[k]) s = urll(s) if quof then s = rquote(s) end return s end function t2h(s) -- text to html -- order is matter ! local t={["&"]="&", ["<"]="<", [">"]=">"} s=gsub(s,"[&<>]",t) return s end function rit(line) -- rit block while sub(line,-2) ~= "}$" do pline("%s", line) line=gline() end pline("%s", line) end function pmatch(line,dic) -- dic: pattern dic -- return: key,func,opt if dic == nil then return nil,nil,nil end for k,v in pairs(dic) do if sub(line,1,#k) == k then return k,v,sub(line,#k+1) end end end function bline(line,pdic,opt) -- opt: for what? local m while not eol(line) do pline("%s
    ",repl(line)) line=gline() if sub(line,1,1)=="-" then ugline() return end end end function parse(term,pat,opt) -- term: terminator; pat: pattern list local m,f,o,line line=gline() while line and line ~= term do --dbg("DBG parse:",nline,line) while true do -- case statement --dbg("DBG:",line, term) if line=="" then pline("

    ") break end if ritmode==true and sub(line,1,2)=="${" then rit(line) break end line = gsub(line,refp,function (m) return ldic[m] end ) subline(line) m,f,o = pmatch(line,pat) if m then --dbg("# DEBUG:", line) --dbg("# DEBUG:m:",m) if f==obsolete then f(line) else f(trim(o)) end break end m = match(line,spec) --dbg("DBG1:",m,line) if m and spectagtab[m] then --dbg("DBG2:",line) pline("%s",line) t = format("",m) braw(t) -- block raw output until t. t is the terminator pline(t) break end if match(line,uncook) then if sub(line,-2) ~= "}" then pline("%s",line) end break end if match(line,obrace) then --dbg("DBG3:",line) pline("%s",line) line=gline() while not match(line,cbrace) do pline("%s",line) line=gline() end pline("%s",line) break end pline("%s
    ",repl(line)) break end line=gline() --dbg("DBG4:",line) end end function init(bob) -- make toc, label -- global toc, label local cnt,line,m,s,sid,key,r,k -- we must read whole data below "H1:" to construct toc etc -- nline=bob line=gline() while line do --dbg(line) -- we must skip pat3 k,m=pmatch(line,pat3) if m then -- enter pat3 m(sub(line,#k+1)) else -- out of pat3 --dbg(line) m,f,o = pmatch(line,pat2) if m then if match(m,"D:") then -- o is "{ " for example f(o) end if match(m,"H[234]:") then sid=inc_seccnt(m) -- something like "1.1.2" toc[sid]=trim(sub(line,4)) toc.n = toc.n + 1 label=sid -- ??? this result is not used end end line = gsub(line,labp,function (m) -- Let -- then m is "tab:hogehoge" -- ldic is label dictionary m1 = match(m,"^([^:]+):") -- m1 is "tab" if ldic[m1] == nil then ldic[m1] = 1 else ldic[m1] = ldic[m1] + 1 end ldic[m] = ldic[m1] return ldic[m] end ) subline(line) end line=gline() end nline=bob end function compf(x,y) local x1 = split(x,".") local y1 = split(y,".") local x2 = format("%4s%4s%4s",x1[1],x1[2],x1[3]) local y2 = format("%4s%4s%4s",y1[1],y1[2],y1[3]) return x2 < y2 end function ptoc(title) -- print toc local n,t --dbg(toc.n) if toc.n < 2 then return end n = toc.n toc.n = nil -- dbg(toc) pline("%s",title) pline("

      ") t={} for k,v in pairs(toc) do push(t,k) end table.sort(t,compf) for k,s in ipairs(t) do -- s is something like "1.1.2" if match(s,"^%d+%.0+%.0+$") then pline('
    • %s %s',s,s,repl(toc[s])) elseif match(s,"^%d+%.%d+%.0+$") then pline('
    • %s %s',s,s,repl(toc[s])) else pline('
    • %s  %s',s,s,repl(toc[s])) end end pline("
    ") pline('

    ') end function mkdic(pat) local pdic={} for k,v in pairs(pat) do pdic[k] = v end return pdic end -- pat0 is used elsewhere -- pat1 is allowed in some block commands pat1= { -- RE pattern ["- "]=item, -- special ["HR:"]=obsolete, -- hr, ["UL:"]=ul, ["OL:"]=ol, ["DL:"]=dl, [tab]=pre, -- special ["I:"]=img, ["II:"]=bimg, ["C:"]=cent, ["CC:"]=bcent, ["#:"]=com, ["##:"]=bcom, ["N:"]=note, ["NN:"]=bnote, ["Q:"]=quote, ["QQ:"]=bquote, ["P:"]=code, ["Ta:"]=tabl, ["!:"]= raw, ["!!:"]=braw, ["-a:"]=obsolete, --actm, ["+a:"]=obsolete, --actp, ["A:"]=obsolete, ["AI:"]=obsolete, ["AI:"]=obsolete, ["B:"]=obsolete, ["T:"]=obsolete, ["R:"]=obsolete } -- outermost level tags pat2= { -- H1: is special ["H4:"]=h4, ["H3:"]=h3, ["H2:"]=h2, ["D:"]=define, ["DD:"]=bsdef, ["DD+:"]=bpdef, } -- pat3 is used for making toc. -- then H1:,.. in comments etc must be skipped pat3={ ["!!:"]=bcom, ["P:"]=bcom, ["##:"]=bcom, ["NN:"]=bcom, ["QQ:"]=bcom, ["II:"]=bcom, ["CC:"]=bcom, ["DD:"]=bcom1, ["DD+:"]=bcom1, } pat4={ -- header level tags, they may be before "H1:" ["In:"]=inc, -- include ["KW:"]=kw, ["D:"]=define, ["DD:"]=bsdef, ["DD+:"]=bsdef, } pat0=merge(pat1,pat2) -- keep this order -- we define some tags in ptt ptth="^(\t|- |[^ ]:|[^ ][^ ][+]?:) *(.*[^ ]|) *$" spectags="script|style|iframe|textarea|form|pre|select|table|ul|ol|dl" t=split(spectags,"|") spectagtab={} for k,v in pairs(t) do spectagtab[v]=true end spec="^<(%w+)[^<>]*>%s*$" -- special tags obrace="^<[^>]*$" cbrace="^.*>.*$" uncook="^<.*>$" -- don't cook the line "< .... >" inbrac=".*<[^>]*$" -- in < > labp="" refp="" if #arg == 0 then sysfatal("usage: ptt [-h] foo.tt") end fn=arg[1] lines={} -- read whole data to lines and trim the trailing spaces for line in io.lines(fn) do line=gsub(line,"([%s]*)$","") push(lines,line) end pdic1=mkdic(pat1) pdic3=mkdic(pat3) nline=0 line=gline() if sub(line,1,3)=="#!/" then -- then rit mode push(hdr,line) ritmode=true line=gline() end while sub(line,1,3) ~= "H1:" do push(hdr,line) line = gline() end bob=#hdr+1 -- beginning of body if sub(lines[bob+1],1,3)=="BO:" then -- body option bopt=sub(lines[#hdr+2],4) bob=bob+1 end hline=line init(bob) -- init() and then h1(), keep this order h1(trim(sub(hline,4))) nline=bob parse(nil,pat0) pline("\n")