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