util-sci.lua /size: 10 Kb    last modification: 2021-10-28 13:50
1if not modules then modules = { } end modules ['util-sci'] = {
2    version   = 1.001,
3    comment   = "companion to m-scite.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
9local gsub, sub, find = string.gsub, string.sub, string.find
10local concat = table.concat
11local formatters = string.formatters
12local lpegmatch = lpeg.match
13local setmetatableindex = table.setmetatableindex
14
15local scite     = scite or { }
16utilities.scite = scite
17
18local report = logs.reporter("scite")
19
20do
21--     local lexerroot = "c:/data/system/scite/wscite/context/lexers"
22--     if not lexerroot then
23        lexerroot = file.dirname(resolvers.findfile("scite-context-lexer.lua"))
24--     end
25    if lfs.isdir(lexerroot) then
26    -- pushluapath
27        package.extraluapath(lexerroot)
28        package.extraluapath(lexerroot.."/themes")
29        package.extraluapath(lexerroot.."/data")
30        report("using lexer root %a",lexerroot)
31    else
32        report("no valid lexer root")
33    end
34end
35
36local knownlexers  = {
37    tex  = "tex",
38    mkiv = "tex", mkvi = "tex",
39    mkxl = "tex", mklx = "tex",
40    mkxi = "tex", mkix = "tex",
41    mkii = "tex",
42    bib  = "bibtex",
43    cld  = "tex",
44    lua  = "lua", lmt = "lua",
45    lfg  = "lua", lus = "lua", luv = "lua",
46    mp   = "mps",
47    mpiv = "mps",
48    mpxl = "mps",
49    mpii = "mps",
50    w    = "web", ww  = "web",
51    c    = "cpp", h   = "cpp",
52    cpp  = "cpp", hpp = "cpp",
53    cxx  = "cpp", hxx = "cpp",
54    xml  = "xml", xsl = "xml", xsd = "xml", dtd = "xml",
55    lmx  = "xml", ctx = "xml", rlx = "xml",
56    css  = "xml",
57    rme  = "txt",
58    txt  = "txt",
59 -- todo: pat/hyp ori
60}
61
62lexers = nil -- main lexer, global (for the moment needed for themes)
63
64local function loadscitelexer()
65    if not lexers then
66        lexers = require("scite-context-lexer")
67        lexers.styles = require("scite-context-theme") -- uses lexer
68        if lexers then
69            (lexers.disablewordcheck or lexers.context.disablewordcheck)()
70        end
71    end
72    return lexer
73end
74
75local loadedlexers = setmetatableindex(function(t,k)
76    local l = knownlexers[k] or k
77    loadscitelexer()
78    local v = lexers.load(formatters["scite-context-lexer-%s"](l))
79    t[l] = v
80    t[k] = v
81    return v
82end)
83
84scite.loadedlexers   = loadedlexers
85scite.knownlexers    = knownlexers
86scite.loadscitelexer = loadscitelexer
87
88local f_fore_bold  = formatters['.%s { display:inline; font-weight:bold; color:#%02X%02X%02X; }']
89local f_fore_none  = formatters['.%s { display:inline; font-weight:normal; color:#%02X%02X%02X; }']
90local f_none_bold  = formatters['.%s { display:inline; font-weight:bold; }']
91local f_none_none  = formatters['.%s { display:inline; font-weight:normal; }']
92local f_div_class  = formatters['<div class="%s">%s</div>']
93local f_linenumber = formatters['<div class="linenumber">%s</div>\n']
94local f_lineerror  = formatters['<div class="linenumber lineerror">%s</div>\n']
95local f_div_number = formatters['.linenumber { display:inline-block; font-weight:normal; width:%sem; margin-right:2em; padding-right:.25em; text-align:right; color:#000000; }'] -- background-color:#C7C7C7;
96local s_div_error  =            '.lineerror { font-weight:bold; color:#FFFFFF; background-color:#000000; }'
97
98local replacer_regular = lpeg.replacer {
99    ["<"]  = "&lt;",
100    [">"]  = "&gt;",
101    ["&"]  = "&amp;",
102}
103
104local linenumber  = 0
105local lineerror   = 0
106local linenumbers = { }
107
108local replacer_numbered = lpeg.replacer {
109    ["<"]  = "&lt;",
110    [">"]  = "&gt;",
111    ["&"]  = "&amp;",
112    [lpeg.patterns.newline] = function()
113        linenumber = linenumber + 1
114        linenumbers[linenumber] = (linenumber == lineerror and f_lineerror or f_linenumber)(linenumber)
115        return "\n"
116    end,
117}
118
119local css = nil
120
121local function exportcsslexing()
122    if not css then
123        loadscitelexer()
124        local function black(f)
125            return (#f == 0 and f[1] == 0) or ((f[1] == f[2]) and (f[2] == f[3]) and (f[3] == 0))
126        end
127        local result, r = { }, 0
128        for k, v in table.sortedhash(lexers.context.styles) do
129            local bold = v.bold
130            local fore = v.fore
131            r = r + 1
132            if fore and not black(fore) then
133                local cr, cg, cb = fore[1], fore[2], fore[3]
134                result[r] = (bold and f_fore_bold or f_fore_none)(k,cr,cg or cr,cb or cr)
135            else
136                result[r] = (bold and f_none_bold or f_none_none)(k)
137            end
138        end
139        css = concat(result,"\n")
140    end
141    return css
142end
143
144local function exportwhites()
145    return setmetatableindex(function(t,k)
146        local v = find(k,"white",1,true) and true or false
147        t[k] = v
148        return v
149    end)
150end
151
152local function exportstyled(lexer,text,numbered)
153    local result = lexers.lex(lexer,text,0)
154    local start  = 1
155    local whites = exportwhites()
156    local buffer = { }
157    local b      = 0
158    linenumber   = 0
159    linenumbers  = { }
160    lineerror    = tonumber(numbered) or 0
161    local replacer = numbered and replacer_numbered or replacer_regular
162    local n = #result
163    for i=1,n,2 do
164        local ii = i + 1
165        local style = result[i]
166        local position = result[ii]
167        local txt = sub(text,start,position-1)
168        txt = lpegmatch(replacer,txt)
169        b = b + 1
170        if whites[style] then
171            buffer[b] = txt
172        else
173            buffer[b] = f_div_class(style,txt)
174        end
175        start = position
176    end
177    return concat(buffer), concat(linenumbers)
178end
179
180local function exportcsslinenumber()
181    return f_div_number(#tostring(linenumber)/2+1) .. "\n" .. s_div_error
182end
183
184local htmlfile = utilities.templates.replacer([[
185<?xml version="1.0"?>
186<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
187    <html xmlns="http://www.w3.org/1999/xhtml">
188    <title>%title%</title>
189    <meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
190    <style type="text/css"><!--
191%lexingstyles%
192%numberstyles%
193    --></style>
194    <body>
195        <table style="padding:0; margin:0; background-color:#FFFFFF;">
196            <tr>
197                <td><pre>%linenumbers%</pre></td>
198                <td><pre>%lexedcontent%</pre></td>
199            </tr>
200        </table>
201    </body>
202</html>
203]])
204
205local htmlblob = utilities.templates.replacer([[
206<style type="text/css"><!--
207%lexingstyles%
208%numberstyles%
209--></style>
210<table style="padding:0; margin:0; background-color:#FFFFFF;">
211    <tr>
212        <td><pre>%linenumbers%</pre></td>
213        <td><pre>%lexedcontent%</pre></td>
214    </tr>
215</table>
216]])
217
218function scite.tohtml(data,lexname,numbered,title)
219    local source, lines = exportstyled(loadedlexers[lexname],data or "",numbered)
220    if source then
221        return (title == false and htmlblob or htmlfile) {
222            lexedcontent = source, -- before numberstyles
223            lexingstyles = exportcsslexing(),
224            numberstyles = exportcsslinenumber(),
225            title        = title or "context source file",
226            linenumbers  = lines,
227        }
228    end
229end
230
231local function maketargetname(name)
232    if name then
233        return file.removesuffix(name) .. "-" .. file.suffix(name) .. ".html"
234    else
235        return "util-sci.html"
236    end
237end
238
239function scite.filetohtml(filename,lexname,targetname,numbered,title)
240    io.savedata(targetname or "util-sci.html",scite.tohtml(io.loaddata(filename),lexname or file.suffix(filename),numbered,title or filename))
241end
242
243function scite.css()
244    return exportcsslexing() .. "\n" .. exportcsslinenumber()
245end
246
247function scite.html(data,lexname,numbered)
248    return exportstyled(loadedlexers[lexname],data or "",numbered)
249end
250
251local f_tree_entry = formatters['<a href="%s" class="dir-entry">%s</a>']
252
253local htmlfile = utilities.templates.replacer([[
254<?xml version="1.0"?>
255<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
256    <html xmlns="http://www.w3.org/1999/xhtml">
257    <title>%title%</title>
258    <meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
259    <style type="text/css"><!--
260%styles%
261    --></style>
262    <body>
263        <pre>
264%dirlist%
265        </pre>
266    </body>
267</html>
268]])
269
270function scite.converttree(sourceroot,targetroot,numbered)
271    if lfs.isdir(sourceroot) then
272        statistics.starttiming()
273        local skipped = { }
274        local noffiles = 0
275        dir.makedirs(targetroot)
276        local function scan(sourceroot,targetroot,subpath)
277            local tree = { }
278            for name in lfs.dir(sourceroot) do
279                if name ~= "." and name ~= ".." then
280                    local sourcename = file.join(sourceroot,name)
281                    local targetname = file.join(targetroot,name)
282                    local mode = lfs.attributes(sourcename,'mode')
283                    local path = subpath and file.join(subpath,name) or name
284                    if mode == 'file' then
285                        local filetype   = file.suffix(sourcename)
286                        local basename   = file.basename(name)
287                        local targetname = maketargetname(targetname)
288                        local fullname   = file.join(path,name)
289                        if knownlexers[filetype] then
290                            report("converting file %a to %a",sourcename,targetname)
291                            scite.filetohtml(sourcename,nil,targetname,numbered,fullname)
292                            noffiles = noffiles + 1
293                            tree[#tree+1] = f_tree_entry(file.basename(targetname),basename)
294                        else
295                            skipped[filetype] = true
296                            report("no lexer for %a",sourcename)
297                        end
298                    else
299                        dir.makedirs(targetname)
300                        scan(sourcename,targetname,path)
301                        tree[#tree+1] = f_tree_entry(file.join(name,"files.html"),name)
302                    end
303                end
304            end
305            report("saving tree in %a",targetroot)
306            local htmldata = htmlfile {
307                dirlist = concat(tree,"\n"),
308                styles  = "",
309                title   = path or "context dir listing",
310            }
311            io.savedata(file.join(targetroot,"files.html"),htmldata)
312        end
313        scan(sourceroot,targetroot)
314        if next(skipped) then
315            report("skipped filetypes: %a",table.concat(table.sortedkeys(skipped)," "))
316        end
317        statistics.stoptiming()
318        report("conversion time for %s files: %s",noffiles,statistics.elapsedtime())
319    end
320end
321
322-- scite.filetohtml("strc-sec.mkiv",nil,"e:/tmp/util-sci.html",true)
323-- scite.filetohtml("syst-aux.mkiv",nil,"e:/tmp/util-sci.html",true)
324
325-- scite.converttree("t:/texmf/tex/context","e:/tmp/html/context",true)
326
327return scite
328