mtx-server-ctx-help.lua /size: 26 Kb    last modification: 2020-07-01 14:35
1if not modules then modules = { } end modules ['mtx-server-ctx-help'] = {
2    version   = 1.001,
3    comment   = "Basic Definition Browser",
4    author    = "Hans Hagen",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9local gsub, find, lower, match = string.gsub, string.find, string.lower, string.match
10local concat, sort = table.concat, table.sort
11
12dofile(resolvers.findfile("trac-lmx.lua","tex"))
13dofile(resolvers.findfile("util-sci.lua","tex"))
14dofile(resolvers.findfile("char-def.lua","tex"))
15dofile(resolvers.findfile("char-ini.lua","tex"))
16dofile(resolvers.findfile("char-utf.lua","tex"))
17
18local scite             = utilities.scite
19local formatters        = string.formatters
20local sortedkeys        = table.sortedkeys
21local setmetatableindex = table.setmetatableindex
22local lowercase         = characters.lower
23local uppercase         = characters.upper
24local interfaces        = dofile(resolvers.findfile("mult-def.lua","tex"))
25local i_setupstrings    = interfaces.setupstrings
26local i_commands        = interfaces.commands
27local i_variables       = interfaces.variables
28local i_constants       = interfaces.constants
29local i_elements        = interfaces.elements
30local report            = logs.reporter("ctx-help")
31local gettime           = os.gettimeofday or os.clock
32
33local xmlcollected      = xml.collected
34local xmlfirst          = xml.first
35local xmltext           = xml.text
36local xmlload           = xml.load
37
38document                = document or { }
39document.setups         = document.setups or { }
40
41local usedsetupfile     = resolvers.findfile("i-context.xml") or ""
42local usedsetuproot     = usedsetupfile ~= "" and xmlload(usedsetupfile) or false
43local useddefinitions   = { }
44
45if usedsetuproot then
46    report("main file loaded: %s",usedsetupfile)
47    xml.include(usedsetuproot,"cd:interfacefile","filename",true,function(s)
48        local fullname =  resolvers.findfile(s)
49        if fullname and fullname ~= "" then
50            report("inclusion loaded: %s",fullname)
51            return io.loaddata(fullname)
52        end
53    end)
54else
55    report("no main file")
56    return false, false
57end
58
59local defaultinterface  = "en"
60
61-- todo: store mode|interface in field but then we need post
62
63for e in xmlcollected(usedsetuproot,"cd:define") do
64    useddefinitions[e.at.name] = e
65end
66
67for e in xml.collected(usedsetuproot,"cd:interface/cd:interface") do
68    e.at.file = e.__f__ -- nicer
69end
70
71local f_divs_t = {
72    pe = formatters["<div dir='rtl' lang='arabic'>%s</div>"],
73}
74
75local f_spans_t = {
76    pe = formatters["<span dir='rtl' lang='arabic'>%s</span>"]
77}
78
79local f_href_in_list_t = {
80    tex = formatters["<a class='setupmenuurl' href='mtx-server-ctx-help.lua?interface=%s&command=%s&mode=%s'>%s</a>"],
81    lua = formatters["<a class='setupmenuurl' href='mtx-server-ctx-help.lua?interface=%s&command=%s&mode=%s'>%s</a>"],
82}
83
84local f_href_in_list_i = {
85    tex = formatters["<a class='setupmenucmd' href='mtx-server-ctx-help.lua?interface=%s&command=%s&mode=%s' id='#current'>%s</a>"],
86    lua = formatters["<a class='setupmenucmd' href='mtx-server-ctx-help.lua?interface=%s&command=%s&mode=%s' id='#current'>%s</a>"],
87}
88
89local f_href_as_command_t = {
90    tex = formatters["<a class='setuplisturl' href='mtx-server-ctx-help.lua?interface=%s&command=%s&mode=%s'>\\%s</a>"],
91    lua = formatters["<a class='setuplisturl' href='mtx-server-ctx-help.lua?interface=%s&command=%s&mode=%s'>context.%s</a>"],
92}
93
94local f_modes_t = {
95    tex = formatters["<a class='setupmodeurl' href='mtx-server-ctx-help.lua?interface=%s&mode=lua'>lua mode</a>"],
96    lua = formatters["<a class='setupmodeurl' href='mtx-server-ctx-help.lua?interface=%s&mode=tex'>tex mode</a>"],
97}
98
99local f_views_t = {
100    groups = formatters["<a class='setupviewurl' href='mtx-server-ctx-help.lua?interface=%s&view=names'>names</a>"],
101    names  = formatters["<a class='setupviewurl' href='mtx-server-ctx-help.lua?interface=%s&view=groups'>groups</a>"],
102}
103
104local f_interface   = formatters["<a href='mtx-server-ctx-help.lua?interface=%s&command=%s&mode=%s'>%s</a>"]
105local f_source      = formatters["<a href='mtx-server-ctx-help.lua?interface=%s&command=%s&source=%s&mode=%s'>%s</a>"]
106local f_keyword     = formatters[" <tr>\n  <td width='15%%'>%s</td>\n  <td width='85%%' colspan='2'>%s</td>\n </tr>\n"]
107local f_parameter   = formatters[" <tr>\n  <td width='15%%'>%s</td>\n  <td width='15%%'>%s</td>\n  <td width='70%%'>%s</td>\n </tr>\n"]
108local f_url         = formatters[" <tr>\n  <td width='15%%'>%s</td>\n  <td width='85%%' colspan='2'><i>%s</i>: %s</td>\n </tr>\n"]
109local f_parameters  = formatters["\n<table width='100%%'>\n%s</table>\n"]
110local f_instance    = formatters["<tt>%s</tt>"]
111local f_instances   = formatters["\n<div class='setupinstances'><b>predefined instances</b>:&nbsp;%s</div>\n"]
112local f_listing     = formatters["<pre><t>%s</t></pre>"]
113local f_special     = formatters["<i>%s</i>"]
114local f_default     = formatters["<u>%s</u>"]
115local f_group       = formatters["<div class='setupmenugroup'>\n<div class='setupmenucategory'>%s</div>%s</div>"]
116
117-- replace('cd:string',    'value',   i_commands, i_elements)
118-- replace('cd:variable' , 'value',   i_variables)
119-- replace('cd:parameter', 'name',    i_constants)
120-- replace('cd:constant',  'type',    i_variables)
121-- replace('cd:constant',  'default', i_variables)
122-- replace('cd:variable',  'type',    i_variables)
123-- replace('cd:inherit',   'name',    i_commands, i_elements)
124
125local function translate(tag,interface,noformat) -- to be checked
126    local translation = i_setupstrings[tag]
127    local translated  = translation and (translation[interface] or translation[interface]) or tag
128    if noformat then
129        return translated
130    else
131        return f_special(translated)
132    end
133end
134
135local function translatedparameter(e,interface)
136    local attributes = e.at
137    local s = attributes.type or "?"
138    if find(s,"^cd:") then
139        local t = i_setupstrings[s]
140        local f = t and (t[interface] or t.en) or s
141        return f
142    else
143        local t = i_variables[s]
144        local f = t and (t[interface] or t.en) or s
145        return f
146    end
147end
148
149local function translatedkeyword(e,interface)
150    local attributes = e.at
151    local s = attributes.type or "?"
152    if find(s,"^cd:") then
153        local t = i_setupstrings[s]
154        local f = t and (t[interface] or t.en) or s
155        return f
156    else
157        local t = i_variables[s]
158        local f = t and (t[interface] or t.en) or s
159        if attributes.default == "yes" then
160            return f_default(f)
161        else
162            return f
163        end
164    end
165end
166
167local function translatedvariable(s,interface)
168    local t = i_variables[s]
169    return t and (t[interface] or t.en) or s
170end
171
172local function translatedconstant(s,interface) -- cache
173    local t = i_constants[s]
174    return t and (t[interface] or t.en) or s
175end
176
177local function translatedelement(s,interface) -- cache
178    local t = i_elements[s]
179    return t and (t[interface] or t.en) or s
180end
181
182local function translatedstring(s,interface) -- cache
183    local t = i_commands[s]
184    if t then
185        t = t[interface] or t.en
186    end
187    if t then
188        return t
189    end
190    t = i_elements[s]
191    return t and (t[interface] or t.en) or s
192end
193
194local function translatedcommand(s,interface) -- cache
195    local t = i_commands[s]
196    return t and (t[interface] or t.en) or s
197end
198
199local function makeidname(e)
200    local at   = e.at
201    local name = at.name
202    if at.type == 'environment' then
203        name = name .. ":environment"
204    end
205    if at.generated == "yes" then
206        name = name .. ":generated"
207    end
208    if at.variant then
209        name = name .. ":" .. at.variant
210    end
211    return lower(name)
212end
213
214local function makecsname(e,interface,prefix) -- stop ?
215    local cs = ""
216    local at = e.at
217    local ok = false
218    local en = at.type == 'environment'
219    if prefix and en then
220        cs = translatedelement("start",interface)
221    end
222    for f in xmlcollected(e,'cd:sequence/(cd:string|cd:variable)') do -- always at the start
223        local tag = f.tg
224        local val = f.at.value or ""
225        if tag == "string" then
226            cs = cs .. translatedstring(val,interface)
227        elseif tag == "variable" then
228            cs = cs .. f_special(translatedconstant("name",interface))
229        else -- can't happen
230            cs = cs .. val
231        end
232        ok = true
233    end
234    if not ok then
235        if en then
236            cs = cs .. translatedstring(at.name,interface)
237        else
238            cs = cs .. translatedcommand(at.name,interface)
239        end
240    end
241    return cs
242end
243
244local function getnames(root,interface)
245    local found  = { }
246    local names  = { }
247    local groups = { }
248    local extra  = { }
249    for e in xmlcollected(root,'cd:interface/cd:interface') do
250        local category = match(e.at.file or "","^i%-(.*)%.xml$")
251        local list     = { }
252        for e in xmlcollected(e,'cd:command') do
253            local idname = makeidname(e)
254            local csname = makecsname(e,interface,true)
255            if not found[idname] then
256                local t = { idname, csname }
257                names[#names+1] = t
258                list[#list+1]   = t
259                found[idname]   = e
260                extra[csname]   = e
261            else
262                -- variant
263            end
264        end
265        if #list > 0 then
266            sort(list, function(a,b) return lower(a[2]) < lower(b[2]) end)
267            groups[#groups+1] = { category, list }
268        end
269
270    end
271    sort(names,  function(a,b) return lower(a[2]) < lower(b[2]) end)
272    sort(groups, function(a,b) return lower(a[1]) < lower(b[1]) end)
273    return names, groups, found, extra
274end
275
276local loaded = setmetatableindex(function(loaded,interface)
277    local names, groups, found, extra = getnames(usedsetuproot,interface)
278    local current = {
279        interface   = interface,
280        root        = usedsetuproot,
281        definitions = useddefinitions,
282        names       = names,
283        groups      = groups,
284        found       = found,
285        extra       = extra,
286    }
287    loaded[interface] = current
288    return current
289end)
290
291local function collect(current,name,interface,lastmode)
292    local command = current.found[name] or current.extra[name]
293    if command then
294        local definitions = current.definitions
295        local attributes  = command.at or { }
296        local generated   = attributes.generated == "yes"
297        local environment = attributes.type      == "environment"
298        local sequence    = { }
299        local tags        = { }
300        local arguments   = { }
301        local parameters  = { }
302        local instances   = { }
303        local tag         = ""
304        local category    = attributes.category or ""
305        local source      = attributes.file and f_source(lastinterface,lastcommand,attributes.file,lastmode,attributes.file) or ""
306
307        -- first pass: construct the top line
308
309        local start   = environment and (attributes["begin"] or translatedelement("start",interface)) or ""
310        local stop    = environment and (attributes["end"]   or translatedelement("stop" ,interface)) or ""
311        local name    = makecsname(command,interface) -- we can use the stored one
312        local valid   = true
313        local texmode = lastmode == "tex"
314
315        local function process(e)
316            for e in xmlcollected(e,"/*") do
317                if not e.special then
318                    local tag        = e.tg
319                    local attributes = e.at
320                    if tag == "resolve" then
321                        local resolved = definitions[e.at.name or ""]
322                        if resolved then
323                           process(resolved)
324                        end
325                    else
326                    -- we need a 'lua' tag i.e. we only support a subset of string/table
327                        local delimiters = attributes.delimiters or "brackets"
328                        local optional   = attributes.optional == "yes"
329                        local list       = attributes.list     == "yes"
330                        if texmode then
331                            local okay
332                            if tag == "keywords" then
333                             -- todo = optional
334                                okay = i_setupstrings["cd:" .. delimiters .. (list and "-l" or "-s")]
335                            elseif tag == "assignments" then
336                             -- todo = optional
337                                okay = i_setupstrings["cd:assignment" .. delimiters .. (list and "-l" or "-s")]
338                            elseif tag == "delimiter" then
339                                tag = "\\" .. attributes.name
340                            elseif tag == "string" then
341                                tag = translatedstring(attributes.value,interface)
342                            else
343                             -- todo = optional
344                                okay = i_setupstrings["cd:" .. tag .. (list and "-l" or "-s")]
345                                    or i_setupstrings["cd:" .. tag]
346                            end
347                            if okay then
348                                tag = okay.en or tag
349                            end
350                        else
351                            local okay
352                            if tag == "keywords" then
353                             -- todo = optional
354                                okay = i_setupstrings["cd:" .. delimiters .. (list and "-l" or "-s")]
355                            elseif tag == "assignments" then
356                             -- todo = optional
357                                okay = i_setupstrings["cd:assignment" .. delimiters .. (list and "-l" or "-s")]
358                            elseif tag == "delimiter" then
359                                okay = false
360                            elseif tag == "string" then
361                                okay = false
362                            else
363                             -- todo = optional
364                                okay = i_setupstrings["cd:" .. tag .. (list and "-l" or "-s")]
365                                    or i_setupstrings["cd:" .. tag]
366                            end
367                            if okay then
368                                local luatag = okay.lua
369                                if luatag then
370                                   tag = luatag
371                                else
372                                    tag   = "unsupported"
373                                    valid = false
374                                end
375                            else
376                                tag   = "unsupported"
377                                valid = false
378                            end
379                        end
380                        if tag then
381                            sequence[#sequence+1] = tag
382                            tags[#tags+1] = tag
383                        end
384                    end
385                end
386           end
387        end
388
389        if start and start ~= "" then
390            if texmode then
391                sequence[#sequence+1] = formatters["\\%s%s"](start,name)
392            else
393                sequence[#sequence+1] = formatters["context.%s%s("](start,name)
394            end
395        else
396            if texmode then
397                sequence[#sequence+1] = formatters["\\%s"](name)
398            else
399                sequence[#sequence+1] = formatters["context.%s("](name)
400            end
401        end
402
403        for e in xmlcollected(command,"/cd:arguments") do
404            process(e)
405        end
406
407        if texmode then
408            if stop and stop ~= "" then
409                sequence[#sequence+1] = "\\" .. stop .. name
410            end
411        else
412            for i=2,#sequence-1 do
413                sequence[i] = sequence[i] .. ", "
414            end
415
416            if stop and stop ~= "" then
417                sequence[#sequence+1] = formatters[") context.%s%s()"](stop,name)
418            else
419                sequence[#sequence+1] = ")"
420            end
421        end
422
423        if valid then
424
425            sequence = concat(sequence," ")
426
427            -- second pass: construct the descriptions
428
429            local n          = 0
430
431            local function process(e)
432                for e in xmlcollected(e,"/*") do
433                    local tag = e.tg
434
435                    if tag == "resolve" then
436
437                        local resolved = definitions[e.at.name or ""]
438                        if resolved then
439                            process(resolved)
440                        end
441
442                    elseif tag == "keywords" then
443
444                        n = n + 1
445                        local left  = tags[n]
446                        local right = { }
447
448                        local function processkeyword(e)
449                            right[#right+1] = translatedkeyword(e,interface)
450                        end
451
452                        for e in xmlcollected(e,"/*") do
453                            if not e.special then
454                                local tag = e.tg
455                                if tag == "resolve" then
456                                    local resolved = definitions[e.at.name or ""]
457                                    if resolved then
458                                        processkeyword(resolved)
459                                    end
460                                elseif tag == "constant" then
461                                    processkeyword(e)
462                                else
463                                    right[#right+1] = "KEYWORD TODO"
464                                end
465                            end
466                        end
467                        parameters[#parameters+1] = f_keyword(left,concat(right, ", "))
468
469                    elseif tag == "assignments" then
470
471                        n = n + 1
472                        local what = tags[n]
473                        local done = false
474
475                        local function processparameter(e,right)
476                            for e in xmlcollected(e,"/*") do
477                                if not e.special then
478                                    local tag = e.tg
479                                    if tag == "resolve" then
480                                        local resolved = definitions[e.at.name or ""]
481                                        if resolved then
482                                            processparameter(resolved,right)
483                                        end
484                                    elseif tag == "constant" then
485                                        right[#right+1] = translatedparameter(e,interface)
486                                    else
487                                        right[#right+1] = "PARAMETER TODO"
488                                    end
489                                end
490                            end
491                        end
492
493                        for e in xmlcollected(e,"/*") do
494                            if not e.special then
495                                local tag   = e.tg
496                                local left  = translatedconstant(e.at.name,interface)
497                                local right = { }
498                                if tag == "resolve" then
499                                    local resolved = definitions[e.at.name or ""]
500                                    if resolved then
501                                        -- todo
502                                        process(resolved)
503                                    end
504                                elseif tag == "inherit" then
505                                    local name = e.at.name or "?"
506                                    local url  = f_href_as_command_t[lastmode](lastinterface,name,lastmode,name)
507                                    parameters[#parameters+1] = f_url(what,translate("cd:inherits",interface),url)
508                                elseif tag == "parameter" then
509                                    processparameter(e,right)
510                                    parameters[#parameters+1] = f_parameter(what,left,concat(right, ", "))
511                                else
512                                    parameters[#parameters+1] = "PARAMETER TODO"
513                                end
514                                if not done then
515                                    done = true
516                                    what = ""
517                                end
518                            end
519                        end
520
521                        what = ""
522                    else
523
524                        n = n + 1
525                        local left  = tags[n]
526                        local right = i_setupstrings["cd:"..tag]
527
528                        if right then
529                            right = uppercase(right[interface] or right.en or tag)
530                        end
531
532                        parameters[#parameters+1] = f_keyword(left,right)
533
534                    end
535                end
536            end
537
538            for e in xmlcollected(command,"/cd:arguments") do
539                process(e)
540            end
541
542        else
543            if texmode then
544                sequence = formatters["unsupported command '%s%s'"](start or "",name)
545            else
546                sequence = formatters["unsupported function '%s%s'"](start or "",name)
547            end
548            parameters = { }
549        end
550
551
552        for e in xmlcollected(command,"/cd:instances/cd:constant") do
553            instances[#instances+1] = f_instance(translatedconstant(e.at.value or "?",interface))
554        end
555
556        return {
557            category   = category,
558            source     = source,
559            mode       = f_modes_t[lastmode or "tex"](lastinterface),
560            view       = f_views_t[lastview or "groups"](lastinterface),
561            sequence   = sequence,
562            parameters = parameters,
563            instances  = instances,
564        }
565    end
566end
567
568-- -- --
569
570local interfaces = {
571    czech    = 'cz',
572    dutch    = 'nl',
573    english  = 'en',
574    french   = 'fr',
575    german   = 'de',
576    italian  = 'it',
577    persian  = 'pe',
578    romanian = 'ro',
579}
580
581local variables = {
582    ['color-background-main-left']  = '#3F3F3F',
583    ['color-background-main-right'] = '#5F5F5F',
584    ['color-background-one']        = lmx.get('color-background-green'),
585    ['color-background-two']        = lmx.get('color-background-blue'),
586    ['title']                       = 'ConTeXt Help Information',
587}
588
589local what = { "environment", "category", "source", "mode", "view" }
590
591local function generate(configuration,filename,hashed)
592
593    local start     = gettime()
594    local detail    = hashed.queries or { }
595    local variables = setmetatableindex({},variables)
596
597    if detail then
598        local lastinterface = detail.interface or defaultinterface or "en"
599        local lastcommand   = detail.command   or ""
600        local lastview      = detail.view      or "groups"
601        local lastsource    = detail.source    or ""
602        local lastmode      = detail.mode      or "tex"
603
604        local current       = loaded[lastinterface]
605
606        local title         = variables.title .. ": " .. lastinterface
607        variables.title     = title
608
609        lastcommand = gsub(lastcommand,"%s*^\\*(.+)%s*","%1")
610
611        local f_div  = f_divs_t[lastinterface]
612        ----- f_span = f_spans[lastinterface]
613
614        local names  = current.names
615        local groups = current.groups
616        local refs   = { }
617        local ints   = { }
618
619        local function addnames(names)
620            local target = { }
621            for k=1,#names do
622                local namedata = names[k]
623                local command  = namedata[1]
624                local text     = namedata[2]
625                if command == lastcommand then
626                    target[#target+1] = f_href_in_list_i[lastmode](lastinterface,command,lastmode,text)
627                else
628                    target[#target+1] = f_href_in_list_t[lastmode](lastinterface,command,lastmode,text)
629                end
630            end
631            return concat(target,"<br/>\n")
632        end
633
634        if lastview == "groups" then
635            local target = { }
636            for i=1,#groups do
637                local group = groups[i]
638                target[#target+1] = f_group(group[1],addnames(group[2]))
639            end
640            refs = concat(target,"<br/>\n")
641        else
642            refs = addnames(names)
643        end
644
645        if lastmode ~= "lua" then
646            local sorted = sortedkeys(interfaces)
647            for k=1,#sorted do
648                local v = sorted[k]
649                ints[k] = f_interface(interfaces[v],lastcommand,lastmode,v)
650            end
651        end
652
653        local n = refs
654        local i = concat(ints,"<br/><br/>\n")
655
656        if f_div then
657            variables.names      = f_div(n)
658            variables.interfaces = f_div(i)
659        else
660            variables.names      = n
661            variables.interfaces = i
662        end
663
664        -- we only support mkiv
665
666        if lastsource and lastsource ~= "" then
667
668            local name = lastsource
669            local full = resolvers.findfile(name)
670            if full == "" and file.suffix(lastsource) == "tex" then
671                name = file.replacesuffix(lastsource,"mkiv")
672                full = resolvers.findfile(name)
673                if full  == "" then
674                    name = file.replacesuffix(lastsource,"mkvi")
675                    full = resolvers.findfile(name)
676                end
677            end
678            if full == "" then
679                variables.maintitle = lastsource
680                variables.maintext  = f_listing("no source found")
681            else
682                local data = io.loaddata(full)
683                data = scite.html(data,file.suffix(full),true)
684                variables.maintitle = name
685                variables.maintext  = f_listing(data)
686            end
687            lastsource      = ""
688            variables.extra = "mode: " .. f_modes_t.tex(lastinterface) .. " " .. f_modes_t.lua(lastinterface)
689
690        elseif lastcommand and lastcommand ~= "" then
691
692            local data  = collect(current,lastcommand,lastinterface,lastmode)
693            if data then
694                local extra = { }
695                for k=1,#what do
696                    local v = what[k]
697                    if data[v] and data[v] ~= "" then
698                        lmx.set(v, data[v])
699                        extra[#extra+1] = v .. ": " .. data[v]
700                    end
701                end
702                local instances     = data.instances
703                variables.maintitle = data.sequence
704                variables.maintext  = f_parameters(concat(data.parameters)) .. (#instances > 0 and f_instances(concat(instances,",&nbsp;")) or "")
705                variables.extra     = concat(extra,"&nbsp;&nbsp;&nbsp;")
706            else
707                variables.maintitle = "no definition"
708                variables.maintext  = ""
709                variables.extra     = ""
710            end
711        else
712            variables.maintitle = "no definition"
713            variables.maintext  = ""
714            variables.extra     = ""
715        end
716
717    else
718
719        variables.maintitle = "no definition"
720        variables.maintext  = "some error"
721        variables.extra     = ""
722
723    end
724
725    local content = lmx.convert('context-help.lmx',false,variables)
726
727    report("time spent on building page: %0.03f seconds",gettime()-start)
728
729    return { content = content }
730end
731
732return generate, true
733