—package
CSS::Coverage;
{
$CSS::Coverage::VERSION
=
'0.01'
;
}
use
Moose;
use
CSS::SAC;
use
CSS::Coverage::XPath;
has
css
=> (
is
=>
'ro'
,
isa
=>
'Str|ScalarRef'
,
required
=> 1,
);
has
documents
=> (
is
=>
'ro'
,
isa
=>
'ArrayRef[Str|ScalarRef]'
,
required
=> 1,
);
has
html_trees
=> (
is
=>
'ro'
,
isa
=>
'ArrayRef'
,
builder
=>
'_build_html_trees'
,
lazy
=> 1,
);
has
_sac_document
=> (
is
=>
'ro'
,
isa
=>
'CSS::Coverage::Document'
,
default
=>
sub
{ CSS::Coverage::Document->new(
delegate
=>
shift
) },
lazy
=> 1,
);
has
_report
=> (
is
=>
'rw'
,
isa
=>
'CSS::Coverage::Report'
,
clearer
=>
'_clear_report'
,
);
has
_ignore_next_rule
=> (
is
=>
'rw'
,
isa
=>
'Bool'
,
default
=> 0,
);
sub
_build_html_trees {
my
$self
=
shift
;
my
@trees
;
for
my
$document
(@{
$self
->documents }) {
my
$tree
= HTML::TreeBuilder::XPath->new;
$tree
->ignore_unknown(0);
if
(
ref
(
$document
)) {
$tree
->parse(
$$document
);
$tree
->
eof
;
}
else
{
$tree
->parse_file(
$document
);
}
push
@trees
,
$tree
;
}
return
\
@trees
;
}
sub
check {
my
$self
=
shift
;
my
$sac
= CSS::SAC->new({
DocumentHandler
=>
$self
->_sac_document,
});
my
$report
= CSS::Coverage::Report->new;
$self
->_report(
$report
);
my
$css
=
$self
->css;
if
(
ref
(
$css
)) {
$sac
->parse({
string
=>
$$css
});
}
else
{
$sac
->parse({
filename
=>
$css
});
}
$self
->_clear_report;
return
$report
;
}
# -- CSS::Coverage::DocumentDelegate
sub
_check_selector {
my
(
$self
,
$selector
) =
@_
;
if
(
$self
->_ignore_next_rule) {
$self
->_ignore_next_rule(0);
return
;
}
my
$xpath
= CSS::Coverage::XPath->new(
$selector
)->to_xpath;
for
my
$tree
(@{
$self
->html_trees }) {
if
(
$tree
->
exists
(
$xpath
)) {
return
;
}
}
if
(
$self
->_report) {
$self
->_report->add_unmatched_selector(
$selector
);
}
else
{
warn
"This selector matches no documents: $selector\n"
;
}
}
sub
_got_coverage_directive {
my
(
$self
,
$directive
) =
@_
;
if
(
$directive
eq
'dynamic'
||
$directive
eq
'ignore'
) {
$self
->_ignore_next_rule(1);
}
}
__PACKAGE__->meta->make_immutable;
1;
__END__
=head1 NAME
CSS::Coverage - Confirm that your CSS matches your DOM
=head1 VERSION
version 0.01
=head1 SYNOPSIS
css-coverage style.css index.html second.html
Unmatched selectors (3):
div.form-actions button:first-child
.expanded span.open
ul.attn li form textarea
my $coverage = CSS::Coverage->new(
css => $css_file,
documents => \@html_files,
);
my $report = $coverage->check;
print for $report->unmatched_selectors;
=head1 DESCRIPTION
It is very tedious to manually confirm whether a particular CSS
selector matches any of your documents. There are browser-based
tools, like one that ships in Chrome, that do this for you. However,
they do not presently check multiple pages. Browser tools are also
not great for running in a continuous integration environment.
This module provides a good first stab at paring down the list of
rules to manually check.
If you know that a particular rule only matches in the presence of
a dynamically-modified DOM, via JavaScript, you can add a comment
like this either inside or before that CSS rule:
/* coverage: dynamic */
This will cause CSS::Coverage to skip that rule.
=head1 ATTRIBUTES
=head2 css (Str|ScalarRef)
If given a string, C<css> is treated as a filename. If given as a scalar reference, C<css> is treated as CSS code.
=head2 documents (ArrayRef[Str|ScalarRef])
A list of HTML documents. For each document, strings are treated as filenames; scalar reference as raw HTML code.
=head1 METHODS
=head2 check
Runs a coverage check of the given CSS against the given documents. Returns a L<CSS::Coverage::Report> object.
=cut