trac-fil.lua /size: 5313 b    last modification: 2021-10-28 13:50
1if not modules then modules = { } end modules ['trac-fil'] = {
2    version   = 1.001,
3    comment   = "for the moment for myself",
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 rawset, tonumber, type, pcall, next = rawset, tonumber, type, pcall, next
10local format, concat = string.format, table.concat
11local openfile = io.open
12local date = os.date
13local sortedpairs = table.sortedpairs
14
15local P, C, Cc, Cg, Cf, Ct, Cs, Carg = lpeg.P, lpeg.C, lpeg.Cc, lpeg.Cg, lpeg.Cf, lpeg.Ct, lpeg.Cs, lpeg.Carg
16local lpegmatch = lpeg.match
17
18local patterns   = lpeg.patterns
19local cardinal   = patterns.cardinal
20local whitespace = patterns.whitespace^0
21
22local timestamp = Cf(Ct("") * (
23      Cg (Cc("year")    * (cardinal/tonumber)) * P("-")
24    * Cg (Cc("month")   * (cardinal/tonumber)) * P("-")
25    * Cg (Cc("day")     * (cardinal/tonumber)) * P(" ")
26    * Cg (Cc("hour")    * (cardinal/tonumber)) * P(":")
27    * Cg (Cc("minute")  * (cardinal/tonumber)) * P(":")
28    * Cg (Cc("second")  * (cardinal/tonumber)) * P("+")
29    * Cg (Cc("thour")   * (cardinal/tonumber)) * P(":")
30    * Cg (Cc("tminute") * (cardinal/tonumber))
31)^0, rawset)
32
33local keysvalues = Cf(Ct("") * (
34    Cg(C(patterns.letter^0) * whitespace * "=" * whitespace * Cs(patterns.unquoted) * whitespace)
35)^0, rawset)
36
37local statusline = Cf(Ct("") * (
38      whitespace * P("[") * Cg(Cc("timestamp") * timestamp ) * P("]")
39    * whitespace *          Cg(Cc("status"   ) * keysvalues)
40),rawset)
41
42patterns.keysvalues = keysvalues
43patterns.statusline = statusline
44patterns.timestamp  = timestamp
45
46loggers = loggers or { }
47
48local timeformat = format("[%%s%s]",os.timezone())
49local dateformat = "!%Y-%m-%d %H:%M:%S"
50
51function loggers.makeline(t)
52    local result = { } -- minimize time that file is open
53    result[#result+1] = format(timeformat,date(dateformat))
54    for k, v in sortedpairs(t) do
55        local tv = type(v)
56        if tv == "string" then
57            if v ~= "password" then
58                result[#result+1] = format(" %s=%q",k,v)
59            end
60        elseif tv == "number" or tv == "boolean" then
61            result[#result+1] = format(" %s=%q",k,tostring(v))
62        end
63    end
64    return concat(result," ")
65end
66
67local function append(filename,...)
68    local f = openfile(filename,"a+")
69    if not f then
70        dir.mkdirs(file.dirname(filename))
71        f = openfile(filename,"a+")
72    end
73    if f then
74        f:write(...)
75        f:close()
76        return true
77    else
78        return false
79    end
80end
81
82function loggers.store(filename,data) -- a log service is nicer
83    if type(data) == "table"then
84        data = loggers.makeline(data)
85    end
86    pcall(append,filename,data,"\n")
87end
88
89function loggers.collect(filename,result)
90    if lfs.isfile(filename) then
91        local r = lpegmatch(Ct(statusline^0),io.loaddata(filename))
92        if result then -- append
93            local nofresult = #result
94            for i=1,#r do
95                nofresult = nofresult + 1
96                result[nofresult] = r[i]
97            end
98            return result
99        else
100            return r
101        end
102    else
103        return result or { }
104    end
105end
106
107function loggers.fields(results) -- returns hash of fields with counts so that we can decide on importance
108    local fields = { }
109    if results then
110        for i=1,#results do
111            local r = results[i]
112            for k, v in next, r do
113                local f = fields[k]
114                if not f then
115                    fields[k] = 1
116                else
117                    fields[k] = f + 1
118                end
119            end
120        end
121    end
122    return fields
123end
124
125local template = [[<!-- log entries: begin --!>
126<table>
127<tr>%s</tr>
128%s
129</table>
130<!-- log entries: end --!>
131]]
132
133function loggers.tohtml(entries,fields)
134    if not fields or #fields == 0 then
135        return ""
136    end
137    if type(entries) == "string" then
138        entries = loggers.collect(entries)
139    end
140    local scratch, lines = { }, { }
141    for i=1,#entries do
142        local entry = entries[i]
143        local status = entry.status
144        for i=1,#fields do
145            local field = fields[i]
146            local v = status[field.name]
147            if v ~= nil then
148                v = tostring(v)
149                local f = field.format
150                if f then
151                    v = format(f,v)
152                end
153                scratch[i] = format("<td nowrap='nowrap' align='%s'>%s</td>",field.align or "left",v)
154            else
155                scratch[i] = "<td/>"
156            end
157        end
158        lines[i] = format("<tr>%s</tr>",concat(scratch))
159    end
160    for i=1,#fields do
161        local field = fields[i]
162        scratch[i] = format("<th nowrap='nowrap' align='left'>%s</th>", field.label or field.name)
163    end
164    local result = format(template,concat(scratch),concat(lines,"\n"))
165    return result, entries
166end
167
168-- loggers.store("test.log", { name = "whatever", more = math.random(1,100) })
169
170-- local fields = {
171--     { name = "name", align = "left"  },
172--     { name = "more", align = "right" },
173-- }
174
175-- local entries = loggers.collect("test.log")
176-- local html    = loggers.tohtml(entries,fields)
177
178-- inspect(entries)
179-- inspect(fields)
180-- inspect(html)
181
182