1if not modules then modules = { } end modules ['trac-lmx'] = {
2 version = 1.002,
3 comment = "companion to trac-lmx.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
11local type, tostring, rawget, loadstring, pcall = type, tostring, rawget, loadstring, pcall
12local format, sub, gsub = string.format, string.sub, string.gsub
13local concat = table.concat
14local collapsespaces = string.collapsespaces
15local P, Cc, Cs, C, Carg, lpegmatch = lpeg.P, lpeg.Cc, lpeg.Cs, lpeg.C, lpeg.Carg, lpeg.match
16local joinpath, replacesuffix, pathpart, filesuffix = file.join, file.replacesuffix, file.pathpart, file.suffix
17
18local allocate = utilities.storage.allocate
19local setmetatableindex = table.setmetatableindex
20
21
22local trace_variables = false trackers .register("lmx.variables", function(v) trace_variables = v end)
23
24local cache_templates = true directives.register("lmx.cache.templates",function(v) cache_templates = v end)
25local cache_files = true directives.register("lmx.cache.files", function(v) cache_files = v end)
26
27local report_lmx = logs.reporter("lmx")
28local report_error = logs.reporter("lmx","error")
29
30lmx = lmx or { }
31local lmx = lmx
32
33
34
35
36local lmxvariables = {
37 ['title-default'] = 'ConTeXt LMX File',
38 ['color-background-green'] = '#4F6F6F',
39 ['color-background-blue'] = '#6F6F8F',
40 ['color-background-yellow'] = '#8F8F6F',
41 ['color-background-purple'] = '#8F6F8F',
42 ['color-background-body'] = '#808080',
43 ['color-background-main'] = '#3F3F3F',
44}
45
46local lmxinherited = {
47 ['title'] = 'title-default',
48 ['color-background-one'] = 'color-background-green',
49 ['color-background-two'] = 'color-background-blue',
50 ['color-background-three'] = 'color-background-one',
51 ['color-background-four'] = 'color-background-two',
52}
53
54lmx.variables = lmxvariables
55lmx.inherited = lmxinherited
56
57setmetatableindex(lmxvariables,function(t,k)
58 k = lmxinherited[k]
59 while k do
60 local v = rawget(lmxvariables,k)
61 if v then
62 return v
63 end
64 k = lmxinherited[k]
65 end
66end)
67
68function lmx.set(key,value)
69 lmxvariables[key] = value
70end
71
72function lmx.get(key)
73 return lmxvariables[key] or ""
74end
75
76lmx.report = report_lmx
77
78
79
80
81
82
83
84local variables = { }
85local result = { }
86
87local function do_print(one,two,...)
88 if two then
89 result[#result+1] = concat { one, two, ... }
90 else
91 result[#result+1] = one
92 end
93end
94
95
96
97
98
99local html = { }
100lmx.html = html
101
102function html.td(str)
103 if type(str) == "table" then
104 for i=1,#str do
105 str[i] = format("<td>%s</td>",str[i] or "")
106 end
107 result[#result+1] = concat(str)
108 else
109 result[#result+1] = format("<td>%s</td>",str or "")
110 end
111end
112
113function html.th(str)
114 if type(str) == "table" then
115 for i=1,#str do
116 str[i] = format("<th>%s</th>",str[i])
117 end
118 result[#result+1] = concat(str)
119 else
120 result[#result+1] = format("<th>%s</th>",str or "")
121 end
122end
123
124function html.a(text,url)
125 result[#result+1] = format("<a href=%q>%s</a>",url,text)
126end
127
128setmetatableindex(html,function(t,k)
129 local f = format("<%s>%%s</%s>",k,k)
130 local v = function(str) result[#result+1] = format(f,str or "") end
131 t[k] = v
132 return v
133end)
134
135
136
137local function loadedfile(name)
138 name = resolvers and resolvers.findfile and resolvers.findfile(name) or name
139 local data = io.loaddata(name)
140 if not data or data == "" then
141 report_lmx("file %a is empty",name)
142 end
143 return data
144end
145
146local function loadedsubfile(name)
147 return io.loaddata(resolvers and resolvers.findfile and resolvers.findfile(name) or name)
148end
149
150lmx.loadedfile = loadedfile
151
152
153
154local usedpaths = { }
155local givenpath = nil
156
157local do_nested_include = nil
158
159local pattern = lpeg.replacer {
160 ["&"] = "&",
161 [">"] = ">",
162 ["<"] = "<",
163 ['"'] = """,
164}
165
166local function do_escape(str)
167 return lpegmatch(pattern,str) or str
168end
169
170local function do_variable(str)
171 local value = variables[str]
172 if not trace_variables then
173
174 elseif type(value) == "string" then
175 if #value > 80 then
176 report_lmx("variable %a is set to: %s ...",str,collapsespaces(sub(value,1,80)))
177 else
178 report_lmx("variable %a is set to: %s",str,collapsespaces(value))
179 end
180 elseif type(value) == "nil" then
181 report_lmx("variable %a is set to: %s",str,"<!-- unset -->")
182 else
183 report_lmx("variable %a is set to: %S",str,value)
184 end
185 if type(value) == "function" then
186 return value(str)
187 else
188 return value
189 end
190end
191
192local function do_type(str)
193 if str and str ~= "" then
194 result[#result+1] = format("<tt>%s</tt>",do_escape(str))
195 end
196end
197
198local function do_fprint(str,...)
199 if str and str ~= "" then
200 result[#result+1] = format(str,...)
201 end
202end
203
204local function do_eprint(str,...)
205 if str and str ~= "" then
206 result[#result+1] = lpegmatch(pattern,format(str,...))
207 end
208end
209
210local function do_print_variable(str)
211 local str = do_variable(str)
212 if str and str ~= "" then
213 result[#result+1] = str
214 end
215end
216
217local function do_type_variable(str)
218 local str = do_variable(str)
219 if str and str ~= "" then
220 result[#result+1] = format("<tt>%s</tt>",do_escape(str))
221 end
222end
223
224local function do_include(filename,option)
225 local data = loadedsubfile(filename)
226 if (not data or data == "") and givenpath then
227 data = loadedsubfile(joinpath(givenpath,filename))
228 end
229 if (not data or data == "") and type(usedpaths) == "table" then
230 for i=1,#usedpaths do
231 data = loadedsubfile(joinpath(usedpaths[i],filename))
232 if data and data ~= "" then
233 break
234 end
235 end
236 end
237 if not data or data == "" then
238 data = format("<!-- unknown lmx include file: %s -->",filename)
239 report_lmx("include file %a is empty",filename)
240 else
241
242 data = do_nested_include(data)
243 end
244 if filesuffix(filename,"css") and option == "strip" then
245 data = lmx.stripcss(data)
246 end
247 return data
248end
249
250
251
252lmx.print = do_print
253lmx.type = do_type
254lmx.eprint = do_eprint
255lmx.fprint = do_fprint
256
257lmx.escape = do_escape
258lmx.urlescape = url.escape
259lmx.variable = do_variable
260lmx.include = do_include
261
262lmx.inject = do_print
263lmx.finject = do_fprint
264lmx.einject = do_eprint
265
266lmx.pv = do_print_variable
267lmx.tv = do_type_variable
268
269
270
271function lmx.initialize(d,v)
272 if not v then
273 setmetatableindex(d,lmxvariables)
274 if variables ~= d then
275 setmetatableindex(variables,d)
276 if trace_variables then
277 report_lmx("using chain: variables => given defaults => lmx variables")
278 end
279 elseif trace_variables then
280 report_lmx("using chain: variables == given defaults => lmx variables")
281 end
282 elseif d ~= v then
283 setmetatableindex(v,d)
284 if d ~= lmxvariables then
285 setmetatableindex(d,lmxvariables)
286 if variables ~= v then
287 setmetatableindex(variables,v)
288 if trace_variables then
289 report_lmx("using chain: variables => given variables => given defaults => lmx variables")
290 end
291 elseif trace_variables then
292 report_lmx("using chain: variables == given variables => given defaults => lmx variables")
293 end
294 else
295 if variables ~= v then
296 setmetatableindex(variables,v)
297 if trace_variables then
298 report_lmx("using chain: variabes => given variables => given defaults")
299 end
300 elseif trace_variables then
301 report_lmx("using chain: variables == given variables => given defaults")
302 end
303 end
304 else
305 setmetatableindex(v,lmxvariables)
306 if variables ~= v then
307 setmetatableindex(variables,v)
308 if trace_variables then
309 report_lmx("using chain: variables => given variables => lmx variables")
310 end
311 elseif trace_variables then
312 report_lmx("using chain: variables == given variables => lmx variables")
313 end
314 end
315 result = { }
316end
317
318function lmx.finalized()
319 local collapsed = concat(result)
320 result = { }
321 return collapsed
322end
323
324function lmx.getvariables()
325 return variables
326end
327
328function lmx.reset()
329
330end
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
367
368
369
370
371
372
373local template = [[
374-- interface
375
376local html = lmx.html
377local inject = lmx.print
378local finject = lmx.fprint -- better use the following
379local einject = lmx.eprint -- better use the following
380local injectf = lmx.fprint
381local injecte = lmx.eprint
382local injectfmt = lmx.fprint
383local injectesc = lmx.eprint
384local escape = lmx.escape
385local verbose = lmx.type
386
387local i_n_j_e_c_t = lmx.print
388
389-- shortcuts (sort of obsolete as there is no gain)
390
391local p = lmx.print
392local f = lmx.fprint
393local v = lmx.variable
394local e = lmx.escape
395local t = lmx.type
396local pv = lmx.pv
397local tv = lmx.tv
398
399local lmx_initialize = lmx.initialize
400local lmx_finalized = lmx.finalized
401local lmx_getvariables = lmx.getvariables
402
403-- generator
404
405return function(defaults,variables)
406
407 lmx_initialize(defaults,variables)
408
409 local definitions = { }
410 local variables = lmx_getvariables()
411
412 %s -- the action: appends to result
413
414 return lmx_finalized()
415
416end
417]]
418
419local function savedefinition(definitions,tag,content)
420 definitions[tag] = content
421 return ""
422end
423
424local function getdefinition(definitions,tag)
425 return definitions[tag] or ""
426end
427
428local whitespace = lpeg.patterns.whitespace
429local optionalspaces = whitespace^0
430
431local dquote = P('"')
432
433local begincomment = P("<!--")
434local endcomment = P("-->")
435
436local beginembedxml = P("<?")
437local endembedxml = P("?>")
438
439local beginembedcss = P("/*")
440local endembedcss = P("*/")
441
442local gobbledendxml = (optionalspaces * endembedxml) / ""
443
444local argumentxml = (whitespace^1 + dquote * C((1-dquote)^1) * dquote + C((1-gobbledendxml-whitespace)^1))^0
445
446local gobbledendcss = (optionalspaces * endembedcss) / ""
447
448local argumentcss = (whitespace^1 + dquote * C((1-dquote)^1) * dquote + C((1-gobbledendcss-whitespace)^1))^0
449
450local commentxml = (begincomment * (1-endcomment)^0 * endcomment) / ""
451
452local beginluaxml = (beginembedxml * P("lua")) / ""
453local endluaxml = endembedxml / ""
454
455local luacodexml = beginluaxml
456 * (1-endluaxml)^1
457 * endluaxml
458
459local beginluacss = (beginembedcss * P("lua")) / ""
460local endluacss = endembedcss / ""
461
462local luacodecss = beginluacss
463 * (1-endluacss)^1
464 * endluacss
465
466local othercode = (1-beginluaxml-beginluacss)^1 / " i_n_j_e_c_t[==[%0]==] "
467
468local includexml = ((beginembedxml * P("lmx-include") * optionalspaces) / "")
469 * (argumentxml / do_include)
470 * gobbledendxml
471
472local includecss = ((beginembedcss * P("lmx-include") * optionalspaces) / "")
473 * (argumentcss / do_include)
474 * gobbledendcss
475
476local definexml_b = ((beginembedxml * P("lmx-define-begin") * optionalspaces) / "")
477 * argumentxml
478 * gobbledendxml
479
480local definexml_e = ((beginembedxml * P("lmx-define-end") * optionalspaces) / "")
481 * argumentxml
482 * gobbledendxml
483
484local definexml_c = C((1-definexml_e)^0)
485
486local definexml = (Carg(1) * C(definexml_b) * definexml_c * definexml_e) / savedefinition
487
488local resolvexml = ((beginembedxml * P("lmx-resolve") * optionalspaces) / "")
489 * ((Carg(1) * C(argumentxml)) / getdefinition)
490 * gobbledendxml
491
492local definecss_b = ((beginembedcss * P("lmx-define-begin") * optionalspaces) / "")
493 * argumentcss
494 * gobbledendcss
495
496local definecss_e = ((beginembedcss * P("lmx-define-end") * optionalspaces) / "")
497 * argumentcss
498 * gobbledendcss
499
500local definecss_c = C((1-definecss_e)^0)
501
502local definecss = (Carg(1) * C(definecss_b) * definecss_c * definecss_e) / savedefinition
503
504local resolvecss = ((beginembedcss * P("lmx-resolve") * optionalspaces) / "")
505 * ((Carg(1) * C(argumentcss)) / getdefinition)
506 * gobbledendcss
507
508
509local pattern_1 = Cs((includexml + includecss + P(1))^0)
510local pattern_2 = Cs((definexml + resolvexml + definecss + resolvecss + P(1))^0)
511local pattern_3 = Cs((luacodexml + luacodecss + othercode)^0)
512
513local cache = { }
514
515local function lmxerror(str)
516 report_error(str)
517 return html.tt(str)
518end
519
520local function wrapper(converter,defaults,variables)
521 local outcome, message = pcall(converter,defaults,variables)
522 if not outcome then
523 return lmxerror(format("error in conversion: %s",message))
524 else
525 return message
526 end
527end
528
529do_nested_include = function(data)
530 return lpegmatch(pattern_1,data)
531end
532
533local function lmxnew(data,defaults,nocache,path)
534 data = data or ""
535 local known = cache[data]
536 if not known then
537 givenpath = path
538 usedpaths = lmxvariables.includepath or { }
539 if type(usedpaths) == "string" then
540 usedpaths = { usedpaths }
541 end
542 data = lpegmatch(pattern_1,data)
543 data = lpegmatch(pattern_2,data,1,{})
544 data = lpegmatch(pattern_3,data)
545 local converted = loadstring(format(template,data))
546 if converted then
547 converted = converted()
548 end
549 defaults = defaults or { }
550 local converter
551 if converted then
552 converter = function(variables)
553 return wrapper(converted,defaults,variables)
554 end
555 else
556 report_error("error in:\n%s\n:",data)
557 converter = function() lmxerror("error in template") end
558 end
559 known = {
560 data = defaults.trace and data or "",
561 variables = defaults,
562 converter = converter,
563 }
564 if cache_templates and nocache ~= false then
565 cache[data] = known
566 end
567 elseif variables then
568 known.variables = variables
569 end
570 return known, known.variables
571end
572
573local function lmxresult(self,variables)
574 if self then
575 local converter = self.converter
576 if converter then
577 local converted = converter(variables)
578 if trace_variables then
579 report_lmx("converted size: %s",#converted)
580 end
581 return converted or lmxerror("no result from converter")
582 else
583 return lmxerror("invalid converter")
584 end
585 else
586 return lmxerror("invalid specification")
587 end
588end
589
590lmx.new = lmxnew
591lmx.result = lmxresult
592
593local loadedfiles = { }
594
595function lmx.convertstring(templatestring,variables,nocache,path)
596 return lmxresult(lmxnew(templatestring,nil,nocache,path),variables)
597end
598
599function lmx.convertfile(templatefile,variables,nocache)
600 if trace_variables then
601 report_lmx("converting file %a",templatefile)
602 end
603 local converter = loadedfiles[templatefile]
604 if not converter then
605 converter = lmxnew(loadedfile(templatefile),nil,nocache,pathpart(templatefile))
606 loadedfiles[templatefile] = converter
607 end
608 return lmxresult(converter,variables)
609end
610
611local function lmxconvert(templatefile,resultfile,variables,nocache)
612 if trace_variables then
613 report_lmx("converting file %a",templatefile)
614 end
615 if not variables and type(resultfile) == "table" then
616 variables = resultfile
617 end
618 local converter = loadedfiles[templatefile]
619 if not converter then
620 converter = lmxnew(loadedfile(templatefile),nil,nocache,pathpart(templatefile))
621 if cache_files then
622 loadedfiles[templatefile] = converter
623 end
624 end
625 local result = lmxresult(converter,variables)
626 if resultfile then
627 io.savedata(resultfile,result)
628 else
629 return result
630 end
631end
632
633lmx.convert = lmxconvert
634
635
636
637local nocomment = (beginembedcss * (1 - endembedcss)^1 * endembedcss) / ""
638local nowhitespace = whitespace^1 / " "
639local semistripped = whitespace^1 / "" * P(";")
640local stripper = Cs((nocomment + semistripped + nowhitespace + 1)^1)
641
642function lmx.stripcss(str)
643 return lpegmatch(stripper,str)
644end
645
646function lmx.color(r,g,b,a)
647 if r > 1 then
648 r = 1
649 end
650 if g > 1 then
651 g = 1
652 end
653 if b > 1 then
654 b = 1
655 end
656 if not a then
657 a= 0
658 elseif a > 1 then
659 a = 1
660 end
661 if a > 0 then
662 return format("rgba(%s%%,%s%%,%s%%,%s)",r*100,g*100,b*100,a)
663 else
664 return format("rgb(%s%%,%s%%,%s%%)",r*100,g*100,b*100)
665 end
666end
667
668
669
670lmx.lmxfile = string.itself
671lmx.htmfile = string.itself
672lmx.popupfile = os.launch
673
674local function lmxmake(name,variables)
675 local lmxfile = lmx.lmxfile(name)
676 local htmfile = lmx.htmfile(name)
677 if lmxfile == htmfile then
678 htmfile = replacesuffix(lmxfile,"html")
679 end
680 lmxconvert(lmxfile,htmfile,variables)
681 return htmfile
682end
683
684lmx.make = lmxmake
685
686function lmx.show(name,variables)
687
688 local htmfile = lmxmake(name,variables)
689
690 return htmfile
691end
692
693
694
695if arg then
696 if arg[1] == "--show" then if arg[2] then lmx.show (arg[2]) end
697 elseif arg[1] == "--convert" then if arg[2] then lmx.convert(arg[2], arg[3] or "temp.html") end
698 end
699else
700 return lmx
701end
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736 |