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 = string.sub, string.format, string.find
12local splitlines, validstring, replacenewlines = string.splitlines, string.valid, string.replacenewlines
13local P, Cs, patterns, lpegmatch = lpeg.P, lpeg.Cs, lpeg.patterns, lpeg.match
14local utfchar = utf.char
15local nameonly = file.nameonly
16local totable = string.totable
17local md5hex = md5.hex
18local isfile = lfs.isfile
19local savedata = io.savedata
20
21local trace_run = false trackers.register("buffers.run", function(v) trace_run = v end)
22local trace_grab = false trackers.register("buffers.grab", function(v) trace_grab = v end)
23local trace_visualize = false trackers.register("buffers.visualize", function(v) trace_visualize = v end)
24
25local report_buffers = logs.reporter("buffers","usage")
26local report_typeset = logs.reporter("buffers","typeset")
27
28
29local context = context
30local commands = commands
31
32local implement = interfaces.implement
33
34local scanners = tokens.scanners
35local scanstring = scanners.string
36local scaninteger = scanners.integer
37local scanboolean = scanners.boolean
38local scancode = scanners.code
39local scantokencode = scanners.tokencode
40
41
42local getters = tokens.getters
43local gettoken = getters.token
44
45local getcommand = token.get_command
46local getcsname = token.get_csname
47local getnextchar = token.scan_next_char or token.get_next_char
48
49local variables = interfaces.variables
50local settings_to_array = utilities.parsers.settings_to_array
51local formatters = string.formatters
52local addsuffix = file.addsuffix
53local replacesuffix = file.replacesuffix
54
55local registertempfile = luatex.registertempfile
56
57local v_yes = variables.yes
58local v_append = variables.append
59
60local eol = patterns.eol
61local space = patterns.space
62local whitespace = patterns.whitespace
63local blackspace = whitespace - eol
64local whatever = (1-eol)^1 * eol^0
65local emptyline = space^0 * eol
66
67local catcodenumbers = catcodes.numbers
68
69local ctxcatcodes = catcodenumbers.ctxcatcodes
70local txtcatcodes = catcodenumbers.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 cache[name] = nil
89end
90
91local function assign(name,str,catcodes)
92 cache[name] = {
93 data = str,
94 catcodes = catcodes,
95 typeset = false,
96 }
97end
98
99local function combine(name,str,prepend)
100 local buffer = cache[name]
101 if buffer then
102 buffer.data = prepend and (str .. buffer.data) or (buffer.data .. str)
103 buffer.typeset = false
104 else
105 cache[name] = {
106 data = str,
107 typeset = false,
108 }
109 end
110end
111
112local function prepend(name,str)
113 combine(name,str,true)
114end
115
116local function append(name,str)
117 combine(name,str)
118end
119
120local function exists(name)
121 return cache[name]
122end
123
124local function getcontent(name)
125 local buffer = name and cache[name]
126 return buffer and buffer.data or ""
127end
128
129local function empty(name)
130 if find(getcontent(name),"%S") then
131 return false
132 else
133 return true
134 end
135end
136
137local function getlines(name)
138 local buffer = name and cache[name]
139 return buffer and splitlines(buffer.data)
140end
141
142local function getnames(name)
143 if type(name) == "string" then
144 return settings_to_array(name)
145 else
146 return name
147 end
148end
149
150local function istypeset(name)
151 local names = getnames(name)
152 if #names == 0 then
153 return false
154 end
155 for i=1,#names do
156 local c = cache[names[i]]
157 if c and not c.typeset then
158 return false
159 end
160 end
161 return true
162end
163
164local function markastypeset(name)
165 local names = getnames(name)
166 for i=1,#names do
167 local c = cache[names[i]]
168 if c then
169 c.typeset = true
170 end
171 end
172end
173
174local function collectcontent(name,separator)
175 local names = getnames(name)
176 local nnames = #names
177 if nnames == 0 then
178 return getcontent("")
179 elseif nnames == 1 then
180 return getcontent(names[1])
181 else
182 local t = { }
183 local n = 0
184 for i=1,nnames do
185 local c = getcontent(names[i])
186 if c ~= "" then
187 n = n + 1
188 t[n] = c
189 end
190 end
191
192
193 return concat(t,separator or os.newline)
194 end
195end
196
197local function loadcontent(name)
198 local content = collectcontent(name,"\n")
199 local ok, err = load(content)
200 if ok then
201 return ok()
202 else
203 report_buffers("invalid lua code in buffer %a: %s",name,err or "unknown error")
204 end
205end
206
207buffers.raw = getcontent
208buffers.erase = erase
209buffers.assign = assign
210buffers.prepend = prepend
211buffers.append = append
212buffers.exists = exists
213buffers.empty = empty
214buffers.getcontent = getcontent
215buffers.getlines = getlines
216buffers.collectcontent = collectcontent
217buffers.loadcontent = loadcontent
218
219
220
221implement {
222 name = "assignbuffer",
223 actions = assign,
224 arguments = { "string", "string", "integer" }
225}
226
227implement {
228 name = "erasebuffer",
229 actions = erase,
230 arguments = "string"
231}
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249local counters = { }
250local nesting = 0
251local autoundent = true
252local continue = false
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299local strippers = { }
300local nofspaces = 0
301
302local normalline = space^0 / function(s) local n = #s if n < nofspaces then nofspaces = n end end
303 * whatever
304
305local getmargin = (emptyline + normalline)^1
306
307local function undent(str)
308 nofspaces = #str
309 local margin = lpegmatch(getmargin,str)
310 if nofspaces == #str or nofspaces == 0 then
311 return str
312 end
313 local stripper = strippers[nofspaces]
314 if not stripper then
315 stripper = Cs(((space^-nofspaces)/"" * whatever + emptyline)^1)
316 strippers[nofspaces] = stripper
317 end
318 return lpegmatch(stripper,str) or str
319end
320
321buffers.undent = undent
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367local split = table.setmetatableindex(function(t,k)
368 local v = totable(k)
369 t[k] = v
370 return v
371end)
372
373local tochar = {
374 [ 0] = "\\",
375 [ 1] = "{",
376 [ 2] = "}",
377 [ 3] = "$",
378 [ 4] = "&",
379 [ 5] = "\n",
380 [ 6] = "#",
381 [ 7] = "^",
382 [ 8] = "_",
383 [10] = " ",
384 [14] = "%",
385}
386
387local experiment = false
388local experiment = scantokencode and true
389
390local function pickup(start,stop)
391 local stoplist = split[stop]
392 local stoplength = #stoplist
393 local stoplast = stoplist[stoplength]
394 local startlist = split[start]
395 local startlength = #startlist
396 local startlast = startlist[startlength]
397 local list = { }
398 local size = 0
399 local depth = 0
400
401 local scancode = experiment and scantokencode or scancode
402 while true do
403 local char = scancode()
404 if char then
405
406
407
408
409
410 char = utfchar(char)
411 size = size + 1
412 list[size] = char
413 if char == stoplast and size >= stoplength then
414 local done = true
415 local last = size
416 for i=stoplength,1,-1 do
417 if stoplist[i] ~= list[last] then
418 done = false
419 break
420 end
421 last = last - 1
422 end
423 if done then
424 if depth > 0 then
425 depth = depth - 1
426 else
427 break
428 end
429 char = false
430 end
431 end
432 if char == startlast and size >= startlength then
433 local done = true
434 local last = size
435 for i=startlength,1,-1 do
436 if startlist[i] ~= list[last] then
437 done = false
438 break
439 end
440 last = last - 1
441 end
442 if done then
443 depth = depth + 1
444 end
445 end
446
447 else
448
449 local t = gettoken()
450 if t then
451
452 if experiment and size > 0 then
453
454 local char = tochar[getcommand(t)]
455 if char then
456 size = size + 1 ; list[size] = char
457 else
458 local csname = getcsname(t)
459 if csname == stop then
460 stoplength = 0
461 break
462 else
463 size = size + 1 ; list[size] = "\\"
464 size = size + 1 ; list[size] = csname
465 size = size + 1 ; list[size] = " "
466 end
467 end
468 else
469
470 end
471 else
472 break
473 end
474 end
475 end
476 local start = 1
477 local stop = size - stoplength - 1
478
479
480
481
482
483 for i=start,stop do
484 local li = list[i]
485 if lpegmatch(blackspace,li) then
486
487 elseif lpegmatch(eol,li) then
488
489 start = i + 1
490 else
491 break
492 end
493 end
494 for i=stop,start,-1 do
495 if lpegmatch(whitespace,list[i]) then
496 stop = i - 1
497 else
498 break
499 end
500 end
501
502 if start <= stop then
503 return concat(list,"",start,stop)
504 else
505 return ""
506 end
507end
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632tokens.pickup = pickup
633
634implement {
635 name = "pickupbuffer",
636 actions = function()
637
638 local name = scanstring()
639 local start = scanstring()
640 local stop = scanstring()
641 local finish = scanstring()
642 local catcodes = scaninteger()
643 local doundent = scanboolean()
644
645 local data = pickup(start,stop)
646 if doundent or (autoundent and doundent == nil) then
647 data = undent(data)
648 end
649 buffers.assign(name,data,catcodes)
650
651 context(finish)
652 end
653}
654
655local function savebuffer(list,name,prefix,option,directory)
656 if not list or list == "" then
657 list = name
658 end
659 if not name or name == "" then
660 name = list
661 end
662 local content = collectcontent(list,nil) or ""
663 if content == "" then
664 content = "empty buffer"
665 end
666 if prefix == v_yes then
667 name = addsuffix(tex.jobname .. "-" .. name,"tmp")
668 end
669 if directory ~= "" and dir.makedirs(directory) then
670 name = file.join(directory,name)
671 end
672 io.savedata(name,replacenewlines(content),"\n",option == v_append)
673end
674
675implement {
676 name = "savebuffer",
677 actions = savebuffer,
678 arguments = "5 strings",
679}
680
681
682
683local olddata = nil
684local newdata = nil
685local getrunner = sandbox.getrunner
686
687local runner = sandbox.registerrunner {
688 name = "run buffer",
689 program = "context",
690 method = "execute",
691 template = (jit and "--jit --engine=luajittex" or "--engine=luatex") .. " --purgeall %?path: --path=%path% ?% %filename%",
692 reporter = report_typeset,
693 checkers = {
694 filename = "readable",
695 }
696}
697
698local function runbuffer(name,encapsulate,runnername,suffixes)
699 if not runnername or runnername == "" then
700 runnername = "run buffer"
701 end
702 local suffix = "pdf"
703 if type(suffixes) == "table" then
704 suffix = suffixes[1]
705 elseif type(suffixes) == "string" and suffixes ~= "" then
706 suffix = suffixes
707 suffixes = { suffix }
708 else
709 suffixes = { suffix }
710 end
711 local runner = getrunner(runnername)
712 if not runner then
713 report_typeset("unknown runner %a",runnername)
714 return
715 end
716 if not olddata then
717 olddata = getdata("buffers","runners") or { }
718 local suffixes = olddata.suffixes
719 local hashes = olddata.hashes
720 if hashes and suffixes then
721 for k, hash in next, hashes do
722 for h, v in next, hash do
723 for s, v in next, suffixes do
724 local tmp = addsuffix(h,s)
725
726 registertempfile(tmp)
727 end
728 end
729 end
730 end
731 end
732 if not newdata then
733 newdata = {
734 version = environment.version,
735 suffixes = { },
736 hashes = { },
737 }
738 setdata {
739 name = "buffers",
740 tag = "runners",
741 data = newdata,
742 }
743 end
744 local oldhashes = olddata.hashes or { }
745 local newhashes = newdata.hashes or { }
746 local old = oldhashes[suffix]
747 local new = newhashes[suffix]
748 if not old then
749 old = { }
750 oldhashes[suffix] = old
751 for hash, n in next, old do
752 local tag = formatters["%s-t-b-%s"](tex.jobname,hash)
753 local tmp = addsuffix(tag,"tmp")
754
755 registertempfile(tmp)
756 end
757 end
758 if not new then
759 new = { }
760 newhashes[suffix] = new
761 end
762 local names = getnames(name)
763 local content = collectcontent(names,nil) or ""
764 if content == "" then
765 content = "empty buffer"
766 end
767 if encapsulate then
768 content = formatters["\\starttext\n%s\n\\stoptext\n"](content)
769 end
770
771 local hash = md5hex(content)
772 local tag = formatters["%s-t-b-%s"](nameonly(tex.jobname),hash)
773
774 local filename = addsuffix(tag,"tmp")
775 local resultname = addsuffix(tag,suffix)
776
777 if new[tag] then
778
779 elseif not old[tag] or olddata.version ~= newdata.version or not isfile(resultname) then
780 if trace_run then
781 report_typeset("changes in %a, processing forced",name)
782 end
783 savedata(filename,content)
784 report_typeset("processing saved buffer %a\n",filename)
785 runner {
786 filename = filename,
787 path = environment.arguments.path,
788 }
789 end
790 new[tag] = (new[tag] or 0) + 1
791 report_typeset("no changes in %a, processing skipped",name)
792 registertempfile(filename)
793
794 for i=1,#suffixes do
795 local suffix = suffixes[i]
796 newdata.suffixes[suffix] = true
797 local tmp = addsuffix(tag,suffix)
798
799 registertempfile(tmp,nil,true)
800 end
801
802 return resultname
803end
804
805local f_getbuffer = formatters["buffer.%s"]
806
807local function getbuffer(name)
808 local str = getcontent(name)
809 if str ~= "" then
810
811 ctx_viafile(str,f_getbuffer(validstring(name,"noname")))
812 end
813end
814
815local function getbuffermkvi(name)
816 ctx_viafile(resolvers.macros.preprocessed(getcontent(name)),formatters["buffer.%s.mkiv"](validstring(name,"noname")))
817end
818
819local function getbuffertex(name)
820 local buffer = name and cache[name]
821 if buffer and buffer.data ~= "" then
822 ctx_pushcatcodetable()
823 if buffer.catcodes == txtcatcodes then
824 ctx_setcatcodetable(txtcatcodes)
825 else
826 ctx_setcatcodetable(ctxcatcodes)
827 end
828
829 ctx_getbuffer { name }
830 ctx_popcatcodetable()
831 end
832end
833
834buffers.get = getbuffer
835buffers.getmkvi = getbuffermkvi
836buffers.gettex = getbuffertex
837buffers.run = runbuffer
838
839implement { name = "getbufferctxlua", actions = loadcontent, arguments = "string" }
840implement { name = "getbuffer", actions = getbuffer, arguments = "string" }
841implement { name = "getbuffermkvi", actions = getbuffermkvi, arguments = "string" }
842implement { name = "getbuffertex", actions = getbuffertex, arguments = "string" }
843
844interfaces.implement {
845 name = "getbuffercontent",
846 arguments = "string",
847 actions = { getcontent, context },
848}
849
850implement {
851 name = "typesetbuffer",
852 actions = { runbuffer, context },
853 arguments = { "string", true }
854}
855
856implement {
857 name = "runbuffer",
858 actions = { runbuffer, context },
859 arguments = { "string", false, "string" }
860}
861
862implement {
863 name = "doifelsebuffer",
864 actions = { exists, commands.doifelse },
865 arguments = "string"
866}
867
868implement {
869 name = "doifelsebufferempty",
870 actions = { empty, commands.doifelse },
871 arguments = "string"
872}
873
874
875
876
877
878implement {
879 name = "feedback",
880 actions = { collectcontent, ctx_printlines },
881 arguments = "string"
882}
883
884do
885
886 local context = context
887 local ctxcore = context.core
888
889 local ctx_startbuffer = ctxcore.startbuffer
890 local ctx_stopbuffer = ctxcore.stopbuffer
891
892 local ctx_startcollecting = context.startcollecting
893 local ctx_stopcollecting = context.stopcollecting
894
895 function ctxcore.startbuffer(...)
896 ctx_startcollecting()
897 ctx_startbuffer(...)
898 end
899
900 function ctxcore.stopbuffer()
901 ctx_stopbuffer()
902 ctx_stopcollecting()
903 end
904
905end
906
907
908
909function buffers.samplefile(name)
910 if not exists(name) then
911 assign(name,io.loaddata(resolvers.findfile(name)))
912 end
913 getbuffer(name)
914end
915
916implement {
917 name = "samplefile",
918 actions = buffers.samplefile,
919 arguments = "string"
920}
921 |