x-ldx.lua /size: 10 Kb    last modification: 2020-07-01 14:35
1if not modules then modules = { } end modules ['x-ldx'] = {
2    version   = 1.001,
3    comment   = "companion to x-ldx.mkiv",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9-- --[[ldx--
10-- <topic>Introduction</topic>
11-- --ldx]]--
12
13--[[ldx--
14<source>Lua Documentation Module</source>
15
16This file is part of the <logo label='context'/> documentation suite and
17itself serves as an example of using <logo label='lua'/> in combination
18with <logo label='tex'/>.
19
20I will rewrite this using lpeg. On the other hand, we cannot expect proper
21<logo label='tex'/> and for educational purposed the syntax might be wrong.
22
23Todo: use the scite parser.
24--ldx]]--
25
26local banner = "version 1.0.1 - 2007+ - PRAGMA ADE / CONTEXT"
27
28local report = logs.reporter("x-ldx")
29
30--[[
31This script needs a few libraries. Instead of merging the code here
32we can use
33
34<typing>
35mtxrun --internal x-ldx.lua
36</typing>
37
38That way, the libraries included in the runner will be used.
39]]--
40
41-- libraries l-string.lua l-table.lua l-io.lua l-file.lua
42
43-- begin library merge
44-- end library merge
45
46local gsub, find, sub = string.gsub, string.find, string.sub
47local splitstring, emptystring = string.split, string.is_empty
48local concat = table.concat
49
50--[[
51Just a demo comment line. We will handle such multiline comments but
52only when they start and end at the beginning of a line. More rich
53comments are tagged differently.
54]]--
55
56--[[ldx--
57First we define a proper namespace for this module. The <q>l</q> stands for
58<logo label='lua'/>, the <q>d</q> for documentation and the <q>x</q> for
59<logo label='xml'/>.
60--ldx]]--
61
62if not ldx then ldx = { } end
63
64--[[ldx--
65We load the lua file into a table. The entries in this table themselves are
66tables and have keys like <t>code</t> and <t>comment</t>.
67--ldx]]--
68
69function ldx.load(filename)
70    local data = file.readdata(filename)
71    local expr = "%s*%-%-%[%[ldx%-*%s*(.-)%s*%-%-ldx%]%]%-*%s*"
72    local i, j, t = 0, 0, { }
73    while true do
74        local comment, ni
75        ni, j, comment = find(data, expr, j)
76        if not ni then break end
77        t[#t+1] = { code = sub(data, i, ni-1) }
78        t[#t+1] = { comment = comment }
79        i = j + 1
80    end
81    local str = sub(data, i, #data)
82    str = gsub(str, "^%s*(.-)%s*$", "%1")
83    if str ~= "" then
84        t[#t+1] = { code = str }
85    end
86    return t
87end
88
89--[[ldx--
90We will tag keywords so that we can higlight them using a special font
91or color. Users can extend this list when needed.
92--ldx]]--
93
94ldx.keywords = { }
95
96--[[ldx--
97Here come the reserved words:
98--ldx]]--
99
100ldx.keywords.reserved = {
101    ["and"]      = 1,
102    ["break"]    = 1,
103    ["do"]       = 1,
104    ["else"]     = 1,
105    ["elseif"]   = 1,
106    ["end"]      = 1,
107    ["false"]    = 1,
108    ["for"]      = 1,
109    ["function"] = 1,
110    ["if"]       = 1,
111    ["in"]       = 1,
112    ["local"]    = 1,
113    ["nil"]      = 1,
114    ["not"]      = 1,
115    ["or"]       = 1,
116    ["repeat"]   = 1,
117    ["return"]   = 1,
118    ["then"]     = 1,
119    ["true"]     = 1,
120    ["until"]    = 1,
121    ["while"]    = 1
122}
123
124--[[ldx--
125We need to escape a few tokens. We keep the hash local to the
126definition but set it up only once, hence the <key>do</key>
127construction.
128--ldx]]--
129
130do
131    local e = { [">"] = "&gt;", ["<"] = "&lt;", ["&"] = "&amp;" }
132    function ldx.escape(str)
133        return (gsub(str, "([><&])",e))
134    end
135end
136
137--[[ldx--
138Enhancing the code is a bit tricky due to the fact that we have to
139deal with strings and escaped quotes within these strings. Before we
140mess around with the code, we hide the strings, and after that we
141insert them again. Single and double quoted strings are tagged so
142that we can use a different font to highlight them.
143--ldx]]--
144
145ldx.make_index = true
146
147function ldx.enhance(data) -- i need to use lpeg and then we can properly autoindent -)
148    local e = ldx.escape
149    for k=1,#data do
150        local v = data[k]
151        if v.code then
152            local dqs, sqs, com, cmt, cod = { }, { }, { }, { }, e(v.code)
153            cod = gsub(cod, '\\"', "##d##")
154            cod = gsub(cod, "\\'", "##s##")
155            cod = gsub(cod, "%-%-%[%[.-%]%]%-%-", function(s)
156                cmt[#cmt+1] = s
157                return "<l<<<".. #cmt ..">>>l>"
158            end)
159            cod = gsub(cod, "%-%-([^\n]*)", function(s)
160                com[#com+1] = s
161                return "<c<<<".. #com ..">>>c>"
162            end)
163            cod = gsub(cod, "(%b\"\")", function(s)
164                dqs[#dqs+1] = sub(s,2,-2) or ""
165                return "<d<<<".. #dqs ..">>>d>"
166            end)
167            cod = gsub(cod, "(%b\'\')", function(s)
168                sqs[#sqs+1] = sub(s,2,-2) or ""
169                return "<s<<<".. #sqs ..">>>s>"
170            end)
171            cod = gsub(cod, "(%a+)",function(key)
172                local class = ldx.keywords.reserved[key]
173                if class then
174                    return "<key class='" .. class .. "'>" .. key .. "</key>"
175                else
176                    return key
177                end
178            end)
179            cod = gsub(cod, "<s<<<(%d+)>>>s>", function(s)
180                return "<sqs>" .. sqs[tonumber(s)] .. "</sqs>"
181            end)
182            cod = gsub(cod, "<d<<<(%d+)>>>d>", function(s)
183                return "<dqs>" .. dqs[tonumber(s)] .. "</dqs>"
184            end)
185            cod = gsub(cod, "<c<<<(%d+)>>>c>", function(s)
186                return "<com>" .. com[tonumber(s)] .. "</com>"
187            end)
188            cod = gsub(cod, "<l<<<(%d+)>>>l>", function(s)
189                return cmt[tonumber(s)]
190            end)
191            cod = gsub(cod, "##d##", "\\\"")
192            cod = gsub(cod, "##s##", "\\\'")
193            if ldx.make_index then
194                local lines = splitstring(cod,"\n")
195                local f = "(<key class='1'>function</key>)%s+([%w%.]+)%s*%("
196                for k=1,#lines do
197                    local v = lines[k]
198                    -- functies
199                    v = gsub(v,f,function(key, str)
200                        return "<function>" .. str .. "</function>("
201                    end)
202                    -- variables
203                    v = gsub(v,"^([%w][%w%,%s]-)(=[^=])",function(str, rest)
204                        local t = splitstring(str,",%s*")
205                        for k=1,#t do
206                            t[k] = "<variable>" .. t[k] .. "</variable>"
207                        end
208                        return concat(t,", ") .. rest
209                    end)
210                    -- so far
211                    lines[k] = v
212                end
213                v.code = concat(lines,"\n")
214            else
215                v.code = cod
216            end
217        end
218    end
219end
220
221--[[ldx--
222We're now ready to save the file in <logo label='xml'/> format. This boils
223down to wrapping the code and comment as well as the whole document. We tag
224lines in the code as such so that we don't need messy <t>CDATA</t> constructs
225and by calculating the indentation we also avoid space troubles. It also makes
226it possible to change the indentation afterwards.
227--ldx]]--
228
229local newmethod = true
230
231function ldx.as_xml(data) -- ldx: not needed
232    local t, cmode = { }, false
233    t[#t+1] = "<?xml version='1.0' standalone='yes'?>\n"
234    t[#t+1] = "\n<document xmlns:ldx='http://www.pragma-ade.com/schemas/ldx.rng' xmlns='http://www.pragma-ade.com/schemas/ldx.rng'>\n"
235    for k=1,#data do
236        local v = data[k]
237        if v.code and not emptystring(v.code) then
238            if newmethod then
239                t[#t+1] = "\n<luacode><![CDATA[\n"
240                t[#t+1] = v.code
241                t[#t+1] = "]]></luacode>\n"
242            else
243                t[#t+1] = "\n<code>\n"
244                local split = splitstring(v.code,"\n")
245                for k=1,#split do -- make this faster
246                    local v = split[k]
247                    local a, b = find(v,"^(%s+)")
248                    if v then v = gsub(v,"[\n\r ]+$","") end
249                    if a and b then
250                        v = sub(v,b+1,#v)
251                        if cmode then
252                            t[#t+1] = "<line comment='yes' n='" .. b .. "'>" .. v .. "</line>\n"
253                        else
254                            t[#t+1] = "<line n='" .. b .. "'>" .. v .. "</line>\n"
255                        end
256                    elseif emptystring(v) then
257                        if cmode then
258                            t[#t+1] = "<line comment='yes'/>\n"
259                        else
260                            t[#t+1] = "<line/>\n"
261                        end
262                    elseif find(v,"^%-%-%[%[") then
263                        t[#t+1] = "<line comment='yes'>" .. v .. "</line>\n"
264                        cmode= true
265                    elseif find(v,"^%]%]%-%-") then
266                        t[#t+1] = "<line comment='yes'>" .. v .. "</line>\n"
267                        cmode= false
268                    elseif cmode then
269                        t[#t+1] = "<line comment='yes'>" .. v .. "</line>\n"
270                    else
271                        t[#t+1] = "<line>" .. v .. "</line>\n"
272                    end
273                end
274                t[#t+1] = "</code>\n"
275            end
276        elseif v.comment then
277            t[#t+1] = "\n<comment>\n" .. v.comment .. "\n</comment>\n"
278        else
279            -- cannot happen
280        end
281    end
282    t[#t+1] = "\n</document>\n"
283    return concat(t,"")
284end
285
286--[[ldx--
287Saving the result is a trivial effort.
288--ldx]]--
289
290function ldx.save(filename,data)
291    file.savedata(filename,ldx.as_xml(data))
292end
293
294--[[ldx--
295The next function wraps it all in one call:
296--ldx]]--
297
298function ldx.convert(luaname,ldxname)
299    if not lfs.isfile(luaname) then
300        file.addsuffix(luaname,"lua")
301    end
302    if lfs.isfile(luaname) then
303        if not ldxname then
304            ldxname = file.replacesuffix(luaname,"ldx")
305        end
306        report("converting file %a to %a",luaname,ldxname)
307        local data = ldx.load(luaname)
308        if data then
309--             ldx.enhance(data)
310            if ldxname ~= luaname then
311                ldx.save(ldxname,data)
312            end
313        else
314            report("invalid file %a",luaname)
315        end
316    else
317        report("unknown file %a",luaname)
318    end
319end
320
321--[[ldx--
322This module can be used directly:
323
324<typing>
325mtxrun --internal x-ldx somefile.lua
326</typing>
327
328will produce an ldx file that can be processed with <logo label='context'/>
329by running:
330
331<typing>
332context --use=x-ldx --forcexml somefile.ldx
333</typing>
334
335You can do this in one step by saying:
336
337<typing>
338context --ctx=x-ldx somefile.lua
339</typing>
340
341This will trigger <logo label='context'/> into loading the mentioned
342<logo label='ctx'/> file. That file describes the conversion as well
343as the module to be used.
344
345The main conversion call is:
346--ldx]]--
347
348-- todo: assume usage of "mtxrun --script x-ldx", maybe make it mtx-ldx
349
350if environment.files and environment.files[1] then
351    ldx.convert(environment.files[1],environment.files[2])
352else
353    report("no file given")
354end
355