mlib-fio.lmt /size: 9635 b    last modification: 2023-12-21 09:44
1if not modules then modules = { } end modules ['mlib-fio'] = {
2    version   = 1.001,
3    comment   = "companion to mlib-ctx.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 type = type
10local find = string.find
11local concat = table.concat
12local suffix, addsuffix, is_writable = file.suffix, file.addsuffix, file.is_writable
13local urlhashed = url.hashed
14
15local findfile     = resolvers.findfile
16local mplibnew     = mplib.new
17----- mplibexecute = mplib.execute
18
19local trace_terminal = false  trackers.register("metapost.terminal", function(v) trace_terminal = v end)
20
21local report_metapost = logs.reporter("metapost")
22local report_terminal = logs.reporter("metapost","terminal")
23local report_logger   = logs.reporter("metapost","log")
24local report_error    = logs.reporter("metapost","error")
25
26mplib.realtimelogging = false
27
28local handlelog  do
29
30    local l, nl, dl = { }, 0, false
31
32    handlelog = function(instance,target,str)
33        if target == 1 then
34            -- log
35        elseif target == 2 or target == 3 then
36            -- term
37            if str == "\n" then
38                mplib.realtimelogging = true
39                if nl > 0 then
40                    report_logger(concat(l,"",1,nl))
41                    nl, dl = 0, false
42                elseif not dl then
43                    report_logger("")
44                    dl = true
45                end
46            else
47                nl = nl + 1
48                l[nl] = str
49            end
50        elseif target == 4 then
51            report_error(str)
52        end
53    end
54
55end
56
57local finders = { }
58mplib.finders = finders -- also used in meta-lua.lua
59
60local function validftype(ftype)
61    return ftype == "mp" and "mp" or nil
62end
63
64-- We can have a list!
65
66local findtexfile = resolvers.findtexfile
67local opentexfile = resolvers.opentexfile
68local splitlines  = string.splitlines
69
70local suffixlist = { "mpxl", "mpiv", "mp" } -- no "mf"
71
72local remapped = {
73    -- We don't yet have an interface for adding more here but when needed
74    -- there will be one.
75    ["hatching.mp"] = "mp-remapped-hatching.mp",
76    ["boxes.mp"]    = "mp-remapped-boxes.mp",
77    ["hatching"]    = "mp-remapped-hatching.mp",
78    ["boxes"]       = "mp-remapped-boxes.mp",
79}
80
81local function findmpfile(name,ftype)
82    local usedname = remapped[name] or name
83    local validtyp = validftype(ftype)
84    local fullname = findtexfile(usedname,validtyp)
85    if fullname and fullname ~= "" then
86        return fullname
87    elseif suffix(usedname) == "" then
88        for i=1,#suffixlist do
89            fullname = findfile(addsuffix(usedname,suffixlist[i]),validtyp)
90            if fullname and fullname ~= "" then
91                return fullname
92            end
93        end
94    end
95    return nil
96end
97
98-- variant 1
99
100-- finders.file = function(specification,name,mode,kind)
101--     if mode == "r" then
102--         return findmpfile(name,kind)
103--     elseif is_writable(name) then
104--         return name
105--     else
106--         return nil
107--     end
108-- end
109
110-- variant 2
111
112-- finders.file = function(specification,name,mode,kind)
113--     if not mode or mode == "r" then
114--         return findmpfile(name,kind)
115--     elseif is_writable(name) then
116--         return name
117--     else
118--         return nil
119--     end
120-- end
121
122-- variant 3
123
124finders.file = function(specification,name,mode,kind)
125    if mode == "w" then
126        return is_writable(name) and name or nil
127    else
128        return findmpfile(name,kind) or nil
129    end
130end
131
132local function finder(name,mode,kind) -- fake message for mpost.map and metafun.mpvi
133    local specification = urlhashed(name)
134    local finder = finders[specification.scheme] or finders.file
135    local found = finder(specification,name,mode,validftype(ftype))
136    return found
137end
138
139local function writetoterminal(terminaldata,maxterm,d)
140    local t = type(d)
141    local n = 0
142    if t == "string" then
143        d = splitlines(d)
144        n = #d
145        for i=1,#d do
146            maxterm = maxterm + 1
147            terminaldata[maxterm] = d[i]
148        end
149    elseif t == "table" then
150        for i=1,#d do
151            local l = d[i]
152            if not l then
153                -- just ignore
154            elseif find(l,"[\n\r]") then
155                local s = splitlines(l)
156                local m = #s
157                for i=1,m do
158                    maxterm = maxterm + 1
159                    terminaldata[maxterm] = s[i]
160                end
161                n = n + m
162            else
163                maxterm = maxterm + 1
164                terminaldata[maxterm] = d[i]
165                n = 1
166            end
167        end
168    end
169    if trace_terminal then
170        report_metapost("writing %i lines, in cache %s",n,maxterm)
171    end
172    return maxterm
173end
174
175local function readfromterminal(terminaldata,maxterm,nowterm)
176    if nowterm >= maxterm then
177        terminaldata[nowterm] = false
178        maxterm = 0
179        nowterm = 0
180        if trace_terminal then
181            report_metapost("resetting, maxcache %i",#terminaldata)
182        end
183        return maxterm, nowterm, nil
184    else
185        if nowterm > 0 then
186            terminaldata[nowterm] = false
187        end
188        nowterm = nowterm + 1
189        local s = terminaldata[nowterm]
190        if trace_terminal then
191            report_metapost("reading line %i: %s",nowterm,s)
192        end
193        return maxterm, nowterm, s
194    end
195end
196
197local function fileopener()
198
199    -- these can go into the table itself
200
201    local terminaldata = { }
202    local maxterm      = 0
203    local nowterm      = 0
204
205    local terminal = {
206        name   = "terminal",
207        close  = function()
208         -- terminal = { }
209         -- maxterm  = 0
210         -- nowterm  = 0
211        end,
212        reader = function()
213            local line
214            maxterm, nowterm, line = readfromterminal(terminaldata,maxterm,nowterm)
215            return line
216        end,
217        writer = function(d)
218            maxterm = writetoterminal(terminaldata,maxterm,d)
219        end,
220    }
221
222    return function(name,mode,kind)
223        if name == "terminal" then
224         -- report_metapost("opening terminal")
225            return terminal
226        elseif mode == "w" then
227            -- we need an extra check here for permissions
228            local f = io.open(name,"wb")
229            if f then
230             -- report_metapost("opening file %a for writing",full)
231                return {
232                    name   = full,
233                    writer = function(s) return f:write(s) end, -- io.write(f,s)
234                    close  = function()  f:close() end,
235                }
236            end
237        else
238            local full = findtexfile(name,validftype(ftype))
239            if full then
240             -- report_metapost("opening file %a for reading",full)
241                return opentexfile(full)
242            end
243        end
244    end
245
246end
247
248local overloadmode = "warning"
249
250directives.register("metapost.overloadmode",function(v)
251    if v == "warning" or v == "error" then
252        overloadmode = v
253    else
254        overloadmode= false
255    end
256end)
257
258local propertycodes = {
259    [-3] = "mutable",
260    [ 1] = "primitive",
261    [ 2] = "permanent",
262    [ 3] = "immutable",
263}
264
265mplib.propertycodes = propertycodes
266
267local report = logs.reporter("metafun", "log")
268
269local function overload(property,name)
270    if overloadmode and property >= 0 then
271        -- turn of warning after format is loaded
272        local code = propertycodes[property] or "unknown"
273        report("overloading %s %a",code, name)
274        -- no overload permitted
275        if overloadmode == "error" then
276            luatex.abort()
277        end
278        return false
279    else
280        -- overload permitted
281        return true
282    end
283end
284
285local showcontext = mplib.showcontext
286
287local function handleerror(instance, message, helpinfo, interaction)
288    report()
289    report("error: %s", message)
290    report()
291    showcontext(instance)
292    report()
293    report(helpinfo)
294    report()
295    if interaction == 5 then
296      -- luatex.abort()
297    end
298end
299
300local function handlewarning(instance, message)
301    report()
302    report("warning: %s", message)
303    report()
304end
305
306function mplib.new(specification)
307    local openfile = fileopener()
308    local handlers = specification.handlers
309    local instance
310    instance = mplibnew {
311        -- used
312        bend_tolerance = specification.bendtolerance,
313        move_tolerance = specification.movetolerance,
314        math_mode      = specification.mathmode,
315        run_script     = specification.runscript,
316        run_internal   = specification.runinternal,
317        script_error   = specification.scripterror,
318        make_text      = specification.maketext,
319        -- always
320        extensions     = 1,
321     -- random_seed    = specification.seed,
322        utf8_mode      = true,
323        text_mode      = true,
324        show_mode      = true,
325        -- not used
326     -- noninteractive = true;
327     -- ini_version    = true,
328        -- always
329        find_file      = finder,
330        run_overload   = overload,
331        open_file      = openfile,
332        interaction    = "silent",
333        job_name       = tex.jobname, -- mandate in order to get something back
334        halt_on_error  = true,
335        run_logger     = handlers.log     or function(...) handlelog    (instance,...) end,
336        run_error      = handlers.error   or function(...) handleerror  (instance,...) end,
337        run_warning    = handlers.warning or function(...) handlewarning(instance,...) end,
338    }
339    return instance, openfile("terminal")
340end
341
342mplib.finder  = finder
343-----.execute = executor
344