1if not modules then modules = { } end modules ['buff-ver'] = {
2 version = 1.001,
3 comment = "companion to buff-ver.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
9
10
11
12
13
14
15
16local type, next, rawset, rawget, setmetatable, getmetatable, tonumber = type, next, rawset, rawget, setmetatable, getmetatable, tonumber
17local lower, upper,match, find, sub = string.lower, string.upper, string.match, string.find, string.sub
18local splitlines = string.splitlines
19local concat = table.concat
20local C, P, R, S, V, Carg, Cc, Cs = lpeg.C, lpeg.P, lpeg.R, lpeg.S, lpeg.V, lpeg.Carg, lpeg.Cc, lpeg.Cs
21local patterns, lpegmatch, is_lpeg = lpeg.patterns, lpeg.match, lpeg.is_lpeg
22
23local trace_visualize = false trackers.register("buffers.visualize", function(v) trace_visualize = v end)
24local report_visualizers = logs.reporter("buffers","visualizers")
25
26local allocate = utilities.storage.allocate
27
28visualizers = visualizers or { }
29local specifications = allocate()
30visualizers.specifications = specifications
31
32local context = context
33local commands = commands
34local implement = interfaces.implement
35
36local formatters = string.formatters
37
38local tabtospace = utilities.strings.tabtospace
39local variables = interfaces.variables
40local settings_to_array = utilities.parsers.settings_to_array
41local variables = interfaces.variables
42local findfile = resolvers.findfile
43local addsuffix = file.addsuffix
44
45local v_yes = variables.yes
46local v_no = variables.no
47local v_last = variables.last
48local v_all = variables.all
49local v_absolute = variables.absolute
50
51
52
53
54
55local ctx_inlineverbatimnewline = context.doinlineverbatimnewline
56local ctx_inlineverbatimbeginline = context.doinlineverbatimbeginline
57local ctx_inlineverbatimemptyline = context.doinlineverbatimemptyline
58local ctx_inlineverbatimstart = context.doinlineverbatimstart
59local ctx_inlineverbatimstop = context.doinlineverbatimstop
60
61local ctx_displayverbatiminitialize = context.dodisplayverbatiminitialize
62local ctx_displayverbatimnewline = context.dodisplayverbatimnewline
63local ctx_displayverbatimbeginline = context.dodisplayverbatimbeginline
64local ctx_displayverbatimemptyline = context.dodisplayverbatimemptyline
65local ctx_displayverbatimstart = context.dodisplayverbatimstart
66local ctx_displayverbatimstop = context.dodisplayverbatimstop
67
68local ctx_verbatim = context.verbatim
69local ctx_verbatimspace = context.doverbatimspace
70
71local CargOne = Carg(1)
72
73local function f_emptyline(s,settings)
74 if settings and settings.nature == "inline" then
75 ctx_inlineverbatimemptyline()
76 else
77 ctx_displayverbatimemptyline()
78 end
79end
80
81local function f_beginline(s,settings)
82 if settings and settings.nature == "inline" then
83 ctx_inlineverbatimbeginline()
84 else
85 ctx_displayverbatimbeginline()
86 end
87end
88
89local function f_newline(s,settings)
90 if settings and settings.nature == "inline" then
91 ctx_inlineverbatimnewline()
92 else
93 ctx_displayverbatimnewline()
94 end
95end
96
97local function f_start(s,settings)
98 if settings and settings.nature == "inline" then
99 ctx_inlineverbatimstart()
100 else
101 ctx_displayverbatimstart()
102 end
103end
104
105local function f_stop(s,settings)
106 if settings and settings.nature == "inline" then
107 ctx_inlineverbatimstop()
108 else
109 ctx_displayverbatimstop()
110 end
111end
112
113local function f_default(s)
114 ctx_verbatim(s)
115end
116
117local function f_space()
118 ctx_verbatimspace()
119end
120
121local function f_signal()
122
123end
124
125local signal = "\000"
126
127visualizers.signal = signal
128visualizers.signalpattern = P(signal)
129
130local functions = {
131 __index = {
132 emptyline = f_emptyline,
133 newline = f_newline,
134 default = f_default,
135 beginline = f_beginline,
136 space = f_space,
137 start = f_start,
138 stop = f_stop,
139 signal = f_signal,
140 }
141}
142
143local handlers = { }
144
145function visualizers.newhandler(name,data)
146 local tname = type(name)
147 local tdata = type(data)
148 if tname == "table" then
149 setmetatable(name,getmetatable(name) or functions)
150 return name
151 elseif tname == "string" then
152 if tdata == "string" then
153 local result = { }
154 setmetatable(result,getmetatable(handlers[data]) or functions)
155 handlers[name] = result
156 return result
157 elseif tdata == "table" then
158 setmetatable(data,getmetatable(data) or functions)
159 handlers[name] = data
160 return data
161 else
162 local result = { }
163 setmetatable(result,functions)
164 handlers[name] = result
165 return result
166 end
167 else
168 local result = { }
169 setmetatable(result,functions)
170 return result
171 end
172end
173
174function visualizers.newgrammar(name,t)
175 name = lower(name)
176 t = t or { }
177 local g = visualizers.specifications[name]
178 g = g and g.grammar
179 if g then
180 if trace_visualize then
181 report_visualizers("cloning grammar %a",name)
182 end
183 for k,v in next, g do
184 if not t[k] then
185 t[k] = v
186 end
187 if is_lpeg(v) then
188 t[name..":"..k] = v
189 end
190 end
191 end
192 return t
193end
194
195local function getvisualizer(method,nature)
196 method = lower(method)
197 local m = specifications[method] or specifications.default
198 if nature then
199 if trace_visualize then
200 report_visualizers("getting visualizer %a with nature %a",method,nature)
201 end
202 return m and (m[nature] or m.parser) or nil
203 else
204 if trace_visualize then
205 report_visualizers("getting visualizer %a",method)
206 end
207 return m and m.parser or nil
208 end
209end
210
211local ctx_fallback = ctx_verbatim
212
213local function makepattern(visualizer,replacement,pattern)
214 if not pattern then
215 report_visualizers("error in visualizer %a",replacement)
216 return patterns.alwaystrue
217 else
218 if type(visualizer) == "table" and type(replacement) == "string" then
219 replacement = visualizer[replacement] or ctx_fallback
220 else
221 replacement = ctx_fallback
222 end
223 return (C(pattern) * CargOne) / replacement
224 end
225end
226
227local function makenested(handler,how,start,stop)
228 local b, e, f = P(start), P(stop), how
229 if type(how) == "string" then
230 f = function(s) getvisualizer(how,"direct")(s) end
231 end
232 return makepattern(handler,"name",b)
233 * ((1-e)^1/f)
234 * makepattern(handler,"name",e)
235end
236
237visualizers.pattern = makepattern
238visualizers.makepattern = makepattern
239visualizers.makenested = makenested
240
241function visualizers.load(name)
242 name = lower(name)
243 if rawget(specifications,name) == nil then
244 name = lower(name)
245 local impname = "buff-imp-"..name
246 local texname = findfile(addsuffix(impname,"mkiv"))
247 local luaname = findfile(addsuffix(impname,"lua"))
248 if texname == "" or luaname == "" then
249
250 luaname = findfile(addsuffix(name,"mkiv"))
251 texname = findfile(addsuffix(name,"lua"))
252 end
253 if texname == "" or luaname == "" then
254 if trace_visualize then
255 report_visualizers("unknown visualizer %a",name)
256 end
257 else
258 if trace_visualize then
259 report_visualizers("loading visualizer %a",name)
260 end
261 lua.registercode(luaname)
262 context.input(texname)
263 end
264 if rawget(specifications,name) == nil then
265 rawset(specifications,name,false)
266 end
267 end
268end
269
270function visualizers.register(name,specification)
271 name = lower(name)
272 if trace_visualize then
273 report_visualizers("registering visualizer %a",name)
274 end
275 specifications[name] = specification
276 local parser = specification.parser
277 local handler = specification.handler
278 local displayparser = specification.display or parser
279 local inlineparser = specification.inline or parser
280 local isparser = is_lpeg(parser)
281 local start, stop
282 if isparser then
283 start = makepattern(handler,"start",patterns.alwaysmatched)
284 stop = makepattern(handler,"stop", patterns.alwaysmatched)
285 end
286 if handler then
287 if isparser then
288 specification.display = function(content,settings)
289 if handler.startdisplay then handler.startdisplay(settings) end
290 lpegmatch(start * displayparser * stop,content,1,settings)
291 if handler.stopdisplay then handler.stopdisplay(settings) end
292 end
293 specification.inline = function(content,settings)
294 if handler.startinline then handler.startinline(settings) end
295 lpegmatch(start * inlineparser * stop,content,1,settings)
296 if handler.stopinline then handler.stopinline(settings) end
297 end
298 specification.direct = function(content,settings)
299 lpegmatch(parser,content,1,settings)
300 end
301 elseif parser then
302 specification.display = function(content,settings)
303 if handler.startdisplay then handler.startdisplay(settings) end
304 parser(content,settings)
305 if handler.stopdisplay then handler.stopdisplay(settings) end
306 end
307 specification.inline = function(content,settings)
308 if handler.startinline then handler.startinline(settings) end
309 parser(content,settings)
310 if handler.stopinline then handler.stopinline(settings) end
311 end
312 specification.direct = parser
313 end
314 elseif isparser then
315 specification.display = function(content,settings)
316 lpegmatch(start * displayparser * stop,content,1,settings)
317 end
318 specification.inline = function(content,settings)
319 lpegmatch(start * inlineparser * stop,content,1,settings)
320 end
321 specification.direct = function(content,settings)
322 lpegmatch(parser,content,1,settings)
323 end
324 elseif parser then
325 specification.display = parser
326 specification.inline = parser
327 specification.direct = parser
328 end
329 return specification
330end
331
332function visualizers.getspecification(name)
333 return specifications[lower(name)]
334end
335
336local escapepatterns = allocate()
337visualizers.escapepatterns = escapepatterns
338
339local function texmethod(s)
340 context.bgroup()
341 context(s)
342 context.egroup()
343end
344
345local function texcommand(s)
346 context[s]()
347end
348
349local function defaultmethod(s,settings)
350 lpegmatch(getvisualizer("default"),lower(s),1,settings)
351end
352
353
354
355local space_pattern = patterns.space^0
356local name_pattern = R("az","AZ")^1
357
358
359
360
361local function hack(pattern)
362 return Cs(pattern * Cc(signal))
363end
364
365local split_processor = typesetters.processors.split
366local apply_processor = typesetters.processors.apply
367
368
369
370function visualizers.registerescapepattern(name,befores,afters,normalmethod,escapemethod,processors)
371 local escapepattern = escapepatterns[name]
372 if not escapepattern then
373 if type(befores) ~= "table" then befores = { befores } end
374 if type(afters) ~= "table" then afters = { afters } end
375 if type(processors) ~= "table" then processors = { processors } end
376 for i=1,#befores do
377 local before = befores[i]
378 local after = afters[i]
379 local processor = processors[i]
380 if trace_visualize then
381 report_visualizers("registering escape pattern, name %a, index %a, before %a, after %a, processor %a",
382 name,i,before,after,processor or "default")
383 end
384 before = P(before) * space_pattern
385 after = space_pattern * P(after)
386 local action
387 if processor then
388 action = function(s) apply_processor(processor,s) end
389 else
390 action = escapemethod or texmethod
391 end
392 local ep = (before / "") * ((1 - after)^0 / action) * (after / "")
393 if escapepattern then
394 escapepattern = escapepattern + ep
395 else
396 escapepattern = ep
397 end
398 end
399 escapepattern = (
400 escapepattern
401 + hack((1 - escapepattern)^1) / (normalmethod or defaultmethod)
402 )^0
403 escapepatterns[name] = escapepattern
404 end
405 return escapepattern
406end
407
408function visualizers.registerescapeline(name,befores,normalmethod,escapemethod,processors)
409 local escapepattern = escapepatterns[name]
410 if not escapepattern then
411 if type(befores) ~= "table" then befores = { befores } end
412 if type(processors) ~= "table" then processors = { processors } end
413 for i=1,#befores do
414 local before = befores[i]
415 local processor = processors[i]
416 if trace_visualize then
417 report_visualizers("registering escape line pattern, name %a, before %a, after <<newline>>",name,before)
418 end
419 before = P(before) * space_pattern
420 after = space_pattern * P("\n")
421 local action
422 if processor then
423 action = function(s) apply_processor(processor,s) end
424 else
425 action = escapemethod or texmethod
426 end
427 local ep = (before / "") * ((1 - after)^0 / action) * (space_pattern / "")
428 if escapepattern then
429 escapepattern = escapepattern + ep
430 else
431 escapepattern = ep
432 end
433 end
434 escapepattern = (
435 escapepattern
436 + hack((1 - escapepattern)^1) / (normalmethod or defaultmethod)
437 )^0
438 escapepatterns[name] = escapepattern
439 end
440 return escapepattern
441end
442
443function visualizers.registerescapecommand(name,token,normalmethod,escapecommand,processor)
444 local escapepattern = escapepatterns[name]
445 if not escapepattern then
446 if trace_visualize then
447 report_visualizers("registering escape token, name %a, token %a",name,token)
448 end
449 token = P(token)
450 local notoken = hack((1 - token)^1)
451 local cstoken = Cs(name_pattern * (space_pattern/""))
452 escapepattern = (
453 (token / "")
454 * (cstoken / (escapecommand or texcommand))
455 + (notoken / (normalmethod or defaultmethod))
456 )^0
457 escapepatterns[name] = escapepattern
458 end
459 return escapepattern
460end
461
462local escapedvisualizers = { }
463local f_escapedvisualizer = formatters["%s : %s"]
464
465local function visualize(content,settings)
466 if content and content ~= "" then
467 local method = lower(settings.method or "default")
468 local m = specifications[method] or specifications.default
469 local e = settings.escape
470 if e and e ~= "" and not m.handler.noescape then
471 local newname = f_escapedvisualizer(method,e)
472 local newspec = specifications[newname]
473 if newspec then
474 m = newspec
475 else
476 local starts, stops, processors = { }, { }, { }
477 if e == v_yes then
478 starts[1] = "/BTEX"
479 stops [1] = "/ETEX"
480 else
481 local s = settings_to_array(e,true)
482 for i=1,#s do
483 local si = s[i]
484 local processor, pattern = split_processor(si)
485 si = processor and pattern or si
486 local start, stop = match(si,"^(.-),(.-)$")
487 if start then
488 local n = #starts + 1
489 starts[n] = start
490 stops [n] = stop or ""
491 processors[n] = processor
492 end
493 end
494 end
495 local oldm = m
496 local oldparser = oldm.direct
497 local newhandler = oldm.handler
498 local newparser = oldm.parser
499 if starts[1] and stops[1] ~= "" then
500 newparser = visualizers.registerescapepattern(newname,starts,stops,oldparser,nil,processors)
501 elseif starts[1] then
502 newparser = visualizers.registerescapeline(newname,starts,oldparser,nil,processors)
503 else
504 newparser = visualizers.registerescapecommand(newname,e,oldparser,nil,processors)
505 end
506 m = visualizers.register(newname, {
507 parser = newparser,
508 handler = newhandler,
509 })
510 end
511 else
512 m = specifications[method] or specifications.default
513 end
514 local nature = settings.nature or "display"
515 local n = m and m[nature]
516 if n then
517 if trace_visualize then
518 report_visualizers("visualize using method %a and nature %a",method,nature)
519 end
520 n(content,settings)
521 else
522 if trace_visualize then
523 report_visualizers("visualize using method %a",method)
524 end
525 ctx_fallback(content,1,settings)
526 end
527 end
528end
529
530visualizers.visualize = visualize
531visualizers.getvisualizer = getvisualizer
532
533local fallbacks = { } table.setmetatableindex(fallbacks,function(t,k) local v = { nature = k } t[k] = v return v end)
534
535local function checkedsettings(settings,nature)
536 if not settings then
537
538 return fallbacks[nature]
539 else
540 if not settings.nature then
541 settings.nature = nature
542 end
543 return settings
544 end
545end
546
547function visualizers.visualizestring(content,settings)
548 visualize(content,checkedsettings(settings,"inline"))
549end
550
551function visualizers.visualizefile(name,settings)
552 visualize(resolvers.loadtexfile(name),checkedsettings(settings,"display"))
553end
554
555function visualizers.visualizebuffer(name,settings)
556 visualize(buffers.getcontent(name),checkedsettings(settings,"display"))
557end
558
559
560
561local space = C(patterns.space) * CargOne / f_space
562local newline = C(patterns.newline) * CargOne / f_newline
563local emptyline = C(patterns.emptyline) * CargOne / f_emptyline
564local beginline = C(patterns.beginline) * CargOne / f_beginline
565local anything = C(patterns.somecontent) * CargOne / f_default
566
567
568local verbosed = (space + newline * (emptyline^0) * beginline + newline * emptyline + newline + anything)^0
569
570local function write(s,settings)
571 lpegmatch(verbosed,s,1,settings or false)
572end
573
574visualizers.write = write
575visualizers.writenewline = f_newline
576visualizers.writeemptyline = f_emptyline
577visualizers.writespace = f_space
578visualizers.writedefault = f_default
579
580function visualizers.writeargument(...)
581 context("{")
582 write(...)
583 context("}")
584end
585
586
587
588local function realign(lines,strip)
589 local n
590 if strip == v_yes then
591 n = 0xFFFF
592 for i=1, #lines do
593 local spaces = find(lines[i],"%S")
594 if not spaces then
595
596 elseif spaces == 0 then
597 n = 0
598 break
599 elseif spaces < n then
600 n = spaces
601 end
602 end
603 n = n - 1
604 else
605 n = tonumber(strip)
606 end
607 if n and n > 0 then
608 local copy = { }
609 for i=1,#lines do
610 copy[i] = sub(lines[i],n+1)
611 end
612 return copy
613 end
614 return lines
615end
616
617local onlyspaces = S(" \t\f\n\r")^0 * P(-1)
618
619local function getstrip(lines,first,last)
620 if not first then
621 first = 1
622 end
623 if not last then
624 last = #lines
625 end
626 for i=first,last do
627 local li = lines[i]
628 if #li == 0 or lpegmatch(onlyspaces,li) then
629 first = first + 1
630 else
631 break
632 end
633 end
634 for i=last,first,-1 do
635 local li = lines[i]
636 if #li == 0 or lpegmatch(onlyspaces,li) then
637 last = last - 1
638 else
639 break
640 end
641 end
642 return first, last, last - first + 1
643end
644
645
646
647
648
649
650
651
652
653
654local comment = "^[%%%-#]"
655
656local function getrange(lines,first,last,range)
657 local noflines = #lines
658 local first = first or 1
659 local last = last or noflines
660 if last < 0 then
661 last = noflines + last
662 end
663 local what = settings_to_array(range)
664 local r_first = what[1]
665 local r_last = what[2]
666 local f = tonumber(r_first)
667 local l = tonumber(r_last)
668 if r_first then
669 if f then
670 if f > first then
671 first = f
672 end
673 elseif r_first == "=" then
674 for i=first,last do
675 if find(lines[i],comment) then
676 first = i + 1
677 else
678 break
679 end
680 end
681 elseif r_first ~= "" then
682 local exact, r_first = match(r_first,"^([=]?)(.*)")
683 for i=first,last do
684 if find(lines[i],r_first) then
685 if exact == "=" then
686 first = i
687 else
688 first = i + 1
689 end
690 break
691 else
692 first = i
693 end
694 end
695 end
696 end
697 if r_last then
698 if l then
699 if l < 0 then
700 l = noflines + l
701 end
702 if find(r_last,"^[%+]") then
703 l = first + l
704 end
705 if l < last then
706 last = l
707 end
708 elseif r_first == "=" then
709 for i=first,last do
710 if find(lines[i],comment) then
711 break
712 else
713 last = i
714 end
715 end
716 elseif r_last ~= "" then
717 local exact, r_last = match(r_last,"^([=]?)(.*)")
718 for i=first,last do
719 if find(lines[i],r_last) then
720 if exact == "=" then
721 last = i
722 end
723 break
724 else
725 last = i
726 end
727 end
728 end
729 end
730 return first, last
731end
732
733local tablength = 7
734
735local function dotabs(content,settings)
736 local tab = settings.tab
737 tab = tab and (tab == v_yes and tablength or tonumber(tab))
738 if tab then
739 return tabtospace(content,tab)
740 else
741 return content
742 end
743end
744
745local function filter(lines,settings)
746 local strip = settings.strip
747
748 if strip ~= v_no and strip ~= false then
749 lines = realign(lines,strip)
750 end
751 local line = 0
752 local n = 0
753 local range = settings.range
754 local first, last, m = getstrip(lines)
755 if range then
756 first, last = getrange(lines,first,last,range)
757 first, last = getstrip(lines,first,last)
758 end
759
760 local content = concat(lines,(settings.nature == "inline" and " ") or "\n",first,last)
761 return content, m
762end
763
764local getlines = buffers.getlines
765
766
767
768local function typebuffer(settings)
769 local lines = getlines(settings.name)
770 if lines then
771 ctx_displayverbatiminitialize(#lines)
772 local content, m = filter(lines,settings)
773 if content and content ~= "" then
774
775 content = dotabs(content,settings)
776 visualize(content,checkedsettings(settings,"display"))
777 end
778 end
779end
780
781local function processbuffer(settings)
782 local lines = getlines(settings.name)
783 if lines then
784 local content, m = filter(lines,settings)
785 if content and content ~= "" then
786 content = dotabs(content,settings)
787 visualize(content,checkedsettings(settings,"direct"))
788 end
789 end
790end
791
792
793
794
795
796
797
798
799
800
801
802local fences = S([[[{]])
803local symbols = S([[!#"$%&'*()+,-./:;<=>?@[]^_`{|}~0123456789]])
804local space = S([[ ]])
805local backslash = S([[\]])
806local nospace = space^1/""
807local endstring = P(-1)
808
809local compactors = {
810 [v_all] = Cs((backslash * (1-backslash-space)^1 * nospace * (endstring + fences + #backslash) + 1)^0),
811 [v_absolute] = Cs((backslash * (1-symbols -space)^1 * nospace * (symbols + backslash ) + 1)^0),
812 [v_last] = Cs((space^1 * endstring/"" + 1)^0),
813}
814
815local function typestring(settings)
816 local content = settings.data
817 if content and content ~= "" then
818 local compact = settings.compact
819 local compactor = compact and compactors[compact]
820 if compactor then
821 content = lpegmatch(compactor,content) or content
822 end
823
824
825 visualize(content,checkedsettings(settings,"inline"))
826 end
827end
828
829local function typefile(settings)
830 local filename = settings.name
831 local foundname = resolvers.findtexfile(filename)
832 if foundname and foundname ~= "" then
833 local str = resolvers.loadtexfile(foundname)
834 if str and str ~= "" then
835 local regime = settings.regime
836 if regime and regime ~= "" then
837 str = regimes.translate(str,regime)
838 end
839 if str and str~= "" then
840
841 local lines = splitlines(str)
842 local content, m = filter(lines,settings)
843 if content and content ~= "" then
844 content = dotabs(content,settings)
845 visualize(content,checkedsettings(settings,"display"))
846 end
847 end
848 end
849 end
850end
851
852implement {
853 name = "type",
854 actions = typestring,
855 arguments = {
856 {
857 { "data" },
858 { "tab" },
859 { "method" },
860 { "compact" },
861 { "nature" },
862 { "escape" },
863 }
864 }
865}
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911implement {
912 name = "processbuffer",
913 actions = processbuffer,
914 arguments = {
915 {
916 { "name" },
917 { "strip" },
918 { "tab" },
919 { "method" },
920 { "nature" },
921 }
922 }
923}
924
925implement {
926 name = "typebuffer",
927 actions = typebuffer,
928 arguments = {
929 {
930 { "name" },
931 { "strip" },
932 { "range" },
933 { "regime" },
934 { "tab" },
935 { "method" },
936 { "escape" },
937 { "nature" },
938 }
939 }
940}
941
942implement {
943 name = "typefile",
944 actions = typefile,
945 arguments = {
946 {
947 { "name" },
948 { "strip" },
949 { "range" },
950 { "regime" },
951 { "tab" },
952 { "method" },
953 { "escape" },
954 { "nature" },
955 }
956 }
957}
958
959implement {
960 name = "doifelsevisualizer",
961 actions = { visualizers.getspecification, commands.doifelse },
962 arguments = "string"
963}
964
965implement {
966 name = "loadvisualizer",
967 actions = visualizers.load,
968 arguments = "string"
969}
970 |