cover_rage 1.1.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/cover_rage/record.rb +8 -1
- data/lib/cover_rage/recorder.rb +5 -1
- data/lib/cover_rage/reporters/html_reporter/index.html.erb +145 -139
- data/lib/cover_rage/stores/sqlite.rb +12 -9
- metadata +9 -51
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 00ad4585a873585456f2c9948a88bcab8d54438b5c952a7fdb03a0e411763e3e
|
|
4
|
+
data.tar.gz: 231662f71294e8e983e7632c8ad530b3c18752f2e6d4d0a5d2ea3ea2de0edc90
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '048eb40f6ed0df7c75e4f353ce17fa516345a2832d6b25497453d4bb94aa302197e8e57c5fdbe50444d3a35da91bf641d1a0bbbd2b8d911cf457988016588932'
|
|
7
|
+
data.tar.gz: 587134f993d40afb1c4b9242e030aac02ea886436a1e3912413630f1e7f4e97a403b1b2693a4af8907df23e9a4a8f91e7fa2f782089fa7609fe997ed6e45fa88
|
data/lib/cover_rage/record.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module CoverRage
|
|
4
|
-
Record = Data.define(:path, :revision, :source, :execution_count) do
|
|
4
|
+
Record = Data.define(:path, :revision, :source, :execution_count, :last_executed_at) do
|
|
5
5
|
def self.merge(existing, current)
|
|
6
6
|
records_to_save = []
|
|
7
7
|
current.each do |record|
|
|
@@ -20,6 +20,13 @@ module CoverRage
|
|
|
20
20
|
with(
|
|
21
21
|
execution_count: execution_count.map.with_index do |item, index|
|
|
22
22
|
item.nil? ? nil : item + other.execution_count[index]
|
|
23
|
+
end,
|
|
24
|
+
last_executed_at: last_executed_at.map.with_index do |item, index|
|
|
25
|
+
if item.nil? && other.last_executed_at[index].nil? then nil
|
|
26
|
+
elsif item.nil? then other.last_executed_at[index]
|
|
27
|
+
elsif other.last_executed_at[index].nil? then item
|
|
28
|
+
else [item, other.last_executed_at[index]].max
|
|
29
|
+
end
|
|
23
30
|
end
|
|
24
31
|
)
|
|
25
32
|
end
|
data/lib/cover_rage/recorder.rb
CHANGED
|
@@ -42,11 +42,15 @@ module CoverRage
|
|
|
42
42
|
relative_path = filepath.delete_prefix(@path_prefix)
|
|
43
43
|
revision, source = read_file_with_revision(filepath)
|
|
44
44
|
|
|
45
|
+
now = Time.now.to_i
|
|
46
|
+
last_executed_at = execution_count.map { |c| c&.positive? ? now : nil }
|
|
47
|
+
|
|
45
48
|
records << Record.new(
|
|
46
49
|
path: relative_path,
|
|
47
50
|
revision:,
|
|
48
51
|
source:,
|
|
49
|
-
execution_count
|
|
52
|
+
execution_count:,
|
|
53
|
+
last_executed_at:
|
|
50
54
|
)
|
|
51
55
|
end
|
|
52
56
|
return unless records.any?
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
|
-
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
|
6
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
6
|
<title>CoverRage</title>
|
|
8
7
|
<link
|
|
@@ -15,43 +14,41 @@
|
|
|
15
14
|
display: inline-block;
|
|
16
15
|
counter-increment: line_number;
|
|
17
16
|
}
|
|
18
|
-
|
|
19
|
-
.
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
.red {
|
|
24
|
-
background-color: rgb(255, 212, 212);
|
|
25
|
-
}
|
|
26
|
-
|
|
17
|
+
.green { background-color: rgb(221, 255, 221); }
|
|
18
|
+
.blue { background-color: rgb(221, 221, 255); }
|
|
19
|
+
.red { background-color: rgb(255, 212, 212); }
|
|
27
20
|
.number {
|
|
28
21
|
display: inline-block;
|
|
29
22
|
text-align: right;
|
|
30
|
-
margin-right: 0.5em;
|
|
31
23
|
background-color: lightgray;
|
|
32
24
|
padding: 0 0.5em 0 1.5em;
|
|
33
25
|
}
|
|
34
|
-
|
|
26
|
+
.timestamp {
|
|
27
|
+
display: inline-block;
|
|
28
|
+
text-align: right;
|
|
29
|
+
margin-right: 0.5em;
|
|
30
|
+
background-color: lightgray;
|
|
31
|
+
padding: 0 0.5em;
|
|
32
|
+
}
|
|
35
33
|
.nav {
|
|
36
34
|
display: flex;
|
|
37
35
|
list-style: none;
|
|
38
36
|
padding-left: 0;
|
|
39
|
-
|
|
40
|
-
.nav > * {
|
|
41
|
-
margin-left: 8px;
|
|
37
|
+
gap: 8px;
|
|
42
38
|
}
|
|
43
39
|
</style>
|
|
44
40
|
</head>
|
|
45
41
|
<body>
|
|
46
42
|
<main id="main"></main>
|
|
47
43
|
|
|
48
|
-
<
|
|
49
|
-
<div
|
|
44
|
+
<template id="tmpl-index">
|
|
45
|
+
<div>
|
|
50
46
|
<nav>
|
|
51
47
|
<ul class="nav">
|
|
52
48
|
<li><a href="#/">Index</a></li>
|
|
53
49
|
</ul>
|
|
54
50
|
</nav>
|
|
51
|
+
<label>stale threshold <input id="stale-threshold" type="number" min="0" value="30"> days</label>
|
|
55
52
|
<table>
|
|
56
53
|
<thead>
|
|
57
54
|
<tr>
|
|
@@ -60,156 +57,165 @@
|
|
|
60
57
|
<th>relevancy</th>
|
|
61
58
|
<th>hit</th>
|
|
62
59
|
<th>miss</th>
|
|
63
|
-
<th>
|
|
60
|
+
<th>stale</th>
|
|
61
|
+
<th class="sortable" data-sort="coverage" style="cursor:pointer">coverage (%) ▼</th>
|
|
62
|
+
<th class="sortable" data-sort="staleness" style="cursor:pointer">staleness (%)</th>
|
|
64
63
|
</tr>
|
|
65
64
|
</thead>
|
|
66
65
|
<tbody></tbody>
|
|
67
66
|
</table>
|
|
68
67
|
</div>
|
|
69
|
-
|
|
68
|
+
</template>
|
|
69
|
+
|
|
70
|
+
<template id="tmpl-file">
|
|
71
|
+
<div>
|
|
70
72
|
<nav>
|
|
71
73
|
<ul class="nav">
|
|
72
74
|
<li><a href="#/">Index</a></li>
|
|
73
|
-
<li
|
|
75
|
+
<li>></li>
|
|
74
76
|
<li id="title"></li>
|
|
75
77
|
</ul>
|
|
76
78
|
</nav>
|
|
79
|
+
<label>stale threshold <input id="stale-threshold" type="number" min="0" value="30"> days</label>
|
|
77
80
|
<pre><code id="code"></code></pre>
|
|
78
81
|
</div>
|
|
79
|
-
</
|
|
82
|
+
</template>
|
|
80
83
|
|
|
81
84
|
<script id="records" type="application/json"><%= records %></script>
|
|
82
85
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
|
|
83
86
|
<script>
|
|
87
|
+
const records = JSON.parse(document.getElementById("records").textContent);
|
|
84
88
|
const main = document.getElementById("main");
|
|
85
|
-
const page_index = document.getElementById("page-index");
|
|
86
|
-
const title = document.createTextNode("");
|
|
87
|
-
document.getElementById("title").appendChild(title);
|
|
88
|
-
const code = document.getElementById("code");
|
|
89
|
-
const page_file = document.getElementById("page-file");
|
|
90
|
-
const records = JSON.parse(
|
|
91
|
-
document.getElementById("records").childNodes[0].nodeValue
|
|
92
|
-
);
|
|
93
89
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
90
|
+
function route() {
|
|
91
|
+
const hash = window.location.hash.slice(1);
|
|
92
|
+
if (hash.startsWith("/file/")) {
|
|
93
|
+
const path = hash.slice(6);
|
|
94
|
+
const record = records.find((r) => r.path === path);
|
|
95
|
+
if (record) return { page: "file", path, record };
|
|
99
96
|
}
|
|
100
|
-
|
|
101
97
|
return { page: "index" };
|
|
102
|
-
}
|
|
98
|
+
}
|
|
103
99
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
.value.split("\n")
|
|
120
|
-
.map((line, index) => {
|
|
121
|
-
const value = record.execution_count[index];
|
|
122
|
-
let color = "";
|
|
123
|
-
if (typeof value === "number")
|
|
124
|
-
color = value > 0 ? "green" : "red";
|
|
125
|
-
const number = typeof value === "number" ? value : "-";
|
|
126
|
-
return `<span class="line ${color}"><span class="number">${number
|
|
127
|
-
.toString()
|
|
128
|
-
.padStart(digit_width, " ")}</span>${line}</span>`;
|
|
129
|
-
})
|
|
130
|
-
.join("\n");
|
|
100
|
+
function cloneTemplate(id) {
|
|
101
|
+
return document.getElementById(id).content.cloneNode(true).firstElementChild;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function formatDate(posixTime) {
|
|
105
|
+
if (typeof posixTime !== "number") return "-";
|
|
106
|
+
return new Date(posixTime * 1000).toISOString().slice(0, 10);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function colorClass(count, lastExecutedAt, staleThresholdDays) {
|
|
110
|
+
if (typeof count !== "number") return "";
|
|
111
|
+
if (count === 0) return "red";
|
|
112
|
+
if (typeof lastExecutedAt === "number") {
|
|
113
|
+
const ageDays = (Date.now() / 1000 - lastExecutedAt) / 86400;
|
|
114
|
+
if (ageDays > staleThresholdDays) return "blue";
|
|
131
115
|
}
|
|
132
|
-
|
|
133
|
-
|
|
116
|
+
return "green";
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function summarize({ path, execution_count, last_executed_at }, staleThresholdDays) {
|
|
120
|
+
let hit = 0, miss = 0, relevancy = 0, stale = 0;
|
|
121
|
+
const staleCutoff = Date.now() / 1000 - staleThresholdDays * 86400;
|
|
122
|
+
for (let i = 0; i < execution_count.length; i++) {
|
|
123
|
+
const count = execution_count[i];
|
|
124
|
+
if (count !== null) {
|
|
125
|
+
relevancy++;
|
|
126
|
+
if (count > 0) {
|
|
127
|
+
hit++;
|
|
128
|
+
const t = last_executed_at[i];
|
|
129
|
+
if (typeof t === "number" && t < staleCutoff) stale++;
|
|
130
|
+
} else {
|
|
131
|
+
miss++;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
134
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
135
|
+
return {
|
|
136
|
+
path,
|
|
137
|
+
lines: execution_count.length,
|
|
138
|
+
relevancy,
|
|
139
|
+
hit,
|
|
140
|
+
miss,
|
|
141
|
+
stale,
|
|
142
|
+
coverage: hit / relevancy,
|
|
143
|
+
staleness: stale / relevancy,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
143
146
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
147
|
+
let staleThresholdDays = 30;
|
|
148
|
+
let sortKey = "coverage";
|
|
149
|
+
let sortAsc = true;
|
|
150
|
+
|
|
151
|
+
function getStaleThreshold(el) {
|
|
152
|
+
const input = el.querySelector("#stale-threshold");
|
|
153
|
+
input.value = staleThresholdDays;
|
|
154
|
+
input.addEventListener("change", () => {
|
|
155
|
+
staleThresholdDays = Number(input.value) || 30;
|
|
156
|
+
render();
|
|
157
|
+
});
|
|
158
|
+
return staleThresholdDays;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function renderIndex() {
|
|
162
|
+
const el = cloneTemplate("tmpl-index");
|
|
163
|
+
const staleThresholdDays = getStaleThreshold(el);
|
|
164
|
+
const tbody = el.querySelector("tbody");
|
|
165
|
+
el.querySelectorAll(".sortable").forEach((th) => {
|
|
166
|
+
const key = th.dataset.sort;
|
|
167
|
+
th.textContent = th.textContent.replace(/ [▲▼]$/, "") + (sortKey === key ? (sortAsc ? " ▲" : " ▼") : "");
|
|
168
|
+
th.addEventListener("click", () => {
|
|
169
|
+
if (sortKey === key) { sortAsc = !sortAsc; } else { sortKey = key; sortAsc = true; }
|
|
170
|
+
render();
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
const rows = records.map((r) => summarize(r, staleThresholdDays)).sort((a, b) => {
|
|
174
|
+
const av = a[sortKey], bv = b[sortKey];
|
|
175
|
+
if (isNaN(bv)) return -1;
|
|
176
|
+
if (isNaN(av)) return 1;
|
|
177
|
+
return sortAsc ? av - bv : bv - av;
|
|
178
|
+
});
|
|
179
|
+
for (const { path, lines, relevancy, hit, miss, stale, coverage, staleness } of rows) {
|
|
180
|
+
const tr = tbody.insertRow();
|
|
181
|
+
tr.insertCell().innerHTML = `<a href="#/file/${path}">${path}</a>`;
|
|
182
|
+
tr.insertCell().textContent = lines;
|
|
183
|
+
tr.insertCell().textContent = relevancy;
|
|
184
|
+
tr.insertCell().textContent = hit;
|
|
185
|
+
tr.insertCell().textContent = miss;
|
|
186
|
+
tr.insertCell().textContent = stale;
|
|
187
|
+
tr.insertCell().textContent = Math.round(coverage * 10000) / 100;
|
|
188
|
+
tr.insertCell().textContent = Math.round(staleness * 10000) / 100;
|
|
189
|
+
}
|
|
190
|
+
return el;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function renderFile(record) {
|
|
194
|
+
const el = cloneTemplate("tmpl-file");
|
|
195
|
+
const staleThresholdDays = getStaleThreshold(el);
|
|
196
|
+
el.querySelector("#title").textContent = record.path;
|
|
197
|
+
const digitWidth = Math.floor(Math.log10(Math.max(...record.execution_count))) + 1;
|
|
198
|
+
const highlighted = hljs.highlight(record.source, { language: "ruby" }).value;
|
|
199
|
+
el.querySelector("#code").innerHTML = highlighted
|
|
200
|
+
.split("\n")
|
|
201
|
+
.map((line, i) => {
|
|
202
|
+
const count = record.execution_count[i];
|
|
203
|
+
const displayCount = typeof count === "number" ? String(count).padStart(digitWidth) : "-".padStart(digitWidth);
|
|
204
|
+
const date = formatDate(record.last_executed_at[i]).padStart(10);
|
|
205
|
+
return `<span class="line ${colorClass(count, record.last_executed_at[i], staleThresholdDays)}"><span class="number">${displayCount}</span><time class="timestamp">${date}</time>${line}</span>`;
|
|
176
206
|
})
|
|
177
|
-
.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
tr.appendChild(
|
|
181
|
-
(() => {
|
|
182
|
-
const anchor = document.createElement("a");
|
|
183
|
-
anchor.href = `#/file/${path}`;
|
|
184
|
-
anchor.appendChild(document.createTextNode(path));
|
|
185
|
-
const td = document.createElement("td");
|
|
186
|
-
td.appendChild(anchor);
|
|
187
|
-
return td;
|
|
188
|
-
})()
|
|
189
|
-
);
|
|
190
|
-
tr.appendChild(createTdTextNode(lines));
|
|
191
|
-
tr.appendChild(createTdTextNode(relevancy));
|
|
192
|
-
tr.appendChild(createTdTextNode(hit));
|
|
193
|
-
tr.appendChild(createTdTextNode(miss));
|
|
194
|
-
tr.appendChild(
|
|
195
|
-
createTdTextNode(Math.round(coverage * 10000) / 100)
|
|
196
|
-
);
|
|
197
|
-
fragment.appendChild(tr);
|
|
198
|
-
return fragment;
|
|
199
|
-
},
|
|
200
|
-
document.createDocumentFragment()
|
|
201
|
-
)
|
|
202
|
-
);
|
|
207
|
+
.join("\n");
|
|
208
|
+
return el;
|
|
209
|
+
}
|
|
203
210
|
|
|
204
|
-
|
|
205
|
-
const routing = route(
|
|
206
|
-
|
|
207
|
-
|
|
211
|
+
function render() {
|
|
212
|
+
const routing = route();
|
|
213
|
+
const page = routing.page === "file" ? renderFile(routing.record) : renderIndex();
|
|
214
|
+
main.replaceChildren(page);
|
|
215
|
+
}
|
|
208
216
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
render(routing);
|
|
212
|
-
});
|
|
217
|
+
render();
|
|
218
|
+
window.addEventListener("hashchange", render);
|
|
213
219
|
</script>
|
|
214
220
|
</body>
|
|
215
221
|
</html>
|
|
@@ -11,12 +11,14 @@ module CoverRage
|
|
|
11
11
|
@mutex = Mutex.new
|
|
12
12
|
@path = path
|
|
13
13
|
@db = SQLite3::Database.new(path)
|
|
14
|
+
@db.busy_handler { true }
|
|
14
15
|
@db.execute <<-SQL
|
|
15
16
|
create table if not exists records (
|
|
16
17
|
path text primary key not null,
|
|
17
18
|
revision blob not null,
|
|
18
19
|
source text not null,
|
|
19
|
-
execution_count text not null
|
|
20
|
+
execution_count text not null,
|
|
21
|
+
last_executed_at text not null
|
|
20
22
|
)
|
|
21
23
|
SQL
|
|
22
24
|
process_ext = Module.new
|
|
@@ -25,6 +27,7 @@ module CoverRage
|
|
|
25
27
|
store.instance_variable_get(:@db).close
|
|
26
28
|
pid = super()
|
|
27
29
|
store.instance_variable_set(:@db, SQLite3::Database.new(path))
|
|
30
|
+
store.instance_variable_get(:@db).busy_handler { true }
|
|
28
31
|
pid
|
|
29
32
|
end
|
|
30
33
|
end
|
|
@@ -34,22 +37,21 @@ module CoverRage
|
|
|
34
37
|
def transaction(&)
|
|
35
38
|
@mutex.synchronize do
|
|
36
39
|
@db.transaction(:exclusive, &)
|
|
37
|
-
rescue SQLite3::BusyException
|
|
38
|
-
retry
|
|
39
40
|
end
|
|
40
41
|
end
|
|
41
42
|
|
|
42
43
|
def update(records)
|
|
43
44
|
@db.execute(
|
|
44
|
-
"insert or replace into records (path, revision, source, execution_count) values #{
|
|
45
|
-
(['(
|
|
45
|
+
"insert or replace into records (path, revision, source, execution_count, last_executed_at) values #{
|
|
46
|
+
(['(?,?,?,?,?)'] * records.length).join(',')
|
|
46
47
|
}",
|
|
47
48
|
records.each_with_object([]) do |record, memo|
|
|
48
49
|
memo.push(
|
|
49
50
|
record.path,
|
|
50
51
|
record.revision,
|
|
51
52
|
record.source,
|
|
52
|
-
JSON.dump(record.execution_count)
|
|
53
|
+
JSON.dump(record.execution_count),
|
|
54
|
+
JSON.dump(record.last_executed_at)
|
|
53
55
|
)
|
|
54
56
|
end
|
|
55
57
|
)
|
|
@@ -57,13 +59,14 @@ module CoverRage
|
|
|
57
59
|
|
|
58
60
|
def list
|
|
59
61
|
@db
|
|
60
|
-
.execute('select path, revision, source, execution_count from records')
|
|
61
|
-
.map do |(path, revision, source, execution_count)|
|
|
62
|
+
.execute('select path, revision, source, execution_count, last_executed_at from records')
|
|
63
|
+
.map do |(path, revision, source, execution_count, last_executed_at)|
|
|
62
64
|
Record.new(
|
|
63
65
|
path:,
|
|
64
66
|
revision:,
|
|
65
67
|
source:,
|
|
66
|
-
execution_count: JSON.parse(execution_count)
|
|
68
|
+
execution_count: JSON.parse(execution_count),
|
|
69
|
+
last_executed_at: JSON.parse(last_executed_at)
|
|
67
70
|
)
|
|
68
71
|
end
|
|
69
72
|
end
|
metadata
CHANGED
|
@@ -1,70 +1,28 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cover_rage
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Weihang Jian
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
|
-
name:
|
|
13
|
+
name: pstore
|
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
|
15
15
|
requirements:
|
|
16
|
-
- - "
|
|
16
|
+
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '
|
|
19
|
-
type: :
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
|
-
- - "
|
|
23
|
+
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '
|
|
26
|
-
- !ruby/object:Gem::Dependency
|
|
27
|
-
name: rake
|
|
28
|
-
requirement: !ruby/object:Gem::Requirement
|
|
29
|
-
requirements:
|
|
30
|
-
- - "~>"
|
|
31
|
-
- !ruby/object:Gem::Version
|
|
32
|
-
version: '13.0'
|
|
33
|
-
type: :development
|
|
34
|
-
prerelease: false
|
|
35
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
-
requirements:
|
|
37
|
-
- - "~>"
|
|
38
|
-
- !ruby/object:Gem::Version
|
|
39
|
-
version: '13.0'
|
|
40
|
-
- !ruby/object:Gem::Dependency
|
|
41
|
-
name: redis
|
|
42
|
-
requirement: !ruby/object:Gem::Requirement
|
|
43
|
-
requirements:
|
|
44
|
-
- - "~>"
|
|
45
|
-
- !ruby/object:Gem::Version
|
|
46
|
-
version: '5.3'
|
|
47
|
-
type: :development
|
|
48
|
-
prerelease: false
|
|
49
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
-
requirements:
|
|
51
|
-
- - "~>"
|
|
52
|
-
- !ruby/object:Gem::Version
|
|
53
|
-
version: '5.3'
|
|
54
|
-
- !ruby/object:Gem::Dependency
|
|
55
|
-
name: sqlite3
|
|
56
|
-
requirement: !ruby/object:Gem::Requirement
|
|
57
|
-
requirements:
|
|
58
|
-
- - "~>"
|
|
59
|
-
- !ruby/object:Gem::Version
|
|
60
|
-
version: '2.5'
|
|
61
|
-
type: :development
|
|
62
|
-
prerelease: false
|
|
63
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
-
requirements:
|
|
65
|
-
- - "~>"
|
|
66
|
-
- !ruby/object:Gem::Version
|
|
67
|
-
version: '2.5'
|
|
25
|
+
version: '0'
|
|
68
26
|
description: |
|
|
69
27
|
cover_rage is a Ruby code coverage tool designed to be simple and easy to use. It can be used not only for test coverage but also in production services to identify unused code.
|
|
70
28
|
|
|
@@ -111,7 +69,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
111
69
|
- !ruby/object:Gem::Version
|
|
112
70
|
version: '0'
|
|
113
71
|
requirements: []
|
|
114
|
-
rubygems_version:
|
|
72
|
+
rubygems_version: 4.0.4
|
|
115
73
|
specification_version: 4
|
|
116
74
|
summary: cover_rage is a Ruby code coverage tool designed to be simple and easy to
|
|
117
75
|
use. It can be used not only for test coverage but also in production services to
|