buff-ini.lmt /size: 27 Kb    last modification: 2023-12-21 09:44
1if not modules then modules = { } end modules ['buff-ini'] = {
2    version   = 1.001,
3    comment   = "companion to buff-ini.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 concat = table.concat
10local type, next, load = type, next, load
11local sub, format, find, match = string.sub, string.format, string.find, string.match
12local splitlines, validstring, replacenewlines = string.splitlines, string.valid, string.replacenewlines
13local P, S, C, Ct, Cs = lpeg.P, lpeg.S, lpeg.C, lpeg.Ct, lpeg.Cs
14local patterns, lpegmatch = lpeg.patterns, lpeg.match
15local utfchar  = utf.char
16local nameonly = file.nameonly
17local totable  = string.totable
18local md5hex = md5.hex
19local isfile = lfs.isfile
20local savedata = io.savedata
21
22local trace_run         = false  trackers.register("buffers.run",       function(v) trace_run       = v end)
23local trace_grab        = false  trackers.register("buffers.grab",      function(v) trace_grab      = v end)
24local trace_visualize   = false  trackers.register("buffers.visualize", function(v) trace_visualize = v end)
25
26local report_buffers    = logs.reporter("buffers","usage")
27local report_typeset    = logs.reporter("buffers","typeset")
28local report_grabbing   = logs.reporter("buffers","grabbing")
29
30local context           = context
31local commands          = commands
32
33local implement         = interfaces.implement
34
35local scanners          = tokens.scanners
36local scanstring        = scanners.string
37local scancsname        = scanners.csname
38local scaninteger       = scanners.integer
39local scanboolean       = scanners.boolean
40local scancode          = scanners.code
41local scantokencode     = scanners.tokencode
42
43local getters           = tokens.getters
44local gettoken          = getters.token
45
46local createtoken       = token.create
47local grabtokens        = token.grab
48
49local getcommand        = tokens.accessors.command
50local getnextchar       = tokens.scanners.nextchar
51
52local variables         = interfaces.variables
53local settings_to_array = utilities.parsers.settings_to_array
54local formatters        = string.formatters
55local addsuffix         = file.addsuffix
56local replacesuffix     = file.replacesuffix
57
58local registertempfile  = luatex.registertempfile
59
60local v_yes             = variables.yes
61local v_append          = variables.append
62
63local eol               = patterns.eol
64local space             = patterns.space
65local whitespace        = patterns.whitespace
66local blackspace        = whitespace - eol
67local whatever          = (1-eol)^1 * eol^0
68local emptyline         = space^0 * eol
69
70local catcodenumbers    = catcodes.numbers
71
72local ctxcatcodes       = catcodenumbers.ctxcatcodes
73local txtcatcodes       = catcodenumbers.txtcatcodes
74
75local setdata           = job.datasets.setdata
76local getdata           = job.datasets.getdata
77
78local ctx_viafile          = context.viafile
79local ctx_getbuffer        = context.getbuffer
80local ctx_pushcatcodetable = context.pushcatcodetable
81local ctx_popcatcodetable  = context.popcatcodetable
82local ctx_setcatcodetable  = context.setcatcodetable
83local ctx_printlines       = context.printlines
84
85buffers       = buffers or { }
86local buffers = buffers
87
88local cache = { }
89
90local function erase(name)
91    if not name or name == "" then
92        cache[""] = false -- nil
93    else
94        local list = settings_to_array(name)
95        for i=1,#list do
96            cache[list[i]] = false -- nil
97        end
98    end
99end
100
101local assign  do
102
103    -- There is no gain in an immediate concatenating lpeg
104
105    local action  = whitespace^0 * C(P("+")^1 + P("*")) * whitespace^0
106    local equal   = whitespace^0 * patterns.equal * whitespace^0
107    local name    = C((1-action)^1)
108    local pattern = C((1-equal)^1) * equal * Ct((action + name)^1)
109
110    assign = function(name,str,catcodes)
111        local target, content = lpegmatch(pattern,name)
112        if target and content then
113            for i=1,#content do
114                local c = content[i]
115                if c == "+" then
116                    content[i] = ""
117                elseif c == "++" then
118                    content[i] = " "
119                elseif c == "+++" then
120                    content[i] = "\r\r"
121                elseif c == "*" then
122                    content[i] = str
123                else
124                    local s = cache[c]
125                    content[i] = s and s.data or ""
126                end
127            end
128            name = target
129            str  = concat(content)
130        end
131        cache[name] = {
132            data     = str,
133            catcodes = catcodes,
134            typeset  = false,
135        }
136    end
137
138end
139
140local prepend, append  do
141
142    local function combine(name,str,prepend)
143        local buffer = cache[name]
144        if buffer then
145            buffer.data    = prepend and (str .. buffer.data) or (buffer.data .. str)
146            buffer.typeset = false
147        else
148            cache[name] = {
149                data     = str,
150                typeset  = false,
151            }
152        end
153    end
154
155    prepend = function(name,str) combine(name,str,true) end
156    append  = function(name,str) combine(name,str)      end
157
158end
159
160local function exists(name)
161    return cache[name]
162end
163
164local function getcontent(name)
165    local buffer = name and cache[name]
166    return buffer and buffer.data or ""
167end
168
169local function empty(name)
170    if find(getcontent(name),"%S") then
171        return false
172    else
173        return true
174    end
175end
176
177local function getlines(name)
178    local buffer = name and cache[name]
179    return buffer and splitlines(buffer.data)
180end
181
182local function getnames(name)
183    if type(name) == "string" then
184        return settings_to_array(name)
185    else
186        return name
187    end
188end
189
190local function istypeset(name)
191    local names = getnames(name)
192    if #names == 0 then
193        return false
194    end
195    for i=1,#names do
196        local c = cache[names[i]]
197        if c and not c.typeset then
198            return false
199        end
200    end
201    return true
202end
203
204local function markastypeset(name)
205    local names  = getnames(name)
206    for i=1,#names do
207        local c = cache[names[i]]
208        if c then
209            c.typeset = true
210        end
211    end
212end
213
214local function collectcontent(name,separator) -- no print
215    local names  = getnames(name)
216    local nnames = #names
217    if nnames == 0 then
218        return getcontent("") -- default buffer
219    elseif nnames == 1 then
220        return getcontent(names[1])
221    else
222        local t = { }
223        local n = 0
224        for i=1,nnames do
225            local c = getcontent(names[i])
226            if c ~= "" then
227                n = n + 1
228                t[n] = c
229            end
230        end
231        -- the default separator was \r, then \n and is now os.newline because buffers
232        -- can be loaded in other applications
233        return concat(t,separator or os.newline)
234    end
235end
236
237local function loadcontent(name) -- no print
238    local content = collectcontent(name,"\n") -- tex likes \n hm, elsewhere \r
239    local ok, err = load(content)
240    if ok then
241        return ok()
242    else
243        report_buffers("invalid lua code in buffer %a: %s",name,err or "unknown error")
244    end
245end
246
247buffers.raw            = getcontent
248buffers.erase          = erase
249buffers.assign         = assign
250buffers.prepend        = prepend
251buffers.append         = append
252buffers.exists         = exists
253buffers.empty          = empty
254buffers.getcontent     = getcontent
255buffers.getlines       = getlines
256buffers.collectcontent = collectcontent
257buffers.loadcontent    = loadcontent
258
259-- the context interface
260
261implement {
262    name      = "assignbuffer",
263    actions   = assign,
264    arguments = { "string", "string", "integer" }
265}
266
267implement {
268    name      = "erasebuffer",
269    actions   = erase,
270    arguments = "string"
271}
272
273-- local anything      = patterns.anything
274-- local alwaysmatched = patterns.alwaysmatched
275-- local utf8character = patterns.utf8character
276--
277-- local function countnesting(b,e)
278--     local n
279--     local g = P(b) / function() n = n + 1 end
280--             + P(e) / function() n = n - 1 end
281--          -- + anything
282--             + utf8character
283--     local p = alwaysmatched / function() n = 0 end
284--             * g^0
285--             * alwaysmatched / function() return n end
286--     return p
287-- end
288
289local counters   = { }
290local nesting    = 0
291local autoundent = true
292local continue   = false
293
294-- Beware: the first character of bufferdata has to be discarded as it's there to
295-- prevent gobbling of newlines in the case of nested buffers. The last one is
296-- a newlinechar and is removed too.
297--
298-- An \n is unlikely to show up as \r is the endlinechar but \n is more generic
299-- for us.
300
301-- This fits the way we fetch verbatim: the indentation before the sentinel
302-- determines the stripping.
303
304-- str = [[
305--     test test test test test test test
306--       test test test test test test test
307--     test test test test test test test
308--
309--     test test test test test test test
310--       test test test test test test test
311--     test test test test test test test
312--     ]]
313
314-- local function undent(str)
315--     local margin = match(str,"[\n\r]( +)[\n\r]*$") or ""
316--     local indent = #margin
317--     if indent > 0 then
318--         local lines = splitlines(str)
319--         local ok = true
320--         local pattern = "^" .. margin
321--         for i=1,#lines do
322--             local l = lines[i]
323--             if find(l,pattern) then
324--                 lines[i] = sub(l,indent+1)
325--             else
326--                 ok = false
327--                 break
328--             end
329--         end
330--         if ok then
331--             return concat(lines,"\n")
332--         end
333--     end
334--     return str
335-- end
336
337-- how about tabs
338
339local strippers  = { }
340local nofspaces  = 0
341
342local normalline = space^0 / function(s) local n = #s if n < nofspaces then nofspaces = n end end
343                 * whatever
344
345local getmargin = (emptyline + normalline)^1
346
347local function undent(str) -- new version, needs testing: todo: not always needed, like in xtables
348    nofspaces = #str
349    local margin = lpegmatch(getmargin,str)
350    if nofspaces == #str or nofspaces == 0 then
351        return str
352    end
353    local stripper = strippers[nofspaces]
354    if not stripper then
355        stripper = Cs(((space^-nofspaces)/"" * whatever + emptyline)^1)
356        strippers[nofspaces] = stripper
357    end
358    return lpegmatch(stripper,str) or str
359end
360
361buffers.undent = undent
362
363local split = table.setmetatableindex(function(t,k)
364    local v = totable(k)
365    t[k] = v
366    return v
367end)
368
369local tochar = {
370    [ 0] = "\\",
371    [ 1] = "{",
372    [ 2] = "}",
373    [ 3] = "$",
374    [ 4] = "&",
375    [ 5] = "\n",
376    [ 6] = "#", -- todo
377    [ 7] = "^",
378    [ 8] = "_",
379    [10] = " ",
380    [14] = "%",
381}
382
383local experiment = false
384local experiment = scantokencode and true
385
386local function pickup(start,stop)
387    local stoplist    = split[stop] -- totable(stop)
388    local stoplength  = #stoplist
389    local stoplast    = stoplist[stoplength]
390    local startlist   = split[start] -- totable(start)
391    local startlength = #startlist
392    local startlast   = startlist[startlength]
393    local list        = { }
394    local size        = 0
395    local depth       = 0
396 -- local done        = 32
397    local scancode    = experiment and scantokencode or scancode
398    while true do -- or use depth
399        local char = scancode()
400        if char then
401          -- if char < done then
402          --     -- we skip leading control characters so that we can use them to
403          --     -- obey spaces (a dirty trick)
404          -- else
405          --     done = 0
406                char = utfchar(char)
407                size = size + 1
408                list[size] = char
409                if char == stoplast and size >= stoplength then
410                    local done = true
411                    local last = size
412                    for i=stoplength,1,-1 do
413                        if stoplist[i] ~= list[last] then
414                            done = false
415                            break
416                        end
417                        last = last - 1
418                    end
419                    if done then
420                        if depth > 0 then
421                            depth = depth - 1
422                        else
423                            break
424                        end
425                        char = false -- trick: let's skip the next (start) test
426                    end
427                end
428                if char == startlast and size >= startlength then
429                    local done = true
430                    local last = size
431                    for i=startlength,1,-1 do
432                        if startlist[i] ~= list[last] then
433                            done = false
434                            break
435                        end
436                        last = last - 1
437                    end
438                    if done then
439                        depth = depth + 1
440                    end
441                end
442         -- end
443        else
444         -- local t = scantoken()
445            local t = gettoken()
446            if t then
447                -- we're skipping leading stuff, like obeyedlines and relaxes
448                if experiment and size > 0 then
449                    -- we're probably in a macro
450                    local char = tochar[getcommand(t)]
451                    if char then
452                        size = size + 1 ; list[size] = char
453                    else
454                     -- local csname = getcsname(t)
455                        local csname = scancsname(t)
456                        if csname == stop then
457                            stoplength = 0
458                            break
459                        else
460                            size = size + 1 ; list[size] = "\\"
461                            size = size + 1 ; list[size] = csname
462                            size = size + 1 ; list[size] = " "
463                        end
464                    end
465                else
466                    -- ignore and hope for the best
467                end
468            else
469                break
470            end
471        end
472    end
473    local start = 1
474    local stop  = size - stoplength - 1
475    -- not good enough: only empty lines, but even then we miss the leading
476    -- for verbatim
477    --
478    -- the next is not yet adapted to the new scanner ... we don't need lpeg here
479    --
480    for i=start,stop do
481        local li = list[i]
482        if lpegmatch(blackspace,li) then
483            -- keep going
484        elseif lpegmatch(eol,li) then
485            -- okay
486            start = i + 1
487        else
488            break
489        end
490    end
491    for i=stop,start,-1 do
492        if lpegmatch(whitespace,list[i]) then
493            stop = i - 1
494        else
495            break
496        end
497    end
498    --
499    if start <= stop then
500        return concat(list,"",start,stop)
501    else
502        return ""
503    end
504end
505
506tokens.pickup = pickup
507
508local function showpickup(name,bufferdata,catcodes,undented)
509    undented = undented and ">" or "="
510    if #bufferdata > 50 then
511        report_grabbing("%s : %i =%s |%s..%s|",name,catcodes,undented,sub(bufferdata,1,20),sub(bufferdata,-20,#bufferdata))
512    else
513        report_grabbing("%s : %i =%s |%s|",name,catcodes,undented,bufferdata)
514    end
515end
516
517implement {
518    name    = "pickupbuffer",
519    actions = function()
520        -- let's pickup all here (no arguments)
521        local name     = scanstring()
522        local start    = scanstring()
523        local stop     = scanstring()
524        local finish   = scancsname()
525        local catcodes = scaninteger()
526        local doundent = scaninteger() == 1 -- better than a keyword scan
527        -- could be a scanner:
528        local data     = pickup(start,stop)
529        local undented = doundent or (autoundent and doundent == nil)
530        if undented then
531            data = undent(data)
532        end
533        if trace_grab then
534            showpickup(name,data,catcodes,undented)
535        end
536        assign(name,data,catcodes)
537        context[finish]()
538    end
539}
540
541implement {
542    name    = "grabbuffer",
543    actions = function()
544        -- let's pickup all here (no arguments)
545        local name     = scanstring()
546        local start    = scanstring()
547        local stop     = scanstring()
548        local finish   = scancsname()
549        local catcodes = scaninteger()
550        local doundent = scaninteger() == 1 -- better than a keyword scan
551        local starttok = createtoken(start,true)
552        local stoptok  = createtoken(stop,true)
553        local data     = grabtokens(starttok,stoptok,true,13) -- strip first and last \endoflineasciicode
554        local undented = doundent or (autoundent and doundent == nil)
555        if undented then
556            data = undent(data)
557        end
558        if trace_grab then
559            showpickup(name,data,catcodes,undented)
560        end
561        assign(name,data,catcodes)
562        context[finish]()
563    end
564}
565
566local function savebuffer(list,name,prefix,option,directory) -- name is optional
567    if not list or list == "" then
568        list = name
569    end
570    if not name or name == "" then
571        name = list
572    end
573    local content = collectcontent(list,nil) or ""
574    if content == "" then
575        content = "empty buffer"
576    end
577    if prefix == v_yes then
578        name = addsuffix(tex.jobname .. "-" .. name,"tmp")
579    end
580    if directory ~= "" and dir.makedirs(directory) then
581        name = file.join(directory,name)
582    end
583    savedata(name,replacenewlines(content),"\n",option == v_append)
584end
585
586implement {
587    name      = "savebuffer",
588    actions   = savebuffer,
589    arguments = "5 strings",
590}
591
592-- we can consider adding a size to avoid unlikely clashes
593
594local olddata   = nil
595local newdata   = nil
596local getrunner = sandbox.getrunner
597
598local runner = sandbox.registerrunner {
599    name     = "run buffer",
600    program  = "context",
601    method   = "execute",
602    template = [[--purgeall %?path: --path=%path% ?% %filename%]],
603    reporter = report_typeset,
604    checkers = {
605        filename = "readable",
606        path     = "string",
607    }
608}
609
610local function runbuffer(name,encapsulate,runnername,suffixes)
611    if not runnername or runnername == "" then
612        runnername = "run buffer"
613    end
614    local suffix = "pdf"
615    if type(suffixes) == "table" then
616        suffix = suffixes[1]
617    elseif type(suffixes) == "string" and suffixes ~= "" then
618        suffix   = suffixes
619        suffixes = { suffix }
620    else
621        suffixes = { suffix }
622    end
623    local runner = getrunner(runnername)
624    if not runner then
625        report_typeset("unknown runner %a",runnername)
626        return
627    end
628    if not olddata then
629        olddata = getdata("buffers","runners") or { }
630        local suffixes = olddata.suffixes
631        local hashes   = olddata.hashes
632        if hashes and suffixes then
633            for k, hash in next, hashes do
634                for h, v in next, hash do
635                    for s, v in next, suffixes do
636                        local tmp = addsuffix(h,s)
637                     -- report_typeset("mark for deletion: %s",tmp)
638                        registertempfile(tmp)
639                    end
640                end
641            end
642        end
643    end
644    if not newdata then
645        newdata = {
646            version  = environment.version,
647            suffixes = { },
648            hashes   = { },
649        }
650        setdata {
651            name = "buffers",
652            tag  = "runners",
653            data = newdata,
654        }
655    end
656    local oldhashes = olddata.hashes or { }
657    local newhashes = newdata.hashes or { }
658    local old = oldhashes[suffix]
659    local new = newhashes[suffix]
660    if not old then
661        old = { }
662        oldhashes[suffix] = old
663        for hash, n in next, old do
664            local tag = formatters["%s-t-b-%s"](tex.jobname,hash)
665            local tmp = addsuffix(tag,"tmp")
666         -- report_typeset("mark for deletion: %s",tmp)
667            registertempfile(tmp) -- to be sure
668        end
669    end
670    if not new then
671        new = { }
672        newhashes[suffix] = new
673    end
674    local names   = getnames(name)
675    local content = collectcontent(names,nil) or ""
676    if content == "" then
677        content = "empty buffer"
678    end
679    if encapsulate then
680        if not find(content,"\\starttext") then
681            content = formatters["\\starttext\n%s\n\\stoptext\n"](content)
682        end
683    end
684    --
685    local hash = md5hex(content)
686    local tag  = formatters["%s-t-b-%s"](nameonly(tex.jobname),hash) -- make sure we run on the local path
687    --
688    local filename   = addsuffix(tag,"tmp")
689    local resultname = addsuffix(tag,suffix)
690    --
691    if new[tag] then
692        -- done
693    elseif not old[tag] or olddata.version ~= newdata.version or not isfile(resultname) then
694        if trace_run then
695            report_typeset("changes in %a, processing forced",name)
696        end
697        savedata(filename,content)
698        report_typeset("processing saved buffer %a\n",filename)
699        runner {
700            filename = filename,
701            path     = environment.arguments.path, -- maybe take all set paths
702        }
703    end
704    new[tag] = (new[tag] or 0) + 1
705    report_typeset("no changes in %a, processing skipped",name)
706    registertempfile(filename)
707 -- report_typeset("mark for persistence: %s",filename)
708    for i=1,#suffixes do
709        local suffix = suffixes[i]
710        newdata.suffixes[suffix] = true
711        local tmp = addsuffix(tag,suffix)
712     -- report_typeset("mark for persistance: %s",tmp)
713        registertempfile(tmp,nil,true)
714    end
715    --
716    return resultname -- first result
717end
718
719local f_getbuffer = formatters["buffer.%s"]
720local defaultlist = { "" }
721
722local function getbuffer(name)
723    local list = name and name ~= "" and settings_to_array(name) or defaultlist
724    for i=1,#list do
725        local buf = list[i]
726        local str = getcontent(buf)
727        if str ~= "" then
728         -- characters.showstring(str)
729            ctx_viafile(str,f_getbuffer(validstring(buf,"noname")))
730        end
731    end
732end
733
734local function getbuffermkvi(name) -- rather direct !
735    ctx_viafile(resolvers.macros.preprocessed(getcontent(name)),formatters["buffer.%s.mkiv"](validstring(name,"noname")))
736end
737
738local function getbuffertex(name)
739    local buffer = name and cache[name]
740    if buffer and buffer.data ~= "" then
741        ctx_pushcatcodetable()
742        if buffer.catcodes == txtcatcodes then
743            ctx_setcatcodetable(txtcatcodes)
744        else
745            ctx_setcatcodetable(ctxcatcodes)
746        end
747     -- context(function() ctx_viafile(buffer.data) end)
748        ctx_getbuffer { name } -- viafile flushes too soon
749        ctx_popcatcodetable()
750    end
751end
752
753buffers.get       = getbuffer
754buffers.getmkvi   = getbuffermkvi
755buffers.gettex    = getbuffertex
756buffers.getctxlua = loadcontent
757buffers.run       = runbuffer
758
759implement { name = "getbufferctxlua", actions = loadcontent,   arguments = "argument" }
760implement { name = "getbuffer",       actions = getbuffer,     arguments = "argument" }
761implement { name = "getbuffermkvi",   actions = getbuffermkvi, arguments = "argument" }
762implement { name = "getbuffertex",    actions = getbuffertex,  arguments = "argument" }
763
764interfaces.implement {
765    name      = "getbuffercontent",
766    arguments = "string",
767    actions   = { getcontent, context },
768}
769
770implement {
771    name      = "typesetbuffer",
772    actions   = { runbuffer, context },
773    arguments = { "string", true }
774}
775
776implement {
777    name      = "runbuffer",
778    actions   = { runbuffer, context },
779    arguments = { "string", false, "string" }
780}
781
782implement {
783    name      = "doifelsebuffer",
784    actions   = { exists, commands.doifelse },
785    public    = true,
786 -- protected = false,
787    arguments = "string"
788}
789
790implement {
791    name      = "doifelsebufferempty",
792    actions   = { empty, commands.doifelse },
793    public    = true,
794    protected = true,
795    arguments = "string"
796}
797
798-- This only used for mp buffers and is a kludge. Don't change the
799-- texprint into texsprint as it fails because "p<nl>enddef" becomes
800-- "penddef" then.
801
802implement {
803    name      = "feedback", -- bad name, maybe rename to injectbuffercontent
804    actions   = { collectcontent, ctx_printlines },
805    arguments = "string"
806}
807
808do
809
810    local context             = context
811    local ctxcore             = context.core
812
813    local ctx_startbuffer     = ctxcore.startbuffer
814    local ctx_stopbuffer      = ctxcore.stopbuffer
815
816    local ctx_startcollecting = context.startcollecting
817    local ctx_stopcollecting  = context.stopcollecting
818
819    function ctxcore.startbuffer(...)
820        ctx_startcollecting()
821        ctx_startbuffer(...)
822    end
823
824    function ctxcore.stopbuffer()
825        ctx_stopbuffer()
826        ctx_stopcollecting()
827    end
828
829end
830
831-- moved here:
832
833function buffers.samplefile(name,before,after,inbetween)
834    if not exists(name) then
835        assign(name,io.loaddata(resolvers.findfile(name)))
836    end
837    getbuffer(name)
838end
839
840implement {
841    name      = "samplefile", -- bad name, maybe rename to injectbuffercontent
842    public    = true,
843    protected = true,
844    actions   = buffers.samplefile,
845    arguments = "string"
846}
847
848function buffers.splitsamplefile(name,before,after,inbetween)
849    local data  = io.loaddata(resolvers.findfile(name)) or ""
850    local split = string.split(utilities.strings.striplines(data,"prune and collapse"),lpeg.patterns.whitespace^1)
851    local size  = #split
852    local before = tonumber(before) or 1
853    local after  = tonumber(after) or 1
854    if before + after < size then
855        table.move(split,size-after,size,before+1)
856        if inbetween and inbetween ~= "" then
857            split[before+1] = inbetween
858        end
859        data = concat(split, " ",1,before+after+1)
860    end
861    assign(name,data)
862    getbuffer(name)
863end
864
865implement {
866    name      = "splitsamplefile", -- bad name, maybe rename to injectbuffercontent
867    public    = true,
868    protected = true,
869    actions   = buffers.splitsamplefile,
870    arguments = "4 strings",
871}
872
873-- A somewhat strange place (for now) so the *.log definitions might move someplace
874-- else (if useful at all).
875
876-- Handy for the math test suite that Mikael Sundqvist and I are making where we
877-- need to track box content as well as some low level math tracing features, so
878-- we can pipe to buffers (via a temporary file).
879
880do
881
882    local insert, remove = table.insert, table.remove
883    local setlogfile = texio.setlogfile
884    local openfile   = io.open
885
886    local stack = { }
887    local files = { }
888
889    local function resetlogfile(name)
890        files[name] = false
891    end
892
893    local function pushlogfile(name)
894        local f = openfile(name,files[name] and "ab" or "wb")
895        insert(stack,f)
896        files[name] = true
897        setlogfile(f)
898    end
899
900    local function poplogfile()
901        remove(stack)
902        setlogfile(stack[#stack])
903    end
904
905    logs.pushlogfile  = pushlogfile
906    logs.poplogfile   = poplogfile
907    logs.resetlogfile = resetlogfile
908
909    implement {
910        name      = "resetlogfile",
911        arguments = "argument",
912        public    = true,
913        protected = true,
914        actions   = resetlogfile,
915    }
916
917    implement {
918        name      = "pushlogfile",
919        arguments = "argument",
920        public    = true,
921        protected = true,
922        actions   = pushlogfile,
923    }
924
925    implement {
926        name      = "poplogfile",
927        public    = true,
928        protected = true,
929        actions   = poplogfile,
930    }
931
932    -- In the end we went for a somewhat hidden low level one (see low level math tests
933    -- for usage):
934
935    local serialized = nodes.nuts.serialized
936    local getbox     = nodes.nuts.getbox
937
938    implement {
939        name      = "showboxinbuffer",
940        public    = true,
941        protected = true,
942        arguments = { "argument", "integer", "integer" },
943        actions   = function(buffer, box, detail)
944            local box = getbox(box)
945            assign(buffer or "",box and serialized(box,detail))
946        end,
947    }
948
949end
950