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