Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/src/resources/pandoc/datadir/luacov/reporter/html.lua
12923 views
1
local luacov_reporter = require("luacov.reporter")
2
3
local reporter = {}
4
5
local HTML_HEADER, HTML_FOOTER, HTML_TOTAL, HTML_FILE_HEADER, HTML_FILE_FOOTER, HTML_LINE_HIT, HTML_LINE_MIS
6
7
local function parse_template(template, values)
8
local content = template:gsub("{{([a-z_]+)}}", function(key)
9
return values[key]
10
end)
11
return content
12
end
13
14
----------------------------------------------------------------
15
--- parse template
16
do
17
local dir = string.gsub(debug.getinfo(1).source, "^@(.+/)[^/]+$", "%1")
18
local dir_sep = package.config:sub(1, 1)
19
if not dir_sep:find("[/\\]") then
20
dir_sep = "/"
21
end
22
local template = require("luacov.reporter.html.template")
23
24
--- Removes a prefix from a string if it's present.
25
-- @param str a string.
26
-- @param prefix a prefix string.
27
-- @return original string if does not start with prefix
28
-- or string without prefix.
29
local function unprefix(str, prefix)
30
if str:sub(1, #prefix) == prefix then
31
return str:sub(#prefix + 1)
32
else
33
return str
34
end
35
end
36
37
-- Returns contents of a file or nil + error message.
38
local function read_asset(name)
39
local f, open_err = io.open(dir .. "html" .. dir_sep .. name, "rb")
40
41
if not f then
42
error(unprefix(open_err, name .. ": "))
43
end
44
45
local contents, read_err = f:read("*a")
46
f:close()
47
48
if contents then
49
return contents
50
else
51
error(read_err)
52
end
53
end
54
55
local asset_types = {
56
script = template.SCRIPT,
57
style = template.STYLE,
58
}
59
60
local assets_content = {}
61
for tag, assets in pairs(asset_types) do
62
for _, name in ipairs(assets) do
63
local content = read_asset(name)
64
if (not assets_content[tag]) then
65
assets_content[tag] = ""
66
end
67
if (tag == "script") then
68
assets_content[tag] = assets_content[tag] .. "\n <script type=\"text/javascript\">\n "
69
else
70
assets_content[tag] = assets_content[tag] .. "\n <" .. tag .. ">\n "
71
end
72
assets_content[tag] = assets_content[tag] .. content:gsub("\n", "\n ") .. "\n </" .. tag .. ">\n"
73
end
74
end
75
76
HTML_HEADER = parse_template(template.HTML_HEADER, {
77
style = assets_content.style
78
})
79
80
HTML_FOOTER = parse_template(template.HTML_FOOTER, {
81
script = assets_content.script,
82
timestamp = os.date("%Y-%m-%d %H:%M:%S", os.time())
83
})
84
85
HTML_TOTAL = template.HTML_TOTAL
86
HTML_FILE_HEADER = template.HTML_FILE_HEADER
87
HTML_FILE_FOOTER = template.HTML_FILE_FOOTER
88
HTML_LINE_HIT = template.HTML_LINE_HIT
89
HTML_LINE_MIS = template.HTML_LINE_MIS
90
end
91
----------------------------------------------------------------
92
93
--- Encodes the HTML entities in a string. Helpfull to avoid XSS.
94
-- @param s (String) String to escape.
95
local function escape_html(s)
96
return (string.gsub(s, "[}{\">/<'&]", {
97
["&"] = "&amp;",
98
["<"] = "&lt;",
99
[">"] = "&gt;",
100
['"'] = "&quot;",
101
["'"] = "&#39;",
102
["/"] = "&#47;"
103
}))
104
end
105
106
local HtmlReporter = setmetatable({}, luacov_reporter.ReporterBase)
107
do
108
HtmlReporter.__index = HtmlReporter
109
110
function HtmlReporter:on_start()
111
self._summary = {}
112
self:write(HTML_HEADER)
113
end
114
115
local HTML = ""
116
local FILE_HTML
117
118
local function write_to_html(content)
119
HTML = HTML .. content
120
end
121
122
local function write_to_file_html(template, values)
123
FILE_HTML = FILE_HTML .. parse_template(template, values)
124
end
125
126
local function coverage_to_string(hits, missed)
127
local total = hits + missed
128
129
if total == 0 then
130
total = 1
131
end
132
133
return ("%.2f"):format(hits / total * 100.0)
134
end
135
136
local function coverage_to_number(hits, missed)
137
return tonumber(coverage_to_string(hits, missed))
138
end
139
140
local function filename_to_id(filename)
141
return filename:lower():gsub("(.lua)$", ""):gsub("([^a-z0-9_]+)", function(_key)
142
return "-"
143
end)
144
end
145
146
local function coverage_to_css_class(hits, missed)
147
local coverageNum = coverage_to_number(hits, missed)
148
local cssClass
149
if (coverageNum < 40) then
150
cssClass = "danger"
151
elseif (coverageNum < 60) then
152
cssClass = "warning"
153
elseif (coverageNum < 75) then
154
cssClass = "success-low"
155
elseif (coverageNum < 90) then
156
cssClass = "success-medium"
157
else
158
cssClass = "success-high"
159
end
160
return cssClass
161
end
162
163
--luacheck: no self
164
function HtmlReporter:on_new_file(_filename)
165
FILE_HTML = ""
166
end
167
168
function HtmlReporter:on_file_error(filename, error_type, message)
169
io.stderr:write(("Couldn't %s %s: %s\n"):format(error_type, filename, message))
170
end
171
172
function HtmlReporter:on_empty_line(_filename, _lineno, line)
173
if line == "" then
174
FILE_HTML = FILE_HTML .. "\n"
175
else
176
FILE_HTML = FILE_HTML .. escape_html(line) .. "\n"
177
end
178
end
179
180
function HtmlReporter:on_mis_line(_filename, lineno, line)
181
write_to_file_html(HTML_LINE_MIS, {
182
line = escape_html(line),
183
lineno = lineno,
184
})
185
end
186
187
function HtmlReporter:on_hit_line(_filename, lineno, line, hits)
188
write_to_file_html(HTML_LINE_HIT, {
189
hits = hits,
190
line = escape_html(line),
191
lineno = lineno,
192
})
193
end
194
195
function HtmlReporter:on_end_file(filename, hits, miss)
196
197
local coverage = coverage_to_string(hits, miss)
198
199
write_to_html(parse_template(HTML_FILE_HEADER, {
200
id = filename_to_id(filename),
201
hits = hits,
202
miss = miss,
203
coverage = coverage,
204
css_class = coverage_to_css_class(hits, miss),
205
filename = filename
206
}))
207
208
write_to_file_html(HTML_FILE_FOOTER, {
209
hits = hits,
210
miss = miss,
211
coverage = coverage,
212
filename = filename,
213
})
214
write_to_html(FILE_HTML)
215
self._summary[filename] = { hits = hits, miss = miss }
216
end
217
218
function HtmlReporter:on_end()
219
local total_hits, total_missed = 0, 0
220
221
for _, filename in ipairs(self:files()) do
222
local summary = self._summary[filename]
223
if summary then
224
local hits, missed = summary.hits, summary.miss
225
total_hits = total_hits + hits
226
total_missed = total_missed + missed
227
end
228
end
229
230
self:write(parse_template(HTML_TOTAL, {
231
hits = total_hits,
232
miss = total_missed,
233
css_class = coverage_to_css_class(total_hits, total_missed),
234
coverage = tonumber(coverage_to_string(total_hits, total_missed)),
235
}))
236
self:write(HTML)
237
self:write(HTML_FOOTER)
238
end
239
end
240
----------------------------------------------------------------
241
242
function reporter.report()
243
return luacov_reporter.report(HtmlReporter)
244
end
245
246
return reporter
247
248