<(common.inc.plp)><:
-use 5.010;
-use utf8;
use List::Util qw(sum max first);
+no if $] >= 5.018, warnings => 'experimental::smartmatch';
Html({
title => 'browser compatibility cheat sheet',
- version => 'v1.1',
+ version => '1.6',
description => [
"Compatibility table of new web features (HTML5, CSS3, SVG, Javascript)",
"comparing support and usage share for all popular browser versions.",
html html5 css css3 svg javascript js dom mobile
ie internet explorer firefox chrome safari webkit opera
'],
- stylesheet => [qw'circus dark mono red light'],
data => ['data/browser/support.inc.pl'],
});
say "<h1>Browser compatibility</h1>\n";
-my $caniuse = do 'data/browser/support.inc.pl' or die $! || $@;
-$_->{verrelease} = {
- # mark last three (future) versions as unreleased, ensure current isn't
- map {
- $_->[-1] => 0, $_->[-2] => 0, $_->[-3] => 0,
- $_->[-4] => undef,
- } $_->{versions}
-} for values %{ $caniuse->{agents} };
+my $caniuse = Data('data/browser/support');
my %CSTATS = (
'n' => 'l1',
'p d' => 'l2',
'a d' => 'l2',
'y' => 'l5',
+ 'y #' => 'l4',
'y x' => 'l5 ex',
+ 'y x #' => 'l4 ex',
'a' => 'l3',
'a x' => 'l3 ex',
- 'p j' => 'l2',
- 'j' => 'l2',
'p' => 'l2',
- 'p p' => 'l2',
'u' => 'l0',
+ 'u d' => 'l2',
);
my %DSTATS = (
u => 'unknown',
n => 'unsupported',
p => 'plugin required',
- j => 'javascript required',
a => 'partial',
y => 'supported',
- d => 'disabled by default',
+ d => '(disabled by default)',
x => sub {
join(' ',
- 'requires prefix',
- (map "-$_-", $caniuse->{agents}->{$_[0]}->{prefix} // ()),
+ 'with prefix',
+ map {"-$_-"}
+ $caniuse->{agents}->{$_[0]}->{version_list}->{$_[1]}->{prefix}
+ // $caniuse->{agents}->{$_[0]}->{prefix} // (),
);
},
);
my %PSTATS = ( # score percentage
y => 1, 'y x' => .9,
- a => .5, 'a x' => .5, 'a d' => .1,
- j => .2, 'p j' => .2, 'n d' => .2, 'n x d' => .2,
- p => .2, 'p p' => .2, 'p d' => .1,
+ a => .5, 'a x' => .5, 'a d' => .2,
+ p => .2, 'p d' => .1,
+ n => 0, 'n d' => .2, 'n x d' => .2,
+ u => 0,
);
my %CSTATUS = (
unoff => 'l1', # unofficial
other => 'l0', # non-w3
);
my %versions;
-if (my ($somerow) = values %{ $caniuse->{data} }) {
- while (my ($browser, $row) = each %{ $somerow->{stats} }) {
- $versions{$browser} = [ sort { paddedver($a) cmp paddedver($b) } keys %$row ];
- }
+while (my ($browser, $row) = each %{ $caniuse->{agents} }) {
+ $versions{$browser} = [@{ $row->{versions} }];
}
-print <<'';
-<p id="intro">Alternate rendition of Fyrd's <a href="http://caniuse.com/">when can I use...</a> page
+my $ref = showlink('Can I use', 'https://caniuse.com/');
+$ref =~ s/(?=>)/ title="updated $_"/
+ for map { s/[\sT].*//r } $caniuse->{-date} || ();
+$ref = "Fyrd's $ref page";
+say '<p id="intro">Alternate rendition of '.$ref;
my ($canihas, $usage);
-my $minusage = $get{threshold} // .7;
+my $minusage = $get{threshold} // 1;
given ($get{usage} // 'wm') {
when (!$_) {
# none
}
when (!m{ \A [a-z]\w+ (?:/\d[\d-]*\d)? \z }x) {
- printf "<p>Invalid browser usage data request: <em>%s</em>",
- 'identifier must be alphanumeric name or <q>0</q>';
+ Alert([
+ 'Invalid browser usage data request',
+ 'Identifier must be alphanumeric name or <q>0</q>.',
+ ]);
}
- $canihas = do "data/browser/usage-$_.inc.pl" or do {
- printf "<p>Browser usage data not found: <em>%s</em>", $! || $@;
+ $canihas = eval { Data("data/browser/usage-$_") } or do {
+ Alert('Browser usage data not found', $@);
break;
};
$usage = $_;
my $ref = $canihas->{-title} || 'unknown';
- $ref = sprintf '<a href="%s">%s</a>', $_, $ref
+ $ref = showlink($ref, $_)
for $canihas->{-site} || $canihas->{-source} || ();
- $ref .= " $_" for $canihas->{-date} || ();
+ $ref =~ s/(?=>)/ title="updated $_"/ for $canihas->{-date} || ();
print "\nwith $ref browser usage statistics";
}
}
} @browsers
}; # fallback hash based on release semantics
-my $usagemax = (max(map { ref $_ eq 'HASH' && sum(values %$_) } values %$canihas) // 1) / 100;
-my $usagepct = 1; # score multiplier for 0..100 result
-# normalise usage percentage to only include shown browsers
-$usagepct = 100.01 / featurescore({ # yes for every possible version
- map { $_ => { map {$_ => 'y'} map { @{$_} } @{$versions{$_}} } } keys %versions
-});
+# score multiplier for percentage of all browser versions
+my $usagepct = 99.99 / sum(
+ map { $_->{-total} // values %{$_} }
+ map { $canihas->{$_} }
+ grep { !/^-/ }
+ keys %{$canihas}
+);
+
+$_->{usage} = featurescore($_->{stats}) * $usagepct
+ for values %{ $caniuse->{data} };
print '<table class="mapped">';
print '<col span="3">'; # should match first thead row
# preceding row without any colspan to work around gecko bug
print "\n<tr>";
for my $browser (@browsers) {
- for (@{ $versions{$browser} }) {
- my $lastver = $_->[-1];
- my $release = $caniuse->{agents}->{$browser}->{verrelease}->{$lastver};
- my $future = defined $release;
+ for my $span (@{ $versions{$browser} }) {
+ my $lastver = first {
+ $caniuse->{agents}->{$browser}->{version_list}->{$_}->{release_date} # stable
+ } reverse @{$span};
printf('<td title="%s"%s>%s',
join(' ',
- sprintf('%.1f%%', sum(@{ $canihas->{$browser} }{@$_}) * $usagepct),
- $future ? 'development' : (),
- 'version ' . join(', ', @{$_}),
+ sprintf('%.1f%%', sum(@{ $canihas->{$browser} }{ @{$span} }) * $usagepct),
+ 'version ' . showversions(@{$span}, undef),
+ (map {
+ $_ ? sprintf('(released %d)', $_/3600/24/365.25 + 1970) : '(development)'
+ } $caniuse->{agents}->{$browser}->{version_list}->{$lastver}->{release_date}),
),
- $future && ' class="ex"',
- showversions($lastver),
+ !defined $lastver && ' class="ex"',
+ showversions($lastver // $span->[0]),
);
}
}
if ($canihas) {
while (my ($browser, $versions) = each %$row) {
ref $versions eq 'HASH' or next;
- while (my ($version, $_) = each %$versions) {
- $rank += ($canihas->{$browser}->{$version} || .001) * $PSTATS{$_};
+ my $prev;
+ for my $version (@{ $caniuse->{agents}->{$browser}->{versions} }) {
+ my $status = $versions->{$version} // $prev;
+ $status =~ s/\h\#\d+//g;
+ $rank += ($canihas->{$browser}->{$version} || .001) * $PSTATS{$status};
+ $prev = $status;
}
}
return $rank;
sub formatnotes {
my @html = @_;
for (@html) {
+ s/\r\n?/\n/g; # windows returns
s/\h* $//gmx; # trailing whitespace
s/(?<= [^.\n]) $/./gmx; # consistently end each line by a period
Entity($_);
s{ ` ([^`]*) ` }{<code>$1</code>}gx;
+ s{ \(\K (?: \Qhttps://caniuse.com\E )? (?: /? \#feat= | / ) }{#}gx;
s{ \[ ([^]]*) \] \( ([^)]*) \) }{<a href="$2">$1</a>}gx;
}
return @html;
}
+sub notestotitle {
+ my @notes = @_;
+ for (@notes) {
+ EscapeHTML($_);
+ s{ \[ ([^]]*) \] \( [^)]* \) }{$1}gx; # strip link urls
+ }
+ return @notes;
+}
+
sub saytitlecol {
my ($id) = @_;
my $row = $caniuse->{data}->{$id};
print '<div class=aside>';
print "<p>$_</p>"
for formatnotes($row->{description}, $row->{notes} || ());
+ if (my %notes = %{ $row->{notes_by_num} }) {
+ say '<p>Browser-specific notes:';
+ say "<br>#$_: ", formatnotes($notes{$_}) for sort keys %notes;
+ say '</p>';
+ }
printf 'Resources: %s.', join(', ', map {
- sprintf '<a href="%s">%s</a>', EscapeHTML($_->{url}), $_->{title}
+ showlink($_->{title}, $_->{url})
} @$_) for grep { @$_ } $row->{links} // ();
printf '<br>Parent feature: %s.', join(', ', map {
- sprintf '<a href="%s">%s</a>', EscapeHTML("#$_"), $caniuse->{data}->{$_}->{title}
+ showlink($caniuse->{data}->{$_}->{title}, "#$_")
} $_) for $row->{parent} || ();
print '</div>';
}
for ($row->{status}) {
my $cell = $_ // '-';
- $cell = sprintf '<a href="%s">%s</a>', $_, $cell for $row->{spec} // ();
+ $cell = showlink($cell, $_) for $row->{spec} // ();
printf '<td title="%s" class="l %s">%s',
$caniuse->{statuses}->{$_}, $CSTATUS{$_} // '', $cell;
}
my $data = $feature->{stats}->{$browser};
if (ref $data eq 'ARRAY') {
# special case for unsupported
- my $release = $caniuse->{agents}->{$browser}->{verrelease};
$data = {
- map { $_ => defined $release->{$_} ? 'u' : 'n' } keys %$release
+ map { $_ => 'n' }
+ keys %{ $caniuse->{agents}->{$browser}->{version_list} }
};
}
my $compare = (
!defined $ver ? undef : # last column if nameless
ref $data ne 'HASH' ? '' : # unclassified if no support hash
- $data->{ $ver->[-1] } // $prev # known or inherit from predecessor
- // (grep { defined } @{$data}{ map { $_->[0] } @{ $versions{$browser} } })[0]
- ~~ 'n' && 'n' # first known version is unsupported
+ (first { defined } @{$data}{ reverse @{$ver} }) # last known version
+ // $prev # inherit from predecessor
|| 'u' # unsure
);
- unless (!defined $prev or $prev ~~ $compare) {
- my $usage = sum(@{ $canihas->{$browser} }{ map { @{$_} } @span });
+ if (defined $prev and not $prev ~~ $compare) {
+ # different columns
+ my @vercover = (map { @{$_} } @span); # accumulated conforming versions
+ for ($ver ? @{$ver} : ()) {
+ last if defined $data->{$_}; # until different
+ push @vercover, $_; # matches from next span start
+ }
+ my $usage = sum(@{ $canihas->{$browser} }{@vercover});
# strip #\d note references from support class
my @notes;
while $prev =~ s/\h \# (\d+) \b//x;
# prepare version hover details
- my $title = sprintf('%.1f%% %s', $usage * $usagepct, join(', ',
- map { ref $_ eq 'CODE' ? $_->($browser) : $_ }
- map { $DSTATS{$_} // () }
- map { split / /, $_ }
- $prev
+ my $title = sprintf('%.1f%% %s', $usage * $usagepct, join(' ',
+ (map { ref $_ eq 'CODE' ? $_->($browser, $vercover[0]) : $_ }
+ map { $DSTATS{$_} // () }
+ map { split / /, $_ }
+ $prev
+ ),
+ 'in', $caniuse->{agents}->{$browser}->{abbr},
+ showversions(@vercover, undef),
));
- $title .= "\n".EscapeHTML($_) for @notes;
+ $title .= "\n$_" for notestotitle(@notes);
+ $prev .= ' #' if @notes and $prev =~ /^y/;
printf('<td class="%s" colspan="%d" title="%s">%s',
join(' ',
X => $CSTATS{$prev},
sprintf('p%01d', $usage * ($usagepct - .0001) / 10),
sprintf('p%02d', $usage * ($usagepct - .0001)),
),
- sprintf('pp%02d', $usage / $usagemax),
),
scalar @span,
$title,
- showversions($span[0]->[0], @span > 1 ? ($span[-1]->[-1]) : ()),
+ showversions($span[0]->[0], @span > 1 && defined $ver ? $span[-1]->[-1] : ()),
);
undef $prev;
@span = ();
}
- push @span, $ver && [ grep { $data->{ $_ } eq $data->{ $ver->[-1] } } @{$ver} ];
+ if ($ver) {
+ my $startversion = first { defined $data->{ $ver->[$_] } }
+ reverse 0 .. $#{$ver}; # compare index
+ push @span, [ @{$ver}[ $startversion .. $#{$ver} ] ];
+ }
$prev = $compare;
}
}
sub sayusagecol {
my ($id) = @_;
- print '<td>', int featurescore($caniuse->{data}->{$id}->{stats}) * $usagepct;
+ print '<td>', int $caniuse->{data}->{$id}->{usage};
}
say '<tbody>';
for my $id (sort {
- featurescore($caniuse->{data}->{$b}->{stats})
- <=> featurescore($caniuse->{data}->{$a}->{stats})
+ $caniuse->{data}->{$b}->{usage} <=> $caniuse->{data}->{$a}->{usage}
} keys %{ $caniuse->{data} }) {
$caniuse->{data}->{$id}->{stats} or next; # skip metadata [summary]
printf '<tr id="%s">', $id;
sub paddedver {
# normalised version number comparable as string (cmp)
- shift =~ /(?:.*-|^)(\d*)(.*)/;
+ $_[0] =~ m/(?:.*-|^)(\d*)(.*)/;
# matched (major)(.minor) of last value in range (a-B)
- return sprintf('%02d', $1 || 99) . $2;
+ return sprintf('%03d', length $1 ? $1 : 999) . $2;
}
sub showversions {
- my @span = ($_[0], @_>1 ? $_[-1] : ());
- s/-.*// for $span[0];
- for (@span) {
- s/^\./0./;
- s/x$/.*/;
- s/.*-//;
- }
+ # title to describe minumum version and optional maximum for multiple cells
+ my @span = (map { split /-/ } grep { defined } @_);
+ return $span[0] =~ s/\.0\z//r if @_ <= 1;
+ splice @span, 1, -1;
return join('‒', @span);
}
<div class="legend">
<table class="glyphs"><tr>
<td class="X l5">supported
+ <td class="X l4">annotated
<td class="X l3">partial
<td class="X l2">optional
<td class="X l1">missing
</div>
<script type="text/javascript" src="/searchlocal.js"></script>
-<script type="text/javascript"> prependsearch(document.getElementById('intro')) </script>
+<script type="text/javascript"><!--
+ prependsearch(document.getElementById('intro'));
+//--></script>