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
93 else
94 local list = settings_to_array(name)
95 for i=1,#list do
96 cache[list[i]] = false
97 end
98 end
99end
100
101local assign do
102
103
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)
215 local names = getnames(name)
216 local nnames = #names
217 if nnames == 0 then
218 return getcontent("")
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
232
233 return concat(t,separator or os.newline)
234 end
235end
236
237local function loadcontent(name)
238 local content = collectcontent(name,"\n")
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
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
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289local counters = { }
290local nesting = 0
291local autoundent = true
292local continue = false
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
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)
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] = "#",
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]
388 local stoplength = #stoplist
389 local stoplast = stoplist[stoplength]
390 local startlist = split[start]
391 local startlength = #startlist
392 local startlast = startlist[startlength]
393 local list = { }
394 local size = 0
395 local depth = 0
396
397 local scancode = experiment and scantokencode or scancode
398 while true do
399 local char = scancode()
400 if char then
401
402
403
404
405
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
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
443 else
444
445 local t = gettoken()
446 if t then
447
448 if experiment and size > 0 then
449
450 local char = tochar[getcommand(t)]
451 if char then
452 size = size + 1 ; list[size] = char
453 else
454
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
467 end
468 else
469 break
470 end
471 end
472 end
473 local start = 1
474 local stop = size - stoplength - 1
475
476
477
478
479
480 for i=start,stop do
481 local li = list[i]
482 if lpegmatch(blackspace,li) then
483
484 elseif lpegmatch(eol,li) then
485
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
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
527
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
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
551 local starttok = createtoken(start,true)
552 local stoptok = createtoken(stop,true)
553 local data = grabtokens(starttok,stoptok,true,13)
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)
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
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
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
667 registertempfile(tmp)
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)
687
688 local filename = addsuffix(tag,"tmp")
689 local resultname = addsuffix(tag,suffix)
690
691 if new[tag] then
692
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,
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
708 for i=1,#suffixes do
709 local suffix = suffixes[i]
710 newdata.suffixes[suffix] = true
711 local tmp = addsuffix(tag,suffix)
712
713 registertempfile(tmp,nil,true)
714 end
715
716 return resultname
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
729 ctx_viafile(str,f_getbuffer(validstring(buf,"noname")))
730 end
731 end
732end
733
734local function getbuffermkvi(name)
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
748 ctx_getbuffer { name }
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
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
799
800
801
802implement {
803 name = "feedback",
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
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",
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",
867 public = true,
868 protected = true,
869 actions = buffers.splitsamplefile,
870 arguments = "4 strings",
871}
872
873
874
875
876
877
878
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
933
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 |