mtx-interface.lua /size: 32 Kb    last modification: 2021-10-28 13:50
1if not modules then modules = { } end modules ['mtx-cache'] = {
2    version   = 1.001,
3    comment   = "companion to mtxrun.lua",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9local concat, sort, insert = table.concat, table.sort, table.insert
10local gsub, format, gmatch, find, upper = string.gsub, string.format, string.gmatch, string.find, string.upper
11local utfchar, utfgsub = utf.char, utf.gsub
12local sortedkeys, sortedhash, serialize = table.sortedkeys, table.sortedhash, table.serialize
13
14local helpinfo = [[
15<?xml version="1.0"?>
16<application>
17 <metadata>
18  <entry name="name">mtx-interface</entry>
19  <entry name="detail">ConTeXt Interface Related Goodies</entry>
20  <entry name="version">0.13</entry>
21 </metadata>
22 <flags>
23  <category name="basic">
24   <subcategory>
25    <flag name="interfaces"><short>generate context mkii interface files</short></flag>
26   </subcategory>
27   <subcategory>
28    <flag name="context"><short>equals <ref name="interfaces"/> <ref name="messages"/> <ref name="languages"/></short></flag>
29   </subcategory>
30   <subcategory>
31    <flag name="scite"><short>generate scite interface</short></flag>
32    <flag name="bbedit"><short>generate bbedit interface files</short></flag>
33    <flag name="jedit"><short>generate jedit interface files</short></flag>
34    <flag name="textpad"><short>generate textpad interface files</short></flag>
35    <flag name="vim"><short>generate vim interface files</short></flag>
36    <flag name="text"><short>create text files for commands and environments</short></flag>
37    <flag name="raw"><short>report commands to the console</short></flag>
38    <flag name="check"><short>generate check file</short></flag>
39    <flag name="meaning"><short>report the mening of commands</short></flag>
40    <flag name="tokens"><short>show the internal representation of commands</short></flag>
41   </subcategory>
42   <subcategory>
43    <flag name="toutf"><short>replace named characters by utf</short></flag>
44    <flag name="preprocess"><short>preprocess mkvi files to tex files [force,suffix]</short></flag>
45   </subcategory>
46   <subcategory>
47    <flag name="suffix"><short>use given suffix for output files</short></flag>
48    <flag name="force"><short>force action even when in doubt</short></flag>
49   </subcategory>
50   <subcategory>
51    <flag name="pattern"><short>a pattern for meaning lookups</short></flag>
52   </subcategory>
53  </category>
54 </flags>
55</application>
56]]
57
58local application = logs.application {
59    name     = "mtx-interface",
60    banner   = "ConTeXt Interface Related Goodies 0.13",
61    helpinfo = helpinfo,
62}
63
64local report = application.report
65
66scripts           = scripts           or { }
67scripts.interface = scripts.interface or { }
68
69local flushers          = { }
70local userinterfaces    = { 'en','cs','de','it','nl','ro','fr','pe' }
71local messageinterfaces = { 'en','cs','de','it','nl','ro','fr','pe','no' }
72
73local function collect(filename,class,data)
74    if data then
75        local result, r = { }, 0
76        for name, list in sortedhash(data) do
77            r = r + 1 ; result[r] = format("keywordclass.%s.%s=\\\n",class,name)
78            for i=1,#list do
79                if i%5 == 0 then
80                    r = r + 1 ; result[r] = "\\\n"
81                end
82                r = r + 1 ; result[r] = format("%s ",list[i])
83            end
84            r = r + 1 ; result[r] = "\n\n"
85        end
86        io.savedata(file.addsuffix(filename,"properties"),concat(result))
87        io.savedata(file.addsuffix(filename,"lua"),serialize(data,true))
88    else
89        os.remove(filename)
90    end
91end
92
93function flushers.scite(collected)
94    local data = { }
95--     for interface, whatever in next, collected do
96--         data[interface] = whatever.commands
97--     end
98    local function add(target,origin,field)
99        if origin then
100            local list = origin[field]
101            if list then
102                for i=1,#list do
103                    target[list[i]] = true
104                end
105            end
106        end
107    end
108    --
109    for interface, whatever in next, collected do
110        local combined = { }
111        add(combined,whatever,"commands")
112        add(combined,whatever,"environments")
113        if interface == "common" then
114            add(combined,whatever,"textnames")
115            add(combined,whatever,"mathnames")
116        end
117        data[interface] = sortedkeys(combined)
118    end
119    --
120    collect("scite-context-data-interfaces", "context",  data)
121    collect("scite-context-data-metapost",   "metapost", dofile(resolvers.findfile("mult-mps.lua")))
122    collect("scite-context-data-metafun",    "metafun",  dofile(resolvers.findfile("mult-fun.lua")))
123    collect("scite-context-data-context",    "context",  dofile(resolvers.findfile("mult-low.lua")))
124    collect("scite-context-data-tex",        "tex",      dofile(resolvers.findfile("mult-prm.lua")))
125end
126
127function flushers.jedit(collected)
128    for interface, whatever in next, collected do
129        local commands     = whatever.commands
130        local environments = whatever.environments
131        local result, r = { }, 0
132        r = r + 1 ; result[r] = "<?xml version='1.0'?>"
133        r = r + 1 ; result[r] = "<!DOCTYPE MODE SYSTEM 'xmode.dtd'>\n"
134        r = r + 1 ; result[r] = "<MODE>"
135        r = r + 1 ; result[r] = "\t<RULES>"
136        r = r + 1 ; result[r] = "\t\t<KEYWORDS>"
137        for i=1,#commands do
138            r = r + 1 ; result[r] = format("\t\t\t<KEYWORD2>%s</KEYWORD2>",commands[i])
139        end
140        r = r + 1 ; result[r] = "\t\t</KEYWORDS>"
141        r = r + 1 ; result[r] = "\t</RULES>"
142        r = r + 1 ; result[r] = "</MODE>"
143        io.savedata(format("context-jedit-%s.xml",interface), concat(result),"\n")
144    end
145end
146
147function flushers.bbedit(collected)
148    for interface, whatever in next, collected do
149        local commands     = whatever.commands
150        local environments = whatever.environments
151        local result, r = { }, 0
152        r = r + 1 ; result[r] = "<?xml version='1.0'?>"
153        r = r + 1 ; result[r] = "<key>BBLMKeywordList</key>"
154        r = r + 1 ; result[r] = "<array>"
155        for i=1,#commands do
156            r = r + 1 ; result[r] = format("\t<string>\\%s</string>",commands[i])
157        end
158        r = r + 1 ; result[r] = "</array>"
159        io.savedata(format("context-bbedit-%s.xml",interface), concat(result),"\n")
160    end
161end
162
163-- The Vim export is maintained by Nicola Vitacolonna:
164
165local function vimcollect(filename,class,data)
166    if data then
167        local result, r = { }, 0
168        local endline = " contained\n"
169        if find(class,"^meta") then
170            endline = "\n"
171        end
172        r = r + 1 ; result[r] = "vim9script\n\n"
173        r = r + 1 ; result[r] = "# Vim syntax file\n"
174        r = r + 1 ; result[r] = "# Language: ConTeXt\n"
175        r = r + 1 ; result[r] = format("# Automatically generated by mtx-interface (%s)\n\n", os.date())
176        local n = 5 -- number of keywords per row
177        for name, list in sortedhash(data) do
178            local i = 1
179            while i <= #list do
180                r = r + 1 ; result[r] = format("syn keyword %s%s", class, (gsub(name,"^%l",upper))) -- upper is fragile
181                local j = 0
182--                 while i+j <= #list and j < n do
183--                     if list[i+j] == "transparent" then -- this is a Vim keyword
184--                         r = r + 1 ; result[r] = format(" %s[]",list[i+j])
185--                     else
186--                         r = r + 1 ; result[r] = format(" %s",list[i+j])
187--                     end
188--                     j = j + 1
189--                 end
190                while j < n do
191                    local lij = list[i + j]
192                    if not lij then
193                        break
194                    elseif lij == "transparent" then -- this is a Vim keyword
195                        r = r + 1 ; result[r] = format(" %s[]",lij)
196                    else
197                        r = r + 1 ; result[r] = format(" %s",lij)
198                    end
199                    j = j + 1
200                end
201                r = r + 1 ; result[r] = endline
202                i = i + n
203            end
204        end
205        io.savedata(file.addsuffix(filename,"vim"),concat(result))
206    else
207        os.remove(filename)
208    end
209end
210
211function flushers.vim(collected)
212    local data = { }
213 -- for interface, whatever in next, collected do
214 --     data[interface] = whatever.commands
215 -- end
216    local function add(target,origin,field)
217        if origin then
218            local list = origin[field]
219            if list then
220                for i=1,#list do
221                    target[list[i]] = true
222                end
223            end
224        end
225    end
226    --
227    for interface, whatever in next, collected do
228        local combined = { }
229        add(combined,whatever,"commands")
230        add(combined,whatever,"environments")
231        if interface == "common" then
232            add(combined,whatever,"textnames")
233            add(combined,whatever,"mathnames")
234        end
235        data[interface] = sortedkeys(combined)
236    end
237    --
238    vimcollect("context-data-interfaces", "context",  data)
239 -- vimcollect("context-data-metapost",   "metapost", dofile(resolvers.findfile("mult-mps.lua")))
240    vimcollect("context-data-metafun",    "metafun",  dofile(resolvers.findfile("mult-fun.lua")))
241    vimcollect("context-data-context",    "context",  dofile(resolvers.findfile("mult-low.lua")))
242    vimcollect("context-data-tex",        "tex",      dofile(resolvers.findfile("mult-prm.lua")))
243end
244
245function flushers.raw(collected)
246    for interface, whatever in next, collected do
247        local commands     = whatever.commands
248        local environments = whatever.environments
249        for i=1,#commands do
250            report(commands[i])
251        end
252    end
253end
254
255function flushers.data(collected)
256    return collected -- table.save("cont-en-filenames.lua",collected.filenames)
257end
258
259local textpadcreator = "mtx-interface-textpad.lua"
260
261function flushers.text(collected)
262    for interface, whatever in next, collected do
263        local commands     = whatever.commands
264        local environments = whatever.environments
265        local c, cname = { }, format("context-commands-%s.txt",interface)
266        local e, ename = { }, format("context-environments-%s.txt",interface)
267        report("saving '%s'",cname)
268        for i=1,#commands do
269            c[#c+1] = format("\\%s",commands[i])
270        end
271        io.savedata(cname,concat(c,"\n"))
272        report("saving '%s'",ename)
273        for i=1,#environments do
274            e[#e+1] = format("\\start%s",environments[i])
275            e[#e+1] = format("\\stop%s", environments[i])
276        end
277        io.savedata(format("context-environments-%s.txt",interface),concat(e,"\n"))
278    end
279end
280
281function flushers.textpad(collected)
282    flushers.text(collected)
283    for interface, whatever in next, collected do
284        local commands     = whatever.commands
285        local environments = whatever.environments
286        --
287        -- plugin, this is a rewrite of a file provided by Lukas Prochazka
288        --
289        local function merge(templatedata,destinationdata,categories)
290            report("loading '%s'",templatedata)
291            local data = io.loaddata(templatedata)
292            local done = 0
293            for i=1,#categories do
294                local category = categories[i]
295                local cpattern = ";%s*category:%s*(" .. category .. ")%s*[\n\r]+"
296                local fpattern = ";%s*filename:%s*(" .. "%S+"     .. ")%s*[\n\r]+"
297                data = gsub(data,cpattern..fpattern,function(category,filename)
298                    local found = resolvers.findfile(filename) or ""
299                    local blob = found ~= "" and io.loaddata(found) or ""
300                    if blob == ""  then
301                        report("category: %s, filename: %s, not found",category,filename)
302                    else
303                        done = done + 1
304                        report("category: %s, filename: %s, merged",category,filename)
305                    end
306                    return format("; category: %s\n; filename: %s\n%s\n\n",category,filename,blob)
307                end)
308            end
309            if done > 0 then
310                report("saving '%s' (%s files merged)",destinationdata,done)
311                io.savedata(destinationdata,data)
312            else
313                report("skipping '%s' (no files merged)",destinationdata)
314            end
315        end
316        local templatename = "textpad-context-template.txt"
317        local templatedata = resolvers.findfile(templatename) or ""
318        if templatedata == "" then
319            report("unable to locate template '%s'",templatename)
320        else
321            merge(templatedata, "context.syn",       { "tex commands","context commands" })
322            if environment.argument("textpad") == "latex" then
323                merge(templatedata, "context-latex.syn", { "tex commands","context commands", "latex commands" })
324            end
325        end
326        local r = { }
327        local c = io.loaddata("context-commands-en.txt")     or "" -- sits on the same path
328        local e = io.loaddata("context-environments-en.txt") or "" -- sits on the same path
329        for s in gmatch(c,"\\(.-)%s") do
330            r[#r+1] = format("\n!TEXT=%s\n\\%s\n!",s,s)
331        end
332        for s in gmatch(e,"\\start(.-)%s+\\stop(.-)") do
333            r[#r+1] = format("\n!TEXT=%s (start/stop)\n\\start%s \\^\\stop%s\n!",s,s,s)
334        end
335        sort(r)
336        insert(r,1,"!TCL=597,\n!TITLE=ConTeXt\n!SORT=N\n!CHARSET=DEFAULT")
337        io.savedata("context.tcl",concat(r,"\n"))
338        -- cleanup
339        os.remove("context-commands-en.txt")
340        os.remove("context-environments-en.txt")
341    end
342end
343
344function scripts.interface.editor(editor,split,forcedinterfaces)
345    require("char-def")
346
347    local interfaces = forcedinterfaces or environment.files or userinterfaces
348    if not interfaces.en then
349        -- loaded as script so we have "cont-yes.*" as name
350        interfaces = { "en" }
351    end
352    --
353 -- local filename = "i-context.xml"
354    local filename = "context-en.xml"
355    local xmlfile  = resolvers.findfile(filename) or ""
356    if xmlfile == "" then
357        report("unable to locate %a",filename)
358        return
359    end
360    --
361    local filename = "mult-def.lua"
362    local deffile  = resolvers.findfile(filename) or ""
363    if deffile == "" then
364        report("unable to locate %a",filename)
365        return
366    end
367    local interface = dofile(deffile)
368    if not interface or not next(interface) then
369        report("invalid file %a",filename)
370        return
371    end
372    local variables = interface.variables
373    local constants = interface.constants
374    local commands  = interface.commands
375    local elements  = interface.elements
376    --
377    local collected = { }
378    --
379    report("generating files for %a",editor)
380    report("loading %a",xmlfile)
381    local xmlroot = xml.load(xmlfile)
382 -- xml.include(xmlroot,"cd:interfacefile","filename",true,function(s)
383 --     local fullname = resolvers.findfile(s)
384 --     if fullname and fullname ~= "" then
385 --         report("including %a",fullname)
386 --         return io.loaddata(fullname)
387 --     end
388 -- end)
389 -- local definitions = { }
390 -- for e in xml.collected(xmlroot,"cd:interface/cd:define") do
391 --     definitions[e.at.name] = e.dt
392 -- end
393 -- local function resolve(root)
394 --     for e in xml.collected(root,"*") do
395 --         if e.tg == "resolve" then
396 --             local resolved = definitions[e.at.name or ""]
397 --             if resolved then
398 --                 -- use proper replace helper
399 --                 e.__p__.dt[e.ni] = resolved
400 --                 resolved.__p__ = e.__p__
401 --                 resolve(resolved)
402 --             end
403 --         end
404 --     end
405 -- end
406 -- resolve(xmlroot)
407
408    -- todo: use sequence
409
410    for i=1,#interfaces do
411        local interface      = interfaces[i]
412        local i_commands     = { }
413        local i_environments = { }
414        local start = elements.start[interface] or elements.start.en
415        local stop  = elements.stop [interface] or elements.stop .en
416        for e in xml.collected(xmlroot,"cd:interface/cd:command") do
417            local at   = e.at
418            local name = at["name"] or ""
419            local type = at["type"]
420            if name ~= "" then
421                local c = commands[name]
422                local n = c and (c[interface] or c.en) or name
423                local sequence = xml.all(e,"/cd:sequence/*")
424                if at.generated == "yes" then
425                    for e in xml.collected(e,"/cd:instances/cd:constant") do
426                        local name = e.at.value
427                        if name then
428                            local c = variables[name]
429                            local n = c and (c[interface] or c.en) or name
430                            if sequence then
431                                local done = { }
432                                for i=1,#sequence do
433                                    local e = sequence[i]
434                                    if e.tg == "string" then
435                                        local value = e.at.value
436                                        if value then
437                                            done[i] = value
438                                        else
439                                            done = false
440                                            break
441                                        end
442                                    else
443                                        local tg = e.tg
444                                        if tg == "instance" or tg == "instance:assignment" or tg == "instance:ownnumber" then
445                                            done[i] = name
446                                        else
447                                            done = false
448                                            break
449                                        end
450                                    end
451                                end
452                                if done then
453                                    n = concat(done)
454                                end
455                            end
456                            if type ~= "environment" then
457                                i_commands[n] = true
458                            elseif split then
459                                i_environments[n] = true
460                            else
461                                i_commands[start..n] = true
462                                i_commands[stop ..n] = true
463                            end
464                        end
465                    end
466                    -- skip (for now)
467                elseif type ~= "environment" then
468                    i_commands[n] = at.file or true
469                elseif split then
470                    i_environments[n] = at.file or true
471                else
472                    -- variables ?
473                    i_commands[start..n] = at.file or true
474                    i_commands[stop ..n] = at.file or true
475                end
476            end
477        end
478        if next(i_commands) then
479            collected[interface] = {
480                commands     = i_commands,
481                environments = i_environments,
482            }
483        end
484    end
485    --
486    local commoncommands     = { }
487    local commonenvironments = { }
488    for k, v in next, collected do
489        local c = v.commands
490        local e = v.environments
491        if k == "en" then
492            for k, v in next, c do
493                commoncommands[k] = true
494            end
495            for k, v in next, e do
496                commonenvironments[k] = true
497            end
498        else
499            for k, v in next, c do
500                if not commoncommands[k] then
501                    commoncommands[k] = nil
502                end
503            end
504            for k, v in next, e do
505                if not commonenvironments[k] then
506                    commonenvironments[k] = nil
507                end
508            end
509        end
510    end
511    local filenames = { }
512    if collected.en then
513        for k, v in next, collected.en.commands do
514            if v ~= true then
515                filenames[k] = v
516            end
517        end
518        for k, v in next, collected.en.environments do
519            if v ~= true then
520                filenames[k] = v
521            end
522        end
523    end
524    for k, v in next, collected do
525        local c = v.commands
526        local e = v.environments
527        for k, v in next, commoncommands do
528            c[k] = nil
529        end
530        for k, v in next, commonenvironments do
531            e[k] = nil
532        end
533        v.commands     = sortedkeys(c)
534        v.environments = sortedkeys(e)
535    end
536    --
537    local mathnames = { }
538    local textnames = { }
539    for k, v in next, characters.data do
540        local name = v.contextname
541        if name then
542            textnames[name] = true
543        end
544        local name = v.mathname
545        if name then
546            mathnames[name] = true
547        end
548        local spec = v.mathspec
549        if spec then
550            for i=1,#spec do
551                local s = spec[i]
552                local name = s.name
553                if name then
554                    mathnames[name] = true
555                end
556            end
557        end
558    end
559    --
560    collected.common = {
561        textnames    = sortedkeys(textnames),
562        mathnames    = sortedkeys(mathnames),
563        commands     = sortedkeys(commoncommands),
564        environments = sortedkeys(commonenvironments),
565        filenames    = filenames,
566    }
567    --
568    local flusher = flushers[editor]
569    if flusher then
570        return flusher(collected)
571    end
572end
573
574function scripts.interface.check()
575    local xmlfile = resolvers.findfile("cont-en.xml") or ""
576    if xmlfile ~= "" then
577        local f = io.open("cont-en-check.tex","w")
578        if f then
579            f:write("\\starttext\n")
580            local x = xml.load(xmlfile)
581            for e, d, k in xml.elements(x,"/cd:interface/cd:command") do
582                local dk = d[k]
583                local at = dk.at
584                if at then
585                    local name = xml.filter(dk,"cd:sequence/cd:string/attribute(value)")
586                    if name and name ~= "" then
587                        if at.type == "environment" then
588                            name = "start" .. name
589                        end
590                        f:write(format("\\doifundefined{%s}{\\writestatus{check}{command '%s' is undefined}}\n",name,name))
591                    end
592                end
593            end
594            f:write("\\stoptext\n")
595            f:close()
596        end
597    end
598end
599
600function scripts.interface.mkii()
601    local filename = resolvers.findfile(environment.files[1] or "mult-def.lua") or ""
602    if filename ~= "" then
603        local interface = dofile(filename)
604        if interface and next(interface) then
605            local variables, constants, commands, elements = interface.variables, interface.constants, interface.commands, interface.elements
606            local filename = resolvers.findfile("cont-en.xml") or ""
607            local xmldata = filename ~= "" and (io.loaddata(filename) or "")
608            local function flush(texresult,xmlresult,language,what,tag)
609                local t = interface[what]
610                texresult[#texresult+1] = format("%% definitions for interface %s for language %s\n%%",what,language)
611                xmlresult[#xmlresult+1] = format("\t<!-- definitions for interface %s for language %s -->\n",what,language)
612                xmlresult[#xmlresult+1] = format("\t<cd:%s>",what)
613                local sorted = sortedkeys(t)
614                for i=1,#sorted do
615                    local key = sorted[i]
616                    local v = t[key]
617                    local value = v[language] or v["en"]
618                    if not value then
619                        report("warning, no value for key '%s' for language '%s'",key,language)
620                    else
621                        local value = t[key][language] or t[key].en
622                        texresult[#texresult+1] = format("\\setinterface%s{%s}{%s}",tag,key,value)
623                        xmlresult[#xmlresult+1] = format("\t\t<cd:%s name='%s' value='%s'/>",tag,key,value)
624                    end
625                end
626                xmlresult[#xmlresult+1] = format("\t</cd:%s>\n",what)
627            end
628            local function replace(str, element, attribute, category, othercategory, language)
629                return str:gsub(format("(<%s[^>]-%s=)([\"\'])([^\"\']-)([\"\'])",element,attribute), function(a,b,c)
630                    local cc = category[c]
631                    if not cc and othercategory then
632                        cc = othercategory[c]
633                    end
634                    if cc then
635                        ccl = cc[language]
636                        if ccl then
637                            return a .. b .. ccl .. b
638                        end
639                    end
640                    return a .. b .. c .. b
641                end)
642            end
643            -- we could just replace attributes
644            for language, _ in next, commands.setuplayout do
645                -- keyword files
646                local texresult, xmlresult = { }, { }
647                texresult[#texresult+1] = format("%% this file is auto-generated, don't edit this file\n%%")
648                xmlresult[#xmlresult+1] = format("<?xml version='1.0'?>\n",tag)
649                xmlresult[#xmlresult+1] = format("<cd:interface xmlns:cd='http://www.pragma-ade.com/commands' name='context' language='%s' version='2008.10.21 19:42'>\n",language)
650                flush(texresult,xmlresult,language,"variables","variable")
651                flush(texresult,xmlresult,language,"constants","constant")
652                flush(texresult,xmlresult,language,"elements", "element")
653                flush(texresult,xmlresult,language,"commands", "command")
654                texresult[#texresult+1] = format("%%\n\\endinput")
655                xmlresult[#xmlresult+1] = format("</cd:interface>")
656                local texfilename = format("mult-%s.mkii",language)
657                local xmlfilename = format("keys-%s.xml",language)
658                io.savedata(texfilename,concat(texresult,"\n"))
659                report("saving interface definitions '%s'",texfilename)
660                io.savedata(xmlfilename,concat(xmlresult,"\n"))
661                report("saving interface translations '%s'",xmlfilename)
662                -- mkii files
663                if language ~= "en" and xmldata ~= "" then
664                    local newdata = xmldata:gsub("(<cd:interface.*language=.)en(.)","%1"..language.."%2",1)
665                 -- newdata = replace(newdata, 'cd:command', 'name', interface.commands, interface.elements, language)
666                    newdata = replace(newdata, 'cd:string', 'value', interface.commands, interface.elements, language)
667                    newdata = replace(newdata, 'cd:variable' , 'value', interface.variables, nil, language)
668                    newdata = replace(newdata, 'cd:parameter', 'name', interface.constants, nil, language)
669                    newdata = replace(newdata, 'cd:constant', 'type', interface.variables, nil, language)
670                    newdata = replace(newdata, 'cd:variable', 'type', interface.variables, nil, language)
671                    newdata = replace(newdata, 'cd:inherit', 'name', interface.commands, interface.elements, language)
672                    local xmlfilename = format("cont-%s.xml",language)
673                    io.savedata(xmlfilename,newdata)
674                    report("saving interface specification '%s'",xmlfilename)
675                end
676                -- mkiv is generated otherwise
677            end
678        end
679    end
680end
681
682function scripts.interface.preprocess()
683    require("luat-mac")
684
685    local newsuffix = environment.argument("suffix") or "log"
686    local force = environment.argument("force")
687    for i=1,#environment.files do
688        local oldname = environment.files[i]
689        local newname = file.replacesuffix(oldname,newsuffix)
690        if oldname == newname then
691            report("skipping '%s' because old and new name are the same",oldname)
692        elseif io.exists(newname) and not force then
693            report("skipping '%s' because new file exists, use --force",oldname)
694        else
695            report("processing '%s' into '%s'",oldname,newname)
696            io.savedata(newname,resolvers.macros.preprocessed(io.loaddata(oldname)))
697        end
698    end
699end
700
701function scripts.interface.bidi()
702    require("char-def")
703
704    local directiondata  = { }
705    local mirrordata     = { }
706    local textclassdata  = { }
707
708    local data = {
709        comment    = "generated by: mtxrun -- script interface.lua --bidi",
710        direction  = directions,
711        mirror     = mirrors,
712        textclass  = textclasses,
713    }
714
715    for k, d in next, characters.data do
716        local direction = d.direction
717        local mirror    = d.mirror
718        local textclass = d.textclass
719        if direction and direction ~= "l" then
720            directiondata[k] = direction
721        end
722        if mirror then
723            mirrordata[k] = mirror
724        end
725        if textclass then
726            textclassdata[k] = textclass
727        end
728    end
729
730    local filename = "scite-context-data-bidi.lua"
731
732    report("saving %a",filename)
733    table.save(filename,data)
734end
735
736function scripts.interface.toutf()
737    local filename = environment.files[1]
738    if filename then
739        require("char-def")
740        local contextnames = { }
741        for unicode, data in next, characters.data do
742            local contextname = data.contextname
743            if contextname then
744                contextnames[contextname] = utf.char(unicode)
745            end
746            contextnames.uumlaut = contextnames.udiaeresis
747            contextnames.Uumlaut = contextnames.Udiaeresis
748            contextnames.oumlaut = contextnames.odiaeresis
749            contextnames.Oumlaut = contextnames.Odiaeresis
750            contextnames.aumlaut = contextnames.adiaeresis
751            contextnames.Aumlaut = contextnames.Adiaeresis
752        end
753        report("loading %a",filename)
754        local str = io.loaddata(filename) or ""
755        local done = { }
756        str = gsub(str,"(\\)([a-zA-Z][a-zA-Z][a-zA-Z]+)(%s*)", function(b,s,a)
757            local cn = contextnames[s]
758            if cn then
759                done[s] = (done[s] or 0) + 1
760                return cn
761            else
762                done[s] = (done[s] or 0) - 1
763                return b .. s .. a
764            end
765        end)
766        for k, v in table.sortedpairs(done) do
767            if v > 0 then
768                report("+ %5i : %s => %s",v,k,contextnames[k])
769            else
770                report("- %5i : %s",-v,k,contextnames[k])
771            end
772        end
773        filename = filename .. ".toutf"
774        report("saving %a",filename)
775        io.savedata(filename,str)
776    end
777end
778
779function scripts.interface.meaning()
780    local runner  = "mtxrun --silent --script context --extra=meaning --once --noconsole --nostatistics"
781    local pattern = environment.arguments.pattern
782    local files   = environment.files
783    if type(pattern) == "string" then
784        runner = runner .. ' --pattern="' .. pattern .. '"'
785    elseif files and #files > 0 then
786        for i=1,#files do
787            runner = runner .. ' "' .. files[i] .. '"'
788        end
789    else
790        return
791    end
792    local r = os.resultof(runner)
793    if type(r) == "string" then
794        r = gsub(r,"^.-(meaning%s+>)","\n%1")
795        print(r)
796    end
797end
798
799function scripts.interface.tokens()
800    local runner  = "mtxrun --silent --script context --extra=meaning --tokens --once --noconsole --nostatistics"
801    local pattern = environment.arguments.pattern
802    local files   = environment.files
803    if type(pattern) == "string" then
804        runner = runner .. ' --pattern="' .. pattern .. '"'
805    elseif files and #files > 0 then
806        for i=1,#files do
807            runner = runner .. ' "' .. files[i] .. '"'
808        end
809    else
810        return
811    end
812    local r = os.resultof(runner)
813    if type(r) == "string" then
814        r = gsub(r,"^.-(tokens%s+>)","\n%1")
815        print(r)
816    end
817end
818
819local ea = environment.argument
820
821if ea("mkii") then
822    scripts.interface.mkii()
823elseif ea("preprocess") then
824    scripts.interface.preprocess()
825elseif ea("meaning") then
826    scripts.interface.meaning()
827elseif ea("tokens") then
828    scripts.interface.tokens()
829elseif ea("toutf") then
830    scripts.interface.toutf()
831elseif ea("bidi") then
832    scripts.interface.bidi()
833elseif ea("check") then
834    scripts.interface.check()
835elseif ea("scite") or ea("bbedit") or ea("jedit") or ea("textpad") or ea("vim") or ea("text") or ea("raw") then
836    if ea("scite") then
837        scripts.interface.editor("scite")
838    end
839    if ea("bbedit") then
840        scripts.interface.editor("bbedit")
841    end
842    if ea("jedit") then
843        scripts.interface.editor("jedit")
844    end
845    if ea("textpad") then
846        scripts.interface.editor("textpad",true, { "en" })
847    end
848    if ea("vim") then
849        scripts.interface.editor("vim",true, { "en" })
850    end
851    if ea("text") then
852        scripts.interface.editor("text")
853    end
854    if ea("raw") then
855        scripts.interface.editor("raw")
856    end
857elseif ea("exporthelp") then
858    application.export(ea("exporthelp"),environment.files[1])
859else
860    application.help()
861end
862