Fix miscalculations and infinite loops in tab width calculation
[notion/jeffpc.git] / build / mkman.lua
blobda97d5036a0cd23978ecb9e450e62cb78afe1216
1 --
2 -- build/mkman.lua
3 --
4 -- Translates bindings from Ion configuration into a listing for
5 -- manual pages.
6 --
9 -- Translations {{{
11 local translations={}
13 local function gettext(x)
14 local t=translations[x]
15 if not t or t=="" then
16 return x
17 else
18 return t
19 end
20 end
22 local function TR(x, ...)
23 return string.format(gettext(x), ...)
24 end
26 local function read_translations(pofile)
27 local f, err=io.open(pofile)
28 if not f then
29 error(err)
30 end
32 local msgid, msgstr, st, en
34 for l in f:lines() do
35 if string.find(l, "^msgid") then
36 if msgid then
37 assert(msgstr)
38 translations[msgid]=msgstr
39 msgstr=nil
40 end
41 st, en, msgid=string.find(l, '^msgid%s*"(.*)"%s*$')
42 elseif string.find(l, "^msgstr") then
43 assert(msgid and not msgstr)
44 st, en, msgstr=string.find(l, '^msgstr%s*"(.*)"%s*$')
45 elseif not (string.find(l, "^%s*#") or string.find(l, "^%s*$")) then
46 local st, en, str=string.find(l, '^%s*"(.*)"%s*$')
47 assert(msgid or msgstr)
48 if not msgstr then
49 msgid=msgid..str
50 else
51 msgstr=msgstr..str
52 end
53 end
54 end
56 if msgid then
57 assert(msgstr)
58 translations[msgid]=msgstr
59 end
61 f:close()
62 end
64 -- }}}
67 -- File parsing {{{
69 local function dobindings(fn, bindings)
70 local p={}
71 local dummy = function() end
73 p.META="Mod1+"
74 p.ALTMETA=""
76 p.dopath=dummy
77 p.defmenu=dummy
78 p.defctxmenu=dummy
79 p.menuentry=dummy
80 p.submenu=dummy
81 p.submap_enter=dummy
82 p.submap_leave=dummy
83 p.submap_wait=dummy
85 p.ioncore={
86 set=dummy,
89 function p.bdoc(text)
90 return {action = "doc", text = text}
91 end
93 function p.submap(kcb_, list)
94 if not list then
95 return function(lst)
96 return p.submap(kcb_, lst)
97 end
98 end
99 return {action = "kpress", kcb = kcb_, submap = list}
102 local function putcmd(cmd, guard, t)
103 t.cmd=cmd
104 t.guard=guard
105 return t
108 function p.kpress(keyspec, cmd, guard)
109 return putcmd(cmd, guard, {action = "kpress", kcb = keyspec})
112 function p.kpress_wait(keyspec, cmd, guard)
113 return putcmd(cmd, guard, {action = "kpress_wait", kcb = keyspec})
116 local function mact(act_, kcb_, cmd, guard)
117 local st, en, kcb2_, area_=string.find(kcb_, "([^@]*)@(.*)")
118 return putcmd(cmd, guard, {
119 action = act_,
120 kcb = (kcb2_ or kcb_),
121 area = area_,
125 function p.mclick(buttonspec, cmd, guard)
126 return mact("mclick", buttonspec, cmd, guard)
129 function p.mdblclick(buttonspec, cmd, guard)
130 return mact("mdblclick", buttonspec, cmd, guard)
133 function p.mpress(buttonspec, cmd, guard)
134 return mact("mpress", buttonspec, cmd, guard)
137 function p.mdrag(buttonspec, cmd, guard)
138 return mact("mdrag", buttonspec, cmd, guard)
141 function ins(t, v)
142 if not t.seen then
143 t.seen={}
146 if (not v.kcb) or v.submap then
147 -- Submap rebinds are not presently handled
148 table.insert(t, v)
149 else
150 local id=v.action..":"..v.kcb..":"..(v.area or "")
151 local i=t.seen[id]
152 if i then
153 t[i].invalid=true
155 if v.cmd then
156 table.insert(t, v)
157 t.seen[id]=#t
158 else
159 -- Unbind only
160 t.seen[id]=nil
165 function p.defbindings(context, bnd)
166 if not bindings[context] then
167 bindings[context]={}
168 else
169 -- Reset documentation
170 table.insert(bindings[context], { action = "doc", text = nil })
173 for _, v in ipairs(bnd) do
174 ins(bindings[context], v)
178 local env=setmetatable({}, {
179 __index=p,
180 __newindex=function(x)
181 error("Setting global "..tostring(x))
182 end,
184 setfenv(fn, env)
185 fn()
186 return bindings
189 local function parsefile(f, bindings)
190 local fn, err=loadfile(f)
191 if not fn then
192 error(err)
195 return dobindings(fn, bindings)
198 -- }}}
201 -- Binding output {{{
203 local function docgroup_bindings(bindings)
204 local out={}
205 local outi=0
207 local function parsetable(t, prefix)
208 --for _, v in ipairs(t) do
209 -- ipairs doesn't like nil values, that e.g. submap_wait dummy might generate
210 for i=1,#t do
211 local v=t[i]
212 if v and not v.invalid then
213 if v.kcb then
214 v.kcb=string.gsub(v.kcb, "AnyModifier%+", "")
216 if v.action=="doc" then
217 if outi==0 or #out[outi].bindings>0 then
218 outi=outi+1
220 out[outi]={doc=v.text, bindings={}}
221 elseif v.submap then
222 parsetable(v.submap, prefix..v.kcb.." ")
223 else
224 assert(out[outi])
225 v.kcb=prefix..v.kcb
226 table.insert(out[outi].bindings, v)
232 if outi~=0 and #out[outi].bindings==0 then
233 out[outi]=nil
236 parsetable(bindings, "")
238 return out
242 local function combine_bindings(v)
243 local nact={
244 ["mpress"]=TR("press"),
245 ["mclick"]=TR("click"),
246 ["mdrag"]=TR("drag"),
247 ["mdblclick"]=TR("double click"),
249 local first=true
250 local s=""
251 for _, b in ipairs(v.bindings) do
252 if not first then
253 s=s..', '
255 first=false
256 if b.action=="kpress" or b.action=="kpress_wait" then
257 s=s..b.kcb
258 else
259 if not b.area then
260 s=s..TR("%s %s", b.kcb, nact[b.action])
261 else
262 s=s..TR("%s %s at %s", b.kcb, nact[b.action], b.area)
267 return s
268 end
270 local function write_bindings_man(db)
271 local function write_binding_man(v)
272 return '.TP\n.B '..combine_bindings(v)..'\n'..gettext(v.doc or "?")..'\n'
275 local s=""
277 for _, v in ipairs(db) do
278 if #(v.bindings)>0 then
279 s=s..write_binding_man(v)
283 return s
286 -- }}}
288 -- Main {{{
290 local infile
291 local outfile
292 local bindings={}
293 local replaces={}
295 local function doargs(a)
296 local i=1
297 while i<=#a do
298 if a[i]=='-o' then
299 outfile=a[i+1]
300 i=i+2
301 elseif a[i]=='-i' then
302 infile=a[i+1]
303 i=i+2
304 elseif a[i]=='-D' then
305 replaces[a[i+1]]=a[i+2]
306 i=i+3
307 elseif a[i]=='-po' then
308 read_translations(a[i+1])
309 i=i+2
310 else
311 parsefile(a[i], bindings)
312 i=i+1
317 doargs({...})
319 local f, err=io.open(infile)
320 if not f then
321 error(err)
324 local of, oerr=io.open(outfile, 'w+')
325 if not of then
326 error(oerr)
329 for l in f:lines() do
330 l=string.gsub(l, '%s*BINDINGS:([%w%.%-]+)%s*',
331 function(s)
332 if not bindings[s] then
333 --error('No bindings for '..s)
334 return "?"
336 local db=docgroup_bindings(bindings[s])
337 return write_bindings_man(db)
338 end)
340 for pat, rep in pairs(replaces) do
341 l=string.gsub(l, pat, rep)
344 of:write(l..'\n')
347 -- }}}