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, Cp = lpeg.P, lpeg.S, lpeg.C, lpeg.Ct, lpeg.Cs, lpeg.Cp
14local lpegpatterns, 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 settings_to_array = utilities.parsers.settings_to_array
53local formatters = string.formatters
54local addsuffix = file.addsuffix
55local replacesuffix = file.replacesuffix
56
57local registertempfile = luatex.registertempfile
58
59local v_yes <const> = interfaces.variables.yes
60local v_append <const> = interfaces.variables.append
61
62local eol = lpegpatterns.eol
63local space = lpegpatterns.space
64local whitespace = lpegpatterns.whitespace
65local blackspace = whitespace - eol
66local whatever = (1-eol)^1 * eol^0
67local emptyline = space^0 * eol
68
69local ctxcatcodes <const> = catcodes.numbers.ctxcatcodes
70local txtcatcodes <const> = catcodes.numbers.txtcatcodes
71
72local setdata = job.datasets.setdata
73local getdata = job.datasets.getdata
74
75local ctx_viafile = context.viafile
76local ctx_getbuffer = context.getbuffer
77local ctx_pushcatcodetable = context.pushcatcodetable
78local ctx_popcatcodetable = context.popcatcodetable
79local ctx_setcatcodetable = context.setcatcodetable
80local ctx_printlines = context.printlines
81
82buffers = buffers or { }
83local buffers = buffers
84
85local cache = { }
86
87local function erase(name)
88 if not name or name == "" then
89 cache[""] = false
90 else
91 local list = settings_to_array(name)
92 for i=1,#list do
93 cache[list[i]] = false
94 end
95 end
96end
97
98local assign do
99
100
101
102 local action = whitespace^0 * C(P("+")^1 + P("*")) * whitespace^0
103 local equal = whitespace^0 * lpegpatterns.equal * whitespace^0
104 local name = C((1-action)^1)
105 local pattern = C((1-equal)^1) * equal * Ct((action + name)^1)
106
107 assign = function(name,str,catcodes)
108 local target, content = lpegmatch(pattern,name)
109 if target and content then
110 for i=1,#content do
111 local c = content[i]
112 if c == "+" then
113 content[i] = ""
114 elseif c == "++" then
115 content[i] = " "
116 elseif c == "+++" then
117 content[i] = "\r\r"
118 elseif c == "*" then
119 content[i] = str
120 else
121 local s = cache[c]
122 content[i] = s and s.data or ""
123 end
124 end
125 name = target
126 str = concat(content)
127 end
128 cache[name] = {
129 data = str,
130 catcodes = catcodes,
131 typeset = false,
132 }
133 end
134
135end
136
137local prepend, append do
138
139 local function combine(name,str,prepend)
140 local buffer = cache[name]
141 if buffer then
142 buffer.data = prepend and (str .. buffer.data) or (buffer.data .. str)
143 buffer.typeset = false
144 else
145 cache[name] = {
146 data = str,
147 typeset = false,
148 }
149 end
150 end
151
152 prepend = function(name,str) combine(name,str,true) end
153 append = function(name,str) combine(name,str) end
154
155end
156
157local function exists(name)
158 return cache[name]
159end
160
161local function getcontent(name)
162 local buffer = name and cache[name]
163 return buffer and buffer.data or ""
164end
165
166local function empty(name)
167 if find(getcontent(name),"%S") then
168 return false
169 else
170 return true
171 end
172end
173
174local function getlines(name)
175 local buffer = name and cache[name]
176 return buffer and splitlines(buffer.data)
177end
178
179local function getnames(name)
180 if type(name) == "string" then
181 return settings_to_array(name)
182 else
183 return name
184 end
185end
186
187local function istypeset(name)
188 local names = getnames(name)
189 if #names == 0 then
190 return false
191 end
192 for i=1,#names do
193 local c = cache[names[i]]
194 if c and not c.typeset then
195 return false
196 end
197 end
198 return true
199end
200
201local function markastypeset(name)
202 local names = getnames(name)
203 for i=1,#names do
204 local c = cache[names[i]]
205 if c then
206 c.typeset = true
207 end
208 end
209end
210
211local function collectcontent(name,separator)
212 local names = getnames(name)
213 local nnames = #names
214 if nnames == 0 then
215 return getcontent("")
216 elseif nnames == 1 then
217 return getcontent(names[1])
218 else
219 local t = { }
220 local n = 0
221 for i=1,nnames do
222 local c = getcontent(names[i])
223 if c ~= "" then
224 n = n + 1
225 t[n] = c
226 end
227 end
228
229
230 return concat(t,separator or os.newline)
231 end
232end
233
234local function loadcontent(name)
235 local content = collectcontent(name,"\n")
236 local ok, err = load(content)
237 if ok then
238 return ok()
239 else
240 report_buffers("invalid lua code in buffer %a: %s",name,err or "unknown error")
241 end
242end
243
244buffers.raw = getcontent
245buffers.erase = erase
246buffers.assign = assign
247buffers.prepend = prepend
248buffers.append = append
249buffers.exists = exists
250buffers.empty = empty
251buffers.getcontent = getcontent
252buffers.getlines = getlines
253buffers.collectcontent = collectcontent
254buffers.loadcontent = loadcontent
255
256
257
258implement {
259 name = "assignbuffer",
260 actions = assign,
261 arguments = { "string", "string", "integer" }
262}
263
264implement {
265 name = "erasebuffer",
266 actions = erase,
267 arguments = "string"
268}
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286local counters = { }
287local nesting = 0
288local autoundent = true
289local continue = false
290
291
292
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
336local strippers = { }
337local nofspaces = 0
338
339local normalline = space^0 / function(s) local n = #s if n < nofspaces then nofspaces = n end end
340 * whatever
341
342local getmargin = (emptyline + normalline)^1
343
344local function undent(str)
345 nofspaces = #str
346 local margin = lpegmatch(getmargin,str)
347 if nofspaces == #str or nofspaces == 0 then
348 return str
349 end
350 local stripper = strippers[nofspaces]
351 if not stripper then
352 stripper = Cs(((space^-nofspaces)/"" * whatever + emptyline)^1)
353 strippers[nofspaces] = stripper
354 end
355 return lpegmatch(stripper,str) or str
356end
357
358buffers.undent = undent
359
360local split = table.setmetatableindex(function(t,k)
361 local v = totable(k)
362 t[k] = v
363 return v
364end)
365
366local tochar = {
367 [ 0] = "\\",
368 [ 1] = "{",
369 [ 2] = "}",
370 [ 3] = "$",
371 [ 4] = "&",
372 [ 5] = "\n",
373 [ 6] = "#",
374 [ 7] = "^",
375 [ 8] = "_",
376 [10] = " ",
377 [14] = "%",
378}
379
380local experiment = false
381local experiment = scantokencode and true
382
383local function pickup(start,stop)
384 local stoplist = split[stop]
385 local stoplength = #stoplist
386 local stoplast = stoplist[stoplength]
387 local startlist = split[start]
388 local startlength = #startlist
389 local startlast = startlist[startlength]
390 local list = { }
391 local size = 0
392 local depth = 0
393
394 local scancode = experiment and scantokencode or scancode
395 while true do
396 local char = scancode()
397 if char then
398
399
400
401
402
403 char = utfchar(char)
404 size = size + 1
405 list[size] = char
406 if char == stoplast and size >= stoplength then
407 local done = true
408 local last = size
409 for i=stoplength,1,-1 do
410 if stoplist[i] ~= list[last] then
411 done = false
412 break
413 end
414 last = last - 1
415 end
416 if done then
417 if depth > 0 then
418 depth = depth - 1
419 else
420 break
421 end
422 char = false
423 end
424 end
425 if char == startlast and size >= startlength then
426 local done = true
427 local last = size
428 for i=startlength,1,-1 do
429 if startlist[i] ~= list[last] then
430 done = false
431 break
432 end
433 last = last - 1
434 end
435 if done then
436 depth = depth + 1
437 end
438 end
439
440 else
441
442 local t = gettoken()
443 if t then
444
445 if experiment and size > 0 then
446
447 local char = tochar[getcommand(t)]
448 if char then
449 size = size + 1 ; list[size] = char
450 else
451
452 local csname = scancsname(t)
453 if csname == stop then
454 stoplength = 0
455 break
456 else
457 size = size + 1 ; list[size] = "\\"
458 size = size + 1 ; list[size] = csname
459 size = size + 1 ; list[size] = " "
460 end
461 end
462 else
463
464 end
465 else
466 break
467 end
468 end
469 end
470 local start = 1
471 local stop = size - stoplength - 1
472
473
474
475
476
477 for i=start,stop do
478 local li = list[i]
479 if lpegmatch(blackspace,li) then
480
481 elseif lpegmatch(eol,li) then
482
483 start = i + 1
484 else
485 break
486 end
487 end
488 for i=stop,start,-1 do
489 if lpegmatch(whitespace,list[i]) then
490 stop = i - 1
491 else
492 break
493 end
494 end
495
496 if start <= stop then
497 return concat(list,"",start,stop)
498 else
499 return ""
500 end
501end
502
503tokens.pickup = pickup
504
505local function showpickup(name,bufferdata,catcodes,undented)
506 undented = undented and ">" or "="
507 if #bufferdata > 50 then
508 report_grabbing("%s : %i =%s |%s..%s|",name,catcodes,undented,sub(bufferdata,1,20),sub(bufferdata,-20,#bufferdata))
509 else
510 report_grabbing("%s : %i =%s |%s|",name,catcodes,undented,bufferdata)
511 end
512end
513
514implement {
515 name = "pickupbuffer",
516 actions = function()
517
518 local name = scanstring()
519 local start = scanstring()
520 local stop = scanstring()
521 local finish = scancsname()
522 local catcodes = scaninteger()
523 local doundent = scaninteger() == 1
524
525 local data = pickup(start,stop)
526 local undented = doundent or (autoundent and doundent == nil)
527 if undented then
528 data = undent(data)
529 end
530 if trace_grab then
531 showpickup(name,data,catcodes,undented)
532 end
533 assign(name,data,catcodes)
534 context[finish]()
535 end
536}
537
538implement {
539 name = "grabbuffer",
540 actions = function()
541
542 local name = scanstring()
543 local start = scanstring()
544 local stop = scanstring()
545 local finish = scancsname()
546 local catcodes = scaninteger()
547 local doundent = scaninteger() == 1
548 local starttok = createtoken(start,true)
549 local stoptok = createtoken(stop,true)
550 local data = grabtokens(starttok,stoptok,true,13)
551 local undented = doundent or (autoundent and doundent == nil)
552 if undented then
553 data = undent(data)
554 end
555 if trace_grab then
556 showpickup(name,data,catcodes,undented)
557 end
558 assign(name,data,catcodes)
559 context[finish]()
560 end
561}
562
563local function savebuffer(list,name,prefix,option,directory)
564 if not list or list == "" then
565 list = name
566 end
567 if not name or name == "" then
568 name = list
569 end
570 local content = collectcontent(list,nil) or ""
571 if content == "" then
572 content = "empty buffer"
573 end
574 if prefix == v_yes then
575 name = addsuffix(tex.jobname .. "-" .. name,"tmp")
576 end
577 if directory ~= "" and dir.makedirs(directory) then
578 name = file.join(directory,name)
579 end
580 savedata(name,replacenewlines(content),"\n",option == v_append)
581end
582
583implement {
584 name = "savebuffer",
585 actions = savebuffer,
586 arguments = "5 strings",
587}
588
589
590
591local olddata = nil
592local newdata = nil
593local getrunner = sandbox.getrunner
594
595local runner = sandbox.registerrunner {
596 name = "run buffer",
597 program = "context",
598 method = "execute",
599 template = [[--purgeall %?path: --path=%path% ?% %filename%]],
600 reporter = report_typeset,
601 checkers = {
602 filename = "readable",
603 path = "string",
604 }
605}
606
607local function runbuffer(name,encapsulate,runnername,suffixes)
608 if not runnername or runnername == "" then
609 runnername = "run buffer"
610 end
611 local suffix = "pdf"
612 if type(suffixes) == "table" then
613 suffix = suffixes[1]
614 elseif type(suffixes) == "string" and suffixes ~= "" then
615 suffix = suffixes
616 suffixes = { suffix }
617 else
618 suffixes = { suffix }
619 end
620 local runner = getrunner(runnername)
621 if not runner then
622 report_typeset("unknown runner %a",runnername)
623 return
624 end
625 if not olddata then
626 olddata = getdata("buffers","runners") or { }
627 local suffixes = olddata.suffixes
628 local hashes = olddata.hashes
629 if hashes and suffixes then
630 for k, hash in next, hashes do
631 for h, v in next, hash do
632 for s, v in next, suffixes do
633 local tmp = addsuffix(h,s)
634
635 registertempfile(tmp)
636 end
637 end
638 end
639 end
640 end
641 if not newdata then
642 newdata = {
643 version = environment.version,
644 suffixes = { },
645 hashes = { },
646 }
647 setdata {
648 name = "buffers",
649 tag = "runners",
650 data = newdata,
651 }
652 end
653 local oldhashes = olddata.hashes or { }
654 local newhashes = newdata.hashes or { }
655 local old = oldhashes[suffix]
656 local new = newhashes[suffix]
657 if not old then
658 old = { }
659 oldhashes[suffix] = old
660 for hash, n in next, old do
661 local tag = formatters["%s-t-b-%s"](tex.jobname,hash)
662 local tmp = addsuffix(tag,"tmp")
663
664 registertempfile(tmp)
665 end
666 end
667 if not new then
668 new = { }
669 newhashes[suffix] = new
670 end
671 local names = getnames(name)
672 local content = collectcontent(names,nil) or ""
673 if content == "" then
674 content = "empty buffer"
675 end
676 if encapsulate then
677 if not find(content,"\\starttext") then
678 content = formatters["\\starttext\n%s\n\\stoptext\n"](content)
679 end
680 end
681
682 local hash = md5hex(content)
683 local tag = formatters["%s-t-b-%s"](nameonly(tex.jobname),hash)
684
685 local filename = addsuffix(tag,"tmp")
686 local resultname = addsuffix(tag,suffix)
687
688 if new[tag] then
689
690 elseif not old[tag] or olddata.version ~= newdata.version or not isfile(resultname) then
691 if trace_run then
692 report_typeset("changes in %a, processing forced",name)
693 end
694 savedata(filename,content)
695 report_typeset("processing saved buffer %a\n",filename)
696 runner {
697 filename = filename,
698 path = environment.arguments.path,
699 }
700 end
701 new[tag] = (new[tag] or 0) + 1
702 report_typeset("no changes in %a, processing skipped",name)
703 registertempfile(filename)
704
705 for i=1,#suffixes do
706 local suffix = suffixes[i]
707 newdata.suffixes[suffix] = true
708 local tmp = addsuffix(tag,suffix)
709
710 registertempfile(tmp,nil,true)
711 end
712
713 return resultname
714end
715
716local f_getbuffer = formatters["buffer.%s"]
717local defaultlist = { "" }
718
719local function getbuffer(name)
720 local list = name and name ~= "" and settings_to_array(name) or defaultlist
721 for i=1,#list do
722 local buf = list[i]
723 local str = getcontent(buf)
724 if str ~= "" then
725
726 ctx_viafile(str,f_getbuffer(validstring(buf,"noname")))
727 end
728 end
729end
730
731local function getbuffermkvi(name)
732 ctx_viafile(resolvers.macros.preprocessed(getcontent(name)),formatters["buffer.%s.mkiv"](validstring(name,"noname")))
733end
734
735local function getbuffertex(name)
736 local buffer = name and cache[name]
737 if buffer and buffer.data ~= "" then
738 ctx_pushcatcodetable()
739 if buffer.catcodes == txtcatcodes then
740 ctx_setcatcodetable(txtcatcodes)
741 else
742 ctx_setcatcodetable(ctxcatcodes)
743 end
744
745 ctx_getbuffer { name }
746 ctx_popcatcodetable()
747 end
748end
749
750buffers.get = getbuffer
751buffers.getmkvi = getbuffermkvi
752buffers.gettex = getbuffertex
753buffers.getctxlua = loadcontent
754buffers.run = runbuffer
755
756implement { name = "getbufferctxlua", actions = loadcontent, arguments = "argument" }
757implement { name = "getbuffer", actions = getbuffer, arguments = "argument" }
758implement { name = "getbuffermkvi", actions = getbuffermkvi, arguments = "argument" }
759implement { name = "getbuffertex", actions = getbuffertex, arguments = "argument" }
760
761implement {
762 name = "getbuffercontent",
763 arguments = "string",
764 actions = { getcontent, context },
765}
766
767implement {
768 name = "typesetbuffer",
769 actions = { runbuffer, context },
770 arguments = { "string", true }
771}
772
773implement {
774 name = "runbuffer",
775 actions = { runbuffer, context },
776 arguments = { "string", false, "string" }
777}
778
779implement {
780 name = "doifelsebuffer",
781 actions = { exists, commands.doifelse },
782 public = true,
783
784 arguments = "string"
785}
786
787implement {
788 name = "doifelsebufferempty",
789 actions = { empty, commands.doifelse },
790 public = true,
791 protected = true,
792 arguments = "string"
793}
794
795
796
797
798
799implement {
800 name = "feedback",
801 actions = { collectcontent, ctx_printlines },
802 arguments = "string"
803}
804
805do
806
807 local context = context
808 local ctxcore = context.core
809
810 local ctx_startbuffer = ctxcore.startbuffer
811 local ctx_stopbuffer = ctxcore.stopbuffer
812
813 local ctx_startcollecting = context.startcollecting
814 local ctx_stopcollecting = context.stopcollecting
815
816 function ctxcore.startbuffer(...)
817 ctx_startcollecting()
818 ctx_startbuffer(...)
819 end
820
821 function ctxcore.stopbuffer()
822 ctx_stopbuffer()
823 ctx_stopcollecting()
824 end
825
826end
827
828
829
830local findfile = resolvers.findfile
831local setfilename = luatex.synctex.setfilename
832
833function buffers.samplefile(name,before,after,inbetween)
834 local fullname = findfile(name)
835 if fullname and fullname ~= "" then
836 if not exists(name) then
837 assign(name,io.loaddata(fullname))
838 end
839 setfilename(fullname)
840 getbuffer(name)
841 end
842end
843
844implement {
845 name = "samplefile",
846 public = true,
847 protected = true,
848 actions = buffers.samplefile,
849 arguments = "string"
850}
851
852function buffers.splitsamplefile(name,before,after,inbetween)
853 local fullname = findfile(name)
854 if fullname and fullname ~= "" then
855 local data = io.loaddata(fullname) or ""
856 local split = string.split(utilities.strings.striplines(data,"prune and collapse"),lpegpatterns.whitespace^1)
857 local size = #split
858 local before = tonumber(before) or 1
859 local after = tonumber(after) or 1
860 if before + after < size then
861 table.move(split,size-after,size,before+1)
862 if inbetween and inbetween ~= "" then
863 split[before+1] = inbetween
864 end
865 data = concat(split, " ",1,before+after+1)
866 end
867 assign(name,data)
868 setfilename(fullname)
869 getbuffer(name)
870 end
871end
872
873implement {
874 name = "splitsamplefile",
875 public = true,
876 protected = true,
877 actions = buffers.splitsamplefile,
878 arguments = "4 strings",
879}
880
881
882
883
884
885
886
887
888do
889
890 local insert, remove = table.insert, table.remove
891 local setlogfile = texio.setlogfile
892 local openfile = io.open
893
894 local stack = { }
895 local files = { }
896
897 local function resetlogfile(name)
898 files[name] = false
899 end
900
901 local function pushlogfile(name)
902 local f = openfile(name,files[name] and "ab" or "wb")
903 insert(stack,f)
904 files[name] = true
905 setlogfile(f)
906 end
907
908 local function poplogfile()
909 remove(stack)
910 setlogfile(stack[#stack])
911 end
912
913 logs.pushlogfile = pushlogfile
914 logs.poplogfile = poplogfile
915 logs.resetlogfile = resetlogfile
916
917 implement {
918 name = "resetlogfile",
919 arguments = "argument",
920 public = true,
921 protected = true,
922 actions = resetlogfile,
923 }
924
925 implement {
926 name = "pushlogfile",
927 arguments = "argument",
928 public = true,
929 protected = true,
930 actions = pushlogfile,
931 }
932
933 implement {
934 name = "poplogfile",
935 public = true,
936 protected = true,
937 actions = poplogfile,
938 }
939
940
941
942
943 local serialized = nodes.nuts.serialized
944 local getbox = nodes.nuts.getbox
945
946 implement {
947 name = "showboxinbuffer",
948 public = true,
949 protected = true,
950 arguments = { "argument", "integer", "integer" },
951 actions = function(buffer, box, detail)
952 local box = getbox(box)
953 assign(buffer or "",box and serialized(box,detail))
954 end,
955 }
956
957end
958
959do
960
961 local integer_value <const> = tokens.values.integer
962
963 local p_newline = lpegpatterns.newline
964
965 local amount
966
967 local p_count = (
968 p_newline / function() amount = amount + 1 end
969 + 1
970 )^0
971
972 local p_width = (
973 (
974 Cp() * (lpegpatterns.utf8char-p_newline)^1 * Cp() * (p_newline + P(0))
975 ) / function(f,t) local l = t - f ; if l > amount then amount = l end end
976 + p_newline
977 )^0
978
979 implement {
980 name = "nofbufferlines",
981 public = true,
982 usage = "value",
983 arguments = "string",
984 actions = function(n)
985 amount = 0
986 local data = getcontent(n)
987 if data and data ~= "" then
988 lpegmatch(p_count,data)
989 end
990 return integer_value, amount
991 end
992 }
993
994 implement {
995 name = "maxbufferwidth",
996 public = true,
997 usage = "value",
998 arguments = "string",
999 actions = function(n)
1000 amount = 0
1001 local data = getcontent(n)
1002 if data and data ~= "" then
1003 lpegmatch(p_width,data)
1004 end
1005 return integer_value, amount
1006 end
1007 }
1008
1009end
1010 |