cataract 0.1.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 +7 -0
- data/.clang-tidy +30 -0
- data/.github/workflows/ci-macos.yml +12 -0
- data/.github/workflows/ci.yml +77 -0
- data/.github/workflows/test.yml +76 -0
- data/.gitignore +45 -0
- data/.overcommit.yml +38 -0
- data/.rubocop.yml +83 -0
- data/BENCHMARKS.md +201 -0
- data/CHANGELOG.md +1 -0
- data/Gemfile +27 -0
- data/LICENSE +21 -0
- data/RAGEL_MIGRATION.md +60 -0
- data/README.md +292 -0
- data/Rakefile +209 -0
- data/benchmarks/benchmark_harness.rb +193 -0
- data/benchmarks/benchmark_merging.rb +121 -0
- data/benchmarks/benchmark_optimization_comparison.rb +168 -0
- data/benchmarks/benchmark_parsing.rb +153 -0
- data/benchmarks/benchmark_ragel_removal.rb +56 -0
- data/benchmarks/benchmark_runner.rb +70 -0
- data/benchmarks/benchmark_serialization.rb +180 -0
- data/benchmarks/benchmark_shorthand.rb +109 -0
- data/benchmarks/benchmark_shorthand_expansion.rb +176 -0
- data/benchmarks/benchmark_specificity.rb +124 -0
- data/benchmarks/benchmark_string_allocation.rb +151 -0
- data/benchmarks/benchmark_stylesheet_to_s.rb +62 -0
- data/benchmarks/benchmark_to_s_cached.rb +55 -0
- data/benchmarks/benchmark_value_splitter.rb +54 -0
- data/benchmarks/benchmark_yjit.rb +158 -0
- data/benchmarks/benchmark_yjit_workers.rb +61 -0
- data/benchmarks/profile_to_s.rb +23 -0
- data/benchmarks/speedup_calculator.rb +83 -0
- data/benchmarks/system_metadata.rb +81 -0
- data/benchmarks/templates/benchmarks.md.erb +221 -0
- data/benchmarks/yjit_tests.rb +141 -0
- data/cataract.gemspec +34 -0
- data/cliff.toml +92 -0
- data/examples/color_conversion_visual_test/color_conversion_test.html +3603 -0
- data/examples/color_conversion_visual_test/generate.rb +202 -0
- data/examples/color_conversion_visual_test/template.html.erb +259 -0
- data/examples/css_analyzer/analyzer.rb +164 -0
- data/examples/css_analyzer/analyzers/base.rb +33 -0
- data/examples/css_analyzer/analyzers/colors.rb +133 -0
- data/examples/css_analyzer/analyzers/important.rb +88 -0
- data/examples/css_analyzer/analyzers/properties.rb +61 -0
- data/examples/css_analyzer/analyzers/specificity.rb +68 -0
- data/examples/css_analyzer/templates/report.html.erb +575 -0
- data/examples/css_analyzer.rb +69 -0
- data/examples/github_analysis.html +5343 -0
- data/ext/cataract/cataract.c +1086 -0
- data/ext/cataract/cataract.h +174 -0
- data/ext/cataract/css_parser.c +1435 -0
- data/ext/cataract/extconf.rb +48 -0
- data/ext/cataract/import_scanner.c +174 -0
- data/ext/cataract/merge.c +973 -0
- data/ext/cataract/shorthand_expander.c +902 -0
- data/ext/cataract/specificity.c +213 -0
- data/ext/cataract/value_splitter.c +116 -0
- data/ext/cataract_color/cataract_color.c +16 -0
- data/ext/cataract_color/color_conversion.c +1687 -0
- data/ext/cataract_color/color_conversion.h +136 -0
- data/ext/cataract_color/color_conversion_lab.c +571 -0
- data/ext/cataract_color/color_conversion_named.c +259 -0
- data/ext/cataract_color/color_conversion_oklab.c +547 -0
- data/ext/cataract_color/extconf.rb +23 -0
- data/ext/cataract_old/cataract.c +393 -0
- data/ext/cataract_old/cataract.h +250 -0
- data/ext/cataract_old/css_parser.c +933 -0
- data/ext/cataract_old/extconf.rb +67 -0
- data/ext/cataract_old/import_scanner.c +174 -0
- data/ext/cataract_old/merge.c +776 -0
- data/ext/cataract_old/shorthand_expander.c +902 -0
- data/ext/cataract_old/specificity.c +213 -0
- data/ext/cataract_old/stylesheet.c +290 -0
- data/ext/cataract_old/value_splitter.c +116 -0
- data/lib/cataract/at_rule.rb +97 -0
- data/lib/cataract/color_conversion.rb +18 -0
- data/lib/cataract/declarations.rb +332 -0
- data/lib/cataract/import_resolver.rb +210 -0
- data/lib/cataract/rule.rb +131 -0
- data/lib/cataract/stylesheet.rb +716 -0
- data/lib/cataract/stylesheet_scope.rb +257 -0
- data/lib/cataract/version.rb +5 -0
- data/lib/cataract.rb +107 -0
- data/lib/tasks/gem.rake +158 -0
- data/scripts/fuzzer/run.rb +828 -0
- data/scripts/fuzzer/worker.rb +99 -0
- data/scripts/generate_benchmarks_md.rb +155 -0
- metadata +135 -0
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>CSS Analysis Report - <%= analysis[:summary][:file_name] %></title>
|
|
7
|
+
|
|
8
|
+
<!-- Bootstrap CSS -->
|
|
9
|
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
10
|
+
|
|
11
|
+
<style>
|
|
12
|
+
body {
|
|
13
|
+
padding: 2rem 0;
|
|
14
|
+
}
|
|
15
|
+
.stat-card {
|
|
16
|
+
text-align: center;
|
|
17
|
+
padding: 1.5rem;
|
|
18
|
+
}
|
|
19
|
+
.stat-number {
|
|
20
|
+
font-size: 2.5rem;
|
|
21
|
+
font-weight: bold;
|
|
22
|
+
color: #0d6efd;
|
|
23
|
+
}
|
|
24
|
+
.stat-label {
|
|
25
|
+
color: #6c757d;
|
|
26
|
+
font-size: 0.875rem;
|
|
27
|
+
text-transform: uppercase;
|
|
28
|
+
letter-spacing: 0.5px;
|
|
29
|
+
}
|
|
30
|
+
.property-row:hover {
|
|
31
|
+
background-color: #f8f9fa;
|
|
32
|
+
}
|
|
33
|
+
.example-box {
|
|
34
|
+
background-color: #f8f9fa;
|
|
35
|
+
padding: 0.5rem;
|
|
36
|
+
border-radius: 0.25rem;
|
|
37
|
+
font-family: 'Courier New', monospace;
|
|
38
|
+
font-size: 0.875rem;
|
|
39
|
+
margin-bottom: 0.5rem;
|
|
40
|
+
}
|
|
41
|
+
.badge-important {
|
|
42
|
+
font-size: 0.75rem;
|
|
43
|
+
margin-left: 0.25rem;
|
|
44
|
+
}
|
|
45
|
+
.progress {
|
|
46
|
+
height: 1.5rem;
|
|
47
|
+
}
|
|
48
|
+
.color-swatch {
|
|
49
|
+
display: inline-block;
|
|
50
|
+
width: 50px;
|
|
51
|
+
height: 50px;
|
|
52
|
+
border: 1px solid #dee2e6;
|
|
53
|
+
border-radius: 0.25rem;
|
|
54
|
+
margin-right: 0.5rem;
|
|
55
|
+
vertical-align: middle;
|
|
56
|
+
}
|
|
57
|
+
.color-row:hover {
|
|
58
|
+
background-color: #f8f9fa;
|
|
59
|
+
}
|
|
60
|
+
</style>
|
|
61
|
+
</head>
|
|
62
|
+
<body>
|
|
63
|
+
<div class="container">
|
|
64
|
+
<!-- Header -->
|
|
65
|
+
<div class="row mb-4">
|
|
66
|
+
<div class="col">
|
|
67
|
+
<h1 class="display-4">CSS Analysis Report</h1>
|
|
68
|
+
<p class="lead text-muted">
|
|
69
|
+
Analysis of <code><%= analysis[:summary][:file_name] %></code>
|
|
70
|
+
</p>
|
|
71
|
+
<p class="text-muted">
|
|
72
|
+
Generated on <%= analysis[:summary][:generated_at].strftime('%B %d, %Y at %I:%M %p') %> using
|
|
73
|
+
<a href="https://github.com/anthropics/cataract" target="_blank">Cataract</a>
|
|
74
|
+
</p>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<!-- Summary Statistics -->
|
|
79
|
+
<div class="row mb-5">
|
|
80
|
+
<div class="col-md-3">
|
|
81
|
+
<div class="card stat-card">
|
|
82
|
+
<div class="card-body">
|
|
83
|
+
<div class="stat-number"><%= analysis[:summary][:total_rules] %></div>
|
|
84
|
+
<div class="stat-label">Total Rules</div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
<div class="col-md-3">
|
|
89
|
+
<div class="card stat-card">
|
|
90
|
+
<div class="card-body">
|
|
91
|
+
<div class="stat-number"><%= analysis[:properties][:total_properties] %></div>
|
|
92
|
+
<div class="stat-label">Total Declarations</div>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
<div class="col-md-3">
|
|
97
|
+
<div class="card stat-card">
|
|
98
|
+
<div class="card-body">
|
|
99
|
+
<div class="stat-number"><%= analysis[:properties][:unique_properties] %></div>
|
|
100
|
+
<div class="stat-label">Unique Properties</div>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
<div class="col-md-3">
|
|
105
|
+
<div class="card stat-card">
|
|
106
|
+
<div class="card-body">
|
|
107
|
+
<div class="stat-number"><%= analysis[:colors][:unique_colors] %></div>
|
|
108
|
+
<div class="stat-label">Unique Colors</div>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<!-- Analysis Tabs -->
|
|
115
|
+
<div class="row">
|
|
116
|
+
<div class="col">
|
|
117
|
+
<ul class="nav nav-tabs mb-4" id="analysisTabs" role="tablist">
|
|
118
|
+
<li class="nav-item" role="presentation">
|
|
119
|
+
<button class="nav-link active" id="properties-tab" data-bs-toggle="tab" data-bs-target="#properties" type="button" role="tab" aria-controls="properties" aria-selected="true">
|
|
120
|
+
Top Properties
|
|
121
|
+
</button>
|
|
122
|
+
</li>
|
|
123
|
+
<li class="nav-item" role="presentation">
|
|
124
|
+
<button class="nav-link" id="colors-tab" data-bs-toggle="tab" data-bs-target="#colors" type="button" role="tab" aria-controls="colors" aria-selected="false">
|
|
125
|
+
Colors
|
|
126
|
+
</button>
|
|
127
|
+
</li>
|
|
128
|
+
<li class="nav-item" role="presentation">
|
|
129
|
+
<button class="nav-link" id="specificity-tab" data-bs-toggle="tab" data-bs-target="#specificity" type="button" role="tab" aria-controls="specificity" aria-selected="false">
|
|
130
|
+
Specificity
|
|
131
|
+
</button>
|
|
132
|
+
</li>
|
|
133
|
+
<li class="nav-item" role="presentation">
|
|
134
|
+
<button class="nav-link" id="important-tab" data-bs-toggle="tab" data-bs-target="#important" type="button" role="tab" aria-controls="important" aria-selected="false">
|
|
135
|
+
!important
|
|
136
|
+
</button>
|
|
137
|
+
</li>
|
|
138
|
+
</ul>
|
|
139
|
+
|
|
140
|
+
<div class="tab-content" id="analysisTabsContent">
|
|
141
|
+
<!-- Top Properties Tab -->
|
|
142
|
+
<div class="tab-pane fade show active" id="properties" role="tabpanel" aria-labelledby="properties-tab">
|
|
143
|
+
<h3 class="mb-4">Top <%= options[:top] %> Most Used Properties</h3>
|
|
144
|
+
|
|
145
|
+
<div class="table-responsive">
|
|
146
|
+
<table class="table table-hover">
|
|
147
|
+
<thead class="table-light">
|
|
148
|
+
<tr>
|
|
149
|
+
<th style="width: 5%">#</th>
|
|
150
|
+
<th style="width: 25%">Property</th>
|
|
151
|
+
<th style="width: 15%">Count</th>
|
|
152
|
+
<th style="width: 15%">Percentage</th>
|
|
153
|
+
<th style="width: 40%">Usage</th>
|
|
154
|
+
</tr>
|
|
155
|
+
</thead>
|
|
156
|
+
<tbody>
|
|
157
|
+
<% analysis[:properties][:top_properties].each_with_index do |prop, index| %>
|
|
158
|
+
<tr class="property-row">
|
|
159
|
+
<td class="text-muted"><%= index + 1 %></td>
|
|
160
|
+
<td>
|
|
161
|
+
<code class="text-primary"><%= prop[:name] %></code>
|
|
162
|
+
</td>
|
|
163
|
+
<td>
|
|
164
|
+
<strong><%= prop[:count] %></strong>
|
|
165
|
+
</td>
|
|
166
|
+
<td>
|
|
167
|
+
<%= prop[:percentage] %>%
|
|
168
|
+
</td>
|
|
169
|
+
<td>
|
|
170
|
+
<div class="progress">
|
|
171
|
+
<div class="progress-bar" role="progressbar"
|
|
172
|
+
style="width: <%= prop[:percentage] %>%"
|
|
173
|
+
aria-valuenow="<%= prop[:percentage] %>"
|
|
174
|
+
aria-valuemin="0"
|
|
175
|
+
aria-valuemax="100">
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
</td>
|
|
179
|
+
</tr>
|
|
180
|
+
<tr>
|
|
181
|
+
<td></td>
|
|
182
|
+
<td colspan="4">
|
|
183
|
+
<details>
|
|
184
|
+
<summary class="text-muted" style="cursor: pointer;">
|
|
185
|
+
Show examples (<%= prop[:examples].length %>)
|
|
186
|
+
</summary>
|
|
187
|
+
<div class="mt-2">
|
|
188
|
+
<% prop[:examples].each do |example| %>
|
|
189
|
+
<div class="example-box">
|
|
190
|
+
<span class="text-muted"><%= example[:selector] %></span>
|
|
191
|
+
{ <strong><%= prop[:name] %></strong>: <%= example[:value] %><% if example[:important] %><span class="badge bg-danger badge-important">!important</span><% end %>; }
|
|
192
|
+
<% unless example[:media].empty? %>
|
|
193
|
+
<span class="badge bg-secondary ms-2"><%= example[:media].join(', ') %></span>
|
|
194
|
+
<% end %>
|
|
195
|
+
</div>
|
|
196
|
+
<% end %>
|
|
197
|
+
</div>
|
|
198
|
+
</details>
|
|
199
|
+
</td>
|
|
200
|
+
</tr>
|
|
201
|
+
<% end %>
|
|
202
|
+
</tbody>
|
|
203
|
+
</table>
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
|
|
207
|
+
<!-- Colors Tab -->
|
|
208
|
+
<div class="tab-pane fade" id="colors" role="tabpanel" aria-labelledby="colors-tab">
|
|
209
|
+
<h3 class="mb-4">Color Palette (<%= analysis[:colors][:unique_colors] %> unique colors)</h3>
|
|
210
|
+
|
|
211
|
+
<div class="table-responsive">
|
|
212
|
+
<table class="table table-hover">
|
|
213
|
+
<thead class="table-light">
|
|
214
|
+
<tr>
|
|
215
|
+
<th style="width: 10%">Swatch</th>
|
|
216
|
+
<th style="width: 25%">Color</th>
|
|
217
|
+
<th style="width: 15%">Count</th>
|
|
218
|
+
<th style="width: 15%">Percentage</th>
|
|
219
|
+
<th style="width: 35%">Usage</th>
|
|
220
|
+
</tr>
|
|
221
|
+
</thead>
|
|
222
|
+
<tbody>
|
|
223
|
+
<% analysis[:colors][:colors].first(50).each do |color_data| %>
|
|
224
|
+
<tr class="color-row">
|
|
225
|
+
<td>
|
|
226
|
+
<div class="color-swatch" style="background-color: <%= color_data[:hex] %>" title="<%= color_data[:color] %>"></div>
|
|
227
|
+
</td>
|
|
228
|
+
<td>
|
|
229
|
+
<code class="text-dark"><%= color_data[:color] %></code>
|
|
230
|
+
</td>
|
|
231
|
+
<td>
|
|
232
|
+
<strong><%= color_data[:count] %></strong>
|
|
233
|
+
</td>
|
|
234
|
+
<td>
|
|
235
|
+
<%= color_data[:percentage] %>%
|
|
236
|
+
</td>
|
|
237
|
+
<td>
|
|
238
|
+
<div class="progress">
|
|
239
|
+
<div class="progress-bar bg-secondary" role="progressbar"
|
|
240
|
+
style="width: <%= color_data[:percentage] %>%"
|
|
241
|
+
aria-valuenow="<%= color_data[:percentage] %>"
|
|
242
|
+
aria-valuemin="0"
|
|
243
|
+
aria-valuemax="100">
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
246
|
+
</td>
|
|
247
|
+
</tr>
|
|
248
|
+
<tr>
|
|
249
|
+
<td></td>
|
|
250
|
+
<td colspan="4">
|
|
251
|
+
<details>
|
|
252
|
+
<summary class="text-muted" style="cursor: pointer;">
|
|
253
|
+
Show examples (<%= color_data[:examples].length %>)
|
|
254
|
+
</summary>
|
|
255
|
+
<div class="mt-2">
|
|
256
|
+
<% color_data[:examples].each do |example| %>
|
|
257
|
+
<div class="example-box">
|
|
258
|
+
<span class="text-muted"><%= example[:selector] %></span>
|
|
259
|
+
{ <strong><%= example[:property] %></strong>: <%= example[:original_value] %>; }
|
|
260
|
+
<% unless example[:media].empty? %>
|
|
261
|
+
<span class="badge bg-secondary ms-2"><%= example[:media].join(', ') %></span>
|
|
262
|
+
<% end %>
|
|
263
|
+
</div>
|
|
264
|
+
<% end %>
|
|
265
|
+
</div>
|
|
266
|
+
</details>
|
|
267
|
+
</td>
|
|
268
|
+
</tr>
|
|
269
|
+
<% end %>
|
|
270
|
+
</tbody>
|
|
271
|
+
</table>
|
|
272
|
+
</div>
|
|
273
|
+
|
|
274
|
+
<% if analysis[:colors][:unique_colors] > 50 %>
|
|
275
|
+
<div class="alert alert-info mt-3">
|
|
276
|
+
Showing top 50 of <%= analysis[:colors][:unique_colors] %> colors
|
|
277
|
+
</div>
|
|
278
|
+
<% end %>
|
|
279
|
+
</div>
|
|
280
|
+
|
|
281
|
+
<!-- Specificity Tab -->
|
|
282
|
+
<div class="tab-pane fade" id="specificity" role="tabpanel" aria-labelledby="specificity-tab">
|
|
283
|
+
<h3 class="mb-4">Specificity Analysis</h3>
|
|
284
|
+
|
|
285
|
+
<!-- Statistics Cards -->
|
|
286
|
+
<div class="row mb-4">
|
|
287
|
+
<div class="col-md-3">
|
|
288
|
+
<div class="card text-center">
|
|
289
|
+
<div class="card-body">
|
|
290
|
+
<h5 class="card-title text-primary"><%= analysis[:specificity][:average_specificity] %></h5>
|
|
291
|
+
<p class="card-text text-muted small">Average Specificity</p>
|
|
292
|
+
</div>
|
|
293
|
+
</div>
|
|
294
|
+
</div>
|
|
295
|
+
<div class="col-md-3">
|
|
296
|
+
<div class="card text-center">
|
|
297
|
+
<div class="card-body">
|
|
298
|
+
<h5 class="card-title text-danger"><%= analysis[:specificity][:max_specificity] %></h5>
|
|
299
|
+
<p class="card-text text-muted small">Highest Specificity</p>
|
|
300
|
+
</div>
|
|
301
|
+
</div>
|
|
302
|
+
</div>
|
|
303
|
+
<div class="col-md-3">
|
|
304
|
+
<div class="card text-center">
|
|
305
|
+
<div class="card-body">
|
|
306
|
+
<h5 class="card-title text-success"><%= analysis[:specificity][:min_specificity] %></h5>
|
|
307
|
+
<p class="card-text text-muted small">Lowest Specificity</p>
|
|
308
|
+
</div>
|
|
309
|
+
</div>
|
|
310
|
+
</div>
|
|
311
|
+
<div class="col-md-3">
|
|
312
|
+
<div class="card text-center">
|
|
313
|
+
<div class="card-body">
|
|
314
|
+
<h5 class="card-title text-warning"><%= analysis[:specificity][:high_specificity_count] %></h5>
|
|
315
|
+
<p class="card-text text-muted small">High Specificity (>100)</p>
|
|
316
|
+
</div>
|
|
317
|
+
</div>
|
|
318
|
+
</div>
|
|
319
|
+
</div>
|
|
320
|
+
|
|
321
|
+
<!-- Specificity Distribution -->
|
|
322
|
+
<div class="card mb-4">
|
|
323
|
+
<div class="card-header">
|
|
324
|
+
<h5 class="mb-0">Specificity Distribution</h5>
|
|
325
|
+
</div>
|
|
326
|
+
<div class="card-body">
|
|
327
|
+
<div class="row text-center">
|
|
328
|
+
<div class="col-md-3">
|
|
329
|
+
<div class="mb-2">
|
|
330
|
+
<span class="badge bg-success" style="font-size: 1rem; padding: 0.5rem 1rem;">
|
|
331
|
+
<%= analysis[:specificity][:categories][:low] %>
|
|
332
|
+
</span>
|
|
333
|
+
</div>
|
|
334
|
+
<small class="text-muted">Low (0-10)<br>Element selectors</small>
|
|
335
|
+
</div>
|
|
336
|
+
<div class="col-md-3">
|
|
337
|
+
<div class="mb-2">
|
|
338
|
+
<span class="badge bg-info" style="font-size: 1rem; padding: 0.5rem 1rem;">
|
|
339
|
+
<%= analysis[:specificity][:categories][:medium] %>
|
|
340
|
+
</span>
|
|
341
|
+
</div>
|
|
342
|
+
<small class="text-muted">Medium (11-100)<br>Class selectors</small>
|
|
343
|
+
</div>
|
|
344
|
+
<div class="col-md-3">
|
|
345
|
+
<div class="mb-2">
|
|
346
|
+
<span class="badge bg-warning" style="font-size: 1rem; padding: 0.5rem 1rem;">
|
|
347
|
+
<%= analysis[:specificity][:categories][:high] %>
|
|
348
|
+
</span>
|
|
349
|
+
</div>
|
|
350
|
+
<small class="text-muted">High (101-1000)<br>ID selectors</small>
|
|
351
|
+
</div>
|
|
352
|
+
<div class="col-md-3">
|
|
353
|
+
<div class="mb-2">
|
|
354
|
+
<span class="badge bg-danger" style="font-size: 1rem; padding: 0.5rem 1rem;">
|
|
355
|
+
<%= analysis[:specificity][:categories][:very_high] %>
|
|
356
|
+
</span>
|
|
357
|
+
</div>
|
|
358
|
+
<small class="text-muted">Very High (>1000)<br>Multiple IDs</small>
|
|
359
|
+
</div>
|
|
360
|
+
</div>
|
|
361
|
+
</div>
|
|
362
|
+
</div>
|
|
363
|
+
|
|
364
|
+
<!-- Top 20 Highest Specificity Selectors -->
|
|
365
|
+
<h4 class="mb-3">Top 20 Highest Specificity Selectors</h4>
|
|
366
|
+
<div class="table-responsive">
|
|
367
|
+
<table class="table table-hover">
|
|
368
|
+
<thead class="table-light">
|
|
369
|
+
<tr>
|
|
370
|
+
<th style="width: 10%">Rank</th>
|
|
371
|
+
<th style="width: 15%">Specificity</th>
|
|
372
|
+
<th style="width: 50%">Selector</th>
|
|
373
|
+
<th style="width: 15%">Declarations</th>
|
|
374
|
+
<th style="width: 10%">Media</th>
|
|
375
|
+
</tr>
|
|
376
|
+
</thead>
|
|
377
|
+
<tbody>
|
|
378
|
+
<% analysis[:specificity][:top_20_highest].each_with_index do |item, index| %>
|
|
379
|
+
<%
|
|
380
|
+
badge_class = if item[:specificity] > 1000
|
|
381
|
+
'bg-danger'
|
|
382
|
+
elsif item[:specificity] > 100
|
|
383
|
+
'bg-warning'
|
|
384
|
+
elsif item[:specificity] > 10
|
|
385
|
+
'bg-info'
|
|
386
|
+
else
|
|
387
|
+
'bg-success'
|
|
388
|
+
end
|
|
389
|
+
%>
|
|
390
|
+
<tr>
|
|
391
|
+
<td class="text-muted"><%= index + 1 %></td>
|
|
392
|
+
<td>
|
|
393
|
+
<span class="badge <%= badge_class %>"><%= item[:specificity] %></span>
|
|
394
|
+
</td>
|
|
395
|
+
<td>
|
|
396
|
+
<code style="font-size: 0.875rem;"><%= item[:selector] %></code>
|
|
397
|
+
</td>
|
|
398
|
+
<td class="text-center">
|
|
399
|
+
<%= item[:declaration_count] %>
|
|
400
|
+
</td>
|
|
401
|
+
<td>
|
|
402
|
+
<% unless item[:media].empty? %>
|
|
403
|
+
<span class="badge bg-secondary" style="font-size: 0.75rem;"><%= item[:media].first %></span>
|
|
404
|
+
<% end %>
|
|
405
|
+
</td>
|
|
406
|
+
</tr>
|
|
407
|
+
<% end %>
|
|
408
|
+
</tbody>
|
|
409
|
+
</table>
|
|
410
|
+
</div>
|
|
411
|
+
</div>
|
|
412
|
+
|
|
413
|
+
<!-- !important Tab -->
|
|
414
|
+
<div class="tab-pane fade" id="important" role="tabpanel" aria-labelledby="important-tab">
|
|
415
|
+
<h3 class="mb-4">!important Usage Analysis</h3>
|
|
416
|
+
|
|
417
|
+
<!-- Statistics Cards -->
|
|
418
|
+
<div class="row mb-4">
|
|
419
|
+
<div class="col-md-3">
|
|
420
|
+
<div class="card text-center">
|
|
421
|
+
<div class="card-body">
|
|
422
|
+
<h5 class="card-title text-danger"><%= analysis[:important][:important_count] %></h5>
|
|
423
|
+
<p class="card-text text-muted small">!important Declarations</p>
|
|
424
|
+
</div>
|
|
425
|
+
</div>
|
|
426
|
+
</div>
|
|
427
|
+
<div class="col-md-3">
|
|
428
|
+
<div class="card text-center">
|
|
429
|
+
<div class="card-body">
|
|
430
|
+
<h5 class="card-title text-warning"><%= analysis[:important][:important_percentage] %>%</h5>
|
|
431
|
+
<p class="card-text text-muted small">Of All Declarations</p>
|
|
432
|
+
</div>
|
|
433
|
+
</div>
|
|
434
|
+
</div>
|
|
435
|
+
<div class="col-md-3">
|
|
436
|
+
<div class="card text-center">
|
|
437
|
+
<div class="card-body">
|
|
438
|
+
<h5 class="card-title text-info"><%= analysis[:important][:properties_using_important] %></h5>
|
|
439
|
+
<p class="card-text text-muted small">Properties Affected</p>
|
|
440
|
+
</div>
|
|
441
|
+
</div>
|
|
442
|
+
</div>
|
|
443
|
+
<div class="col-md-3">
|
|
444
|
+
<div class="card text-center">
|
|
445
|
+
<div class="card-body">
|
|
446
|
+
<h5 class="card-title text-primary"><%= analysis[:important][:selectors_using_important] %></h5>
|
|
447
|
+
<p class="card-text text-muted small">Selectors Using It</p>
|
|
448
|
+
</div>
|
|
449
|
+
</div>
|
|
450
|
+
</div>
|
|
451
|
+
</div>
|
|
452
|
+
|
|
453
|
+
<!-- Top Properties Using !important -->
|
|
454
|
+
<h4 class="mb-3 mt-4">Top Properties Using !important</h4>
|
|
455
|
+
<div class="table-responsive">
|
|
456
|
+
<table class="table table-hover">
|
|
457
|
+
<thead class="table-light">
|
|
458
|
+
<tr>
|
|
459
|
+
<th style="width: 10%">Rank</th>
|
|
460
|
+
<th style="width: 40%">Property</th>
|
|
461
|
+
<th style="width: 20%">Count</th>
|
|
462
|
+
<th style="width: 30%">% of !important</th>
|
|
463
|
+
</tr>
|
|
464
|
+
</thead>
|
|
465
|
+
<tbody>
|
|
466
|
+
<% analysis[:important][:top_properties].first(10).each_with_index do |prop, index| %>
|
|
467
|
+
<tr>
|
|
468
|
+
<td class="text-muted"><%= index + 1 %></td>
|
|
469
|
+
<td>
|
|
470
|
+
<code class="text-primary"><%= prop[:property] %></code>
|
|
471
|
+
</td>
|
|
472
|
+
<td>
|
|
473
|
+
<strong><%= prop[:count] %></strong>
|
|
474
|
+
</td>
|
|
475
|
+
<td>
|
|
476
|
+
<div class="progress">
|
|
477
|
+
<div class="progress-bar bg-danger" role="progressbar"
|
|
478
|
+
style="width: <%= prop[:percentage] %>%"
|
|
479
|
+
aria-valuenow="<%= prop[:percentage] %>"
|
|
480
|
+
aria-valuemin="0"
|
|
481
|
+
aria-valuemax="100">
|
|
482
|
+
<%= prop[:percentage] %>%
|
|
483
|
+
</div>
|
|
484
|
+
</div>
|
|
485
|
+
</td>
|
|
486
|
+
</tr>
|
|
487
|
+
<% end %>
|
|
488
|
+
</tbody>
|
|
489
|
+
</table>
|
|
490
|
+
</div>
|
|
491
|
+
|
|
492
|
+
<!-- Top Selectors Using !important -->
|
|
493
|
+
<h4 class="mb-3 mt-4">Top Selectors Using !important</h4>
|
|
494
|
+
<div class="table-responsive">
|
|
495
|
+
<table class="table table-hover">
|
|
496
|
+
<thead class="table-light">
|
|
497
|
+
<tr>
|
|
498
|
+
<th style="width: 10%">Rank</th>
|
|
499
|
+
<th style="width: 70%">Selector</th>
|
|
500
|
+
<th style="width: 20%">!important Count</th>
|
|
501
|
+
</tr>
|
|
502
|
+
</thead>
|
|
503
|
+
<tbody>
|
|
504
|
+
<% analysis[:important][:top_selectors].first(20).each_with_index do |sel, index| %>
|
|
505
|
+
<tr>
|
|
506
|
+
<td class="text-muted"><%= index + 1 %></td>
|
|
507
|
+
<td>
|
|
508
|
+
<code style="font-size: 0.875rem;"><%= sel[:selector] %></code>
|
|
509
|
+
</td>
|
|
510
|
+
<td>
|
|
511
|
+
<span class="badge bg-danger"><%= sel[:count] %></span>
|
|
512
|
+
</td>
|
|
513
|
+
</tr>
|
|
514
|
+
<% end %>
|
|
515
|
+
</tbody>
|
|
516
|
+
</table>
|
|
517
|
+
</div>
|
|
518
|
+
|
|
519
|
+
<!-- All !important Declarations -->
|
|
520
|
+
<% if analysis[:important][:important_count] <= 50 %>
|
|
521
|
+
<h4 class="mb-3 mt-4">All !important Declarations (<%= analysis[:important][:important_count] %>)</h4>
|
|
522
|
+
<div class="table-responsive">
|
|
523
|
+
<table class="table table-hover table-sm">
|
|
524
|
+
<thead class="table-light">
|
|
525
|
+
<tr>
|
|
526
|
+
<th style="width: 40%">Selector</th>
|
|
527
|
+
<th style="width: 25%">Property</th>
|
|
528
|
+
<th style="width: 25%">Value</th>
|
|
529
|
+
<th style="width: 10%">Media</th>
|
|
530
|
+
</tr>
|
|
531
|
+
</thead>
|
|
532
|
+
<tbody>
|
|
533
|
+
<% analysis[:important][:all_important].each do |item| %>
|
|
534
|
+
<tr>
|
|
535
|
+
<td>
|
|
536
|
+
<code style="font-size: 0.75rem;"><%= item[:selector] %></code>
|
|
537
|
+
</td>
|
|
538
|
+
<td>
|
|
539
|
+
<code class="text-primary"><%= item[:property] %></code>
|
|
540
|
+
</td>
|
|
541
|
+
<td>
|
|
542
|
+
<code class="text-muted" style="font-size: 0.75rem;"><%= item[:value] %></code>
|
|
543
|
+
</td>
|
|
544
|
+
<td>
|
|
545
|
+
<% unless item[:media].empty? %>
|
|
546
|
+
<span class="badge bg-secondary" style="font-size: 0.65rem;"><%= item[:media].first %></span>
|
|
547
|
+
<% end %>
|
|
548
|
+
</td>
|
|
549
|
+
</tr>
|
|
550
|
+
<% end %>
|
|
551
|
+
</tbody>
|
|
552
|
+
</table>
|
|
553
|
+
</div>
|
|
554
|
+
<% else %>
|
|
555
|
+
<div class="alert alert-info mt-4">
|
|
556
|
+
<%= analysis[:important][:important_count] %> !important declarations found. Showing top properties and selectors above.
|
|
557
|
+
</div>
|
|
558
|
+
<% end %>
|
|
559
|
+
</div>
|
|
560
|
+
</div>
|
|
561
|
+
</div>
|
|
562
|
+
</div>
|
|
563
|
+
|
|
564
|
+
<!-- Footer -->
|
|
565
|
+
<footer class="mt-5 pt-4 border-top text-center text-muted">
|
|
566
|
+
<p>
|
|
567
|
+
Generated by <a href="https://github.com/anthropics/cataract" target="_blank">Cataract CSS Parser</a>
|
|
568
|
+
</p>
|
|
569
|
+
</footer>
|
|
570
|
+
</div>
|
|
571
|
+
|
|
572
|
+
<!-- Bootstrap JS (optional, for interactive components) -->
|
|
573
|
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
|
574
|
+
</body>
|
|
575
|
+
</html>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'optparse'
|
|
5
|
+
require_relative 'css_analyzer/analyzer'
|
|
6
|
+
|
|
7
|
+
# CLI Interface
|
|
8
|
+
if __FILE__ == $PROGRAM_NAME
|
|
9
|
+
options = {}
|
|
10
|
+
|
|
11
|
+
OptionParser.new do |opts|
|
|
12
|
+
opts.banner = 'Usage: css_analyzer.rb [options] URL_OR_FILE'
|
|
13
|
+
opts.separator ''
|
|
14
|
+
opts.separator 'Analyze CSS from a file, URL, or website:'
|
|
15
|
+
opts.separator ' - Local file: css_analyzer.rb styles.css'
|
|
16
|
+
opts.separator ' - CSS URL: css_analyzer.rb https://example.com/styles.css'
|
|
17
|
+
opts.separator ' - Website: css_analyzer.rb https://example.com (analyzes all CSS)'
|
|
18
|
+
opts.separator ''
|
|
19
|
+
|
|
20
|
+
opts.on('-t', '--top N', Integer, 'Show top N properties (default: 20)') do |n|
|
|
21
|
+
options[:top] = n
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
opts.on('-o', '--output FILE', 'Write report to FILE instead of stdout') do |file|
|
|
25
|
+
options[:output] = file
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
opts.on('--use-shim', 'Use Cataract shim for css_parser (for Premailer)') do
|
|
29
|
+
options[:use_shim] = true
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
opts.on('-h', '--help', 'Show this help message') do
|
|
33
|
+
puts opts
|
|
34
|
+
exit
|
|
35
|
+
end
|
|
36
|
+
end.parse!
|
|
37
|
+
|
|
38
|
+
# Check for ENV var to enable shim
|
|
39
|
+
options[:use_shim] = true if ENV['CATARACT_SHIM']
|
|
40
|
+
|
|
41
|
+
# Check for required argument
|
|
42
|
+
if ARGV.empty?
|
|
43
|
+
warn 'Error: No URL or file specified'
|
|
44
|
+
warn 'Usage: css_analyzer.rb [options] URL_OR_FILE'
|
|
45
|
+
exit 1
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
source = ARGV[0]
|
|
49
|
+
|
|
50
|
+
# Run analyzer
|
|
51
|
+
total_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
52
|
+
analyzer = CSSAnalyzer::Analyzer.new(source, options)
|
|
53
|
+
|
|
54
|
+
analysis_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
55
|
+
analyzer.save_report
|
|
56
|
+
|
|
57
|
+
# Output timing information to stderr
|
|
58
|
+
total_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) - total_start
|
|
59
|
+
analysis_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) - analysis_start
|
|
60
|
+
|
|
61
|
+
warn "\n=== Timing ==="
|
|
62
|
+
if analyzer.timings[:fetch]
|
|
63
|
+
warn "Fetch webpage: #{format('%.3f', analyzer.timings[:fetch])}s"
|
|
64
|
+
warn "Premailer parse: #{format('%.3f', analyzer.timings[:premailer_parse])}s"
|
|
65
|
+
warn "Cataract parse: #{format('%.3f', analyzer.timings[:cataract_parse])}s"
|
|
66
|
+
end
|
|
67
|
+
warn "Analysis & report: #{format('%.3f', analysis_time)}s"
|
|
68
|
+
warn "Total: #{format('%.3f', total_time)}s"
|
|
69
|
+
end
|