File Coverage

blib/lib/Acme/Perl/Consensual.pm
Criterion Covered Total %
statement 62 69 89.8
branch 29 38 76.3
condition 4 11 36.3
subroutine 13 14 92.8
pod 6 6 100.0
total 114 138 82.6


line stmt bran cond sub pod time code
1             package Acme::Perl::Consensual;
2              
3 4     4   146894 use 5;
  4         16  
  4         210  
4 4     4   24 use strict;
  4         7  
  4         199  
5 4     4   5207 use POSIX qw(mktime floor);
  4         71316  
  4         39  
6              
7             BEGIN {
8 4     4   6433 $Acme::Perl::Consensual::AUTHORITY = 'cpan:TOBYINK';
9 4         8703 $Acme::Perl::Consensual::VERSION = '0.002';
10             };
11              
12             # Mostly sourced from
13             # http://upload.wikimedia.org/wikipedia/commons/4/4e/Age_of_Consent_-_Global.svg
14             my %requirements = (
15             bo => { puberty => 1 },
16             ao => { age => 12 },
17             (map { $_ => { age => 13 } } qw(
18             ar bf es jp km kr ne
19             )),
20             (map { $_ => { age => 14 } } qw(
21             al at ba bd bg br cl cn co de
22             ec ee hr hu it li me mg mk mm
23             mo mw pt py rs sl sm td va
24             )),
25             (map { $_ => { age => 15 } } qw(
26             aw cr cw cz dk fo fr gf gl gn
27             gp gr hn is kh ki kp la mc mf
28             mq pf pl re ro sb se si sk sx
29             sy tf th tv uy vc wf
30             )),
31             (map { $_ => { age => 16 } } qw(
32             ad ag am as ax az bb be bh bm
33             bn bq bs bw by bz ca cc ch ck
34             cm cu dm dz fi fj gb ge gh gi
35             gu gw gy hk il im in je jm jo
36             ke kg kn ky kz lc lk ls lt lu
37             lv md mh mn mr ms mu my mz na
38             nf nl no np nz pg pn pr pw ru
39             sg sj sn sr sz tj tm to tt tw
40             ua um uz ve vu ws za zm zw
41             )),
42             (map { $_ => { age => 17 } } qw(
43             cy ie nr
44             )),
45             (map { $_ => { age => 18 } } qw(
46             bi bj bt cd dj do eg er et ga
47             gm gq gt ht lb lr ma ml mt ng
48             ni pa pe ph ss rw sc sd so sv
49             tr tz ug vi vn
50             )),
51             id => { age => 19 },
52             tn => { age => 20 },
53             (map { $_ => { married => 1 } } qw(
54             ae af ir kw mv om pk qa sa ye
55             )),
56             (map { $_ => undef } qw(
57             ai bl bv cf cg ci cv cx eh fk
58             fm gd gg hm io iq ly mp nc nu
59             pm ps sh st tc tg tl vg
60             )),
61             # There are US federal laws, but they're fairly complicated for a little
62             # module like this to assess, and the state laws (below) are generally
63             # more relevant.
64             us => undef,
65             (map { ;"us-$_" => { age => 16 } } qw(
66             al ak ar ct dc ga hi id ia ks
67             ky me md ma mi mn ms mt nv nh
68             nj nc oh ok ri sc sd vt wa wv
69             )),
70             (map { ;"us-$_" => { age => 17 } } qw(
71             co il la mo ne nm ny tx wy
72             )),
73             (map { ;"us-$_" => { age => 18 } } qw(
74             az ca de fl id nd or tn ut va
75             wi pa
76             )),
77             # Australian federal laws apply to Australian citizens while outside
78             # Australia; while inside Australia only state laws are relevant.
79             au => undef,
80             (map { ;"au-$_" => { age => 16 } } qw(
81             act nsw nt qld vic wa
82             )),
83             (map { ;"au-$_" => { age => 17 } } qw(
84             sa tas
85             )),
86             mx => { age => 12 },
87             (map { ;"mx-$_" => { age => 12 } } qw(
88             agu bcs cam chp coa dif gua gro
89             hid jal mic mor oax pue que roo
90             slp sin son tab
91             )),
92             (map { ;"mx-$_" => { age => 13 } } qw(
93             yuc zac
94             )),
95             (map { ;"mx-$_" => { age => 14 } } qw(
96             bcn chh col dur nle tla ver
97             )),
98             "mx-mex" => { age => 15 },
99             "mx-nay" => { puberty => 1 },
100             );
101              
102             my %perlhist;
103              
104             sub new
105             {
106 4     4 1 44 my ($class, %args) = @_;
107 4 100 50     143 $args{locale} = $ENV{LC_ALL} || $ENV{LC_LEGAL} || 'en_XX.UTF-8'
108             unless exists $args{locale};
109 4 100       131 $args{locale} = $1
110             if $args{locale} =~ /^.._(.+?)(\.|$)/;
111 4         34 bless \%args => $class;
112             }
113              
114             sub locale
115             {
116 18     18 1 68 lc shift->{locale};
117             }
118              
119             sub can
120             {
121 10 100 66 10 1 56 if (@_ == 2 and not ref $_[1])
122             {
123 1         14 shift->SUPER::can(@_);
124             }
125             else
126             {
127 9         22 shift->_can_consent(@_);
128             }
129             }
130              
131             sub _can_consent
132             {
133 9 50   9   23 my $self = ref $_[0] ? shift : shift->new;
134            
135 9 50       34 my $provides = ref $_[0] ? shift : +{@_};
136 9         21 my $requires = $requirements{ $self->locale };
137            
138             # If locale includes a region, fallback to country.
139 9 50       20 if ($self->locale =~ /^([a-z]{2})-/)
140             {
141 0   0     0 $requires ||= $requirements{ $1 };
142             }
143            
144 9 50       20 return undef unless defined $requires;
145            
146 9         25 for (keys %$requires)
147             {
148 9 100       31 return undef unless defined $provides->{$_};
149 7 100       32 return !1 unless $provides->{$_} >= $requires->{$_};
150             }
151            
152 5         126 !0;
153             }
154              
155             sub age_of_perl
156             {
157 2     2 1 6 my $class = shift;
158 2         7 return $class->age_of_perl_in_seconds(shift)
159             / 31_556_736 # 365.24 * 24 * 60 * 60
160             }
161              
162             sub age_of_perl_in_seconds
163             {
164 2     2 1 5 my ($class, $v) = @_;
165 2   33     6 $v ||= $];
166            
167             my $pl_date = do
168 2         4 {
169 2         9 $class->_perlhist;
170            
171 2         7 my $date = $perlhist{$v};
172 2 100       9 unless ($date)
173             {
174 1         208 for (sort keys %perlhist)
175             {
176 258 100       442 next if $_ lt $v; # XXX: need smarter version matching!
177 1 50       7 $date = $perlhist{$_} and last;
178             }
179             }
180 2 50       41 return unless $date;
181 2         12 $class->_parse_date($date);
182             };
183            
184 2         30 return time() - $pl_date;
185             }
186              
187             sub _parse_date
188             {
189 2     2   5 my ($class, $date) = @_;
190 2         25 my ($y, $m, $d) = split '-', $date;
191            
192 2         1425 $m = {
193             Jan => 0x00,
194             Feb => 0x01,
195             Mar => 0x02,
196             Apr => 0x03,
197             May => 0x04,
198             Jun => 0x05,
199             Jul => 0x06,
200             Aug => 0x07,
201             Sep => 0x08,
202             Oct => 0x09,
203             Nov => 0x0A,
204             Dec => 0x0B,
205             }->{$m};
206            
207 2         636 return mktime(0, 0, 0, $d, $m, $y - 1900);
208             }
209              
210             sub _perlhist
211             {
212 2 100   2   12 unless (%perlhist)
213             {
214 1         2 my $prev_date;
215 1         8 while ( )
216             {
217 440 100       2470 if (/([1-5]\.[A-Za-z0-9\._]+)\s+(\d{4}-[\?\w]{3}-[\?\d]{2})/)
218             {
219 313         608 my $vers = $1;
220 313         548 my $date = $2;
221 313         548 my @vers = ($vers);
222            
223 313 100       807 if ($vers =~ /^(\d)\.(\d{3})\.\.(\d*)/)
224             {
225 13         20 @vers = ();
226 13         49 for (my $i = $2; $i >= $3; $i++)
227             {
228 0         0 push @vers, sprintf "%d.%03d", $1, $i;
229             }
230             }
231            
232 313 100       824 if ($date =~ /\?/)
233             {
234 4         75 $date = $prev_date;
235             }
236             else
237             {
238 309         413 $prev_date = $date;
239             }
240            
241 313         2601 $perlhist{$_} = $date for @vers;
242             }
243             }
244             }
245             }
246              
247             sub perl_can
248             {
249 0     0 1 0 my $self = shift;
250 0         0 $self->can(
251             age => floor($self->age_of_perl(shift)),
252             puberty => 1,
253             );
254             }
255              
256             sub import
257             {
258 4     4   67 my $class = shift;
259            
260 4 50       7130 if (grep { $_ eq '-check' } @_)
  0            
261             {
262 0           require Carp;
263 0 0         Carp::croak("Perl $] failed age of consent check, died")
264             unless $class->new->perl_can;
265             }
266             }
267              
268             1;
269              
270             =head1 NAME
271              
272             Acme::Perl::Consensual - check that your version of Perl is old enough to consent
273              
274             =head1 DESCRIPTION
275              
276             This module checks that your version of Perl is old enough to consent to
277             sexual activity. It could be considered a counterpart for L.
278              
279             =head2 Constructor
280              
281             =over
282              
283             =item C<< new(locale => $locale) >>
284              
285             Creates a new Acme::Perl::Consensual object which can act as an age of consent
286             checker for a particular locale.
287              
288             The locale string should be an ISO 3166 alpha2 country code such as "US" for
289             the United States, "GB" for the United Kingdom or "DE" for Germany. It may
290             optionally include a hyphen followed by a subdivision designator, such as
291             "US-TX" for Texas, United States, "AU-NSW" for New South Wales, Australia or
292             "GB-WLS" for Wales, United Kingdom.
293              
294             If the locale is omitted, the module will attempt to extract the locale
295             from the LC_LEGAL or LC_ALL environment variable.
296              
297             =back
298              
299             =head2 Methods
300              
301             =over
302              
303             =item C<< locale >>
304              
305             Returns the locale provided to the constructor, or detected from environment
306             variables, lower-cased.
307              
308             =item C<< can(%details) >>
309              
310             Given a person's details (or a piece of software's details), returns true if
311             they are legally able to consent. For example:
312              
313             my $can_consent = $acme->can(age => 26, married => 1);
314              
315             Currently recognised details are 'age' (in years), 'married' (0 for no, 1 for
316             yes) and 'puberty' (0 for no, 1 for yes).
317              
318             If called with a single scalar argument, acts like UNIVERSAL::can (see
319             L).
320              
321             =item C<< age_of_perl_in_seconds($version) >>
322              
323             The age of a particular release of Perl, in seconds. (Actually we don't know
324             exactly what time of day Perl was released, so we assume midnight on the
325             release date.)
326              
327             If C<< $version >> is omitted, then the current version.
328              
329             =item C<< age_of_perl($version) >>
330              
331             As per C, but measured in years. Returns a floating
332             point. Use POSIX::floor to round down to the nearest whole number. This
333             method assumes that all years are 365.24 days long, and all days are 86400
334             (i.e. 24*60*60) seconds long.
335              
336             =item C<< perl_can($version) >>
337              
338             Shorthand for:
339              
340             $acme->can(
341             age => POSIX::floor($acme->age_of_perl($version)),
342             puberty => 1, # Perl is regarded as a mature programming language
343             );
344              
345             =back
346              
347             =head2 Import
348              
349             Passing a "-check" parameter on import:
350              
351             use Acme::Perl::Consensual -check;
352              
353             is a shorthand for:
354              
355             BEGIN {
356             require Acme::Perl::Consensual;
357             Acme::Perl::Consensual->new()->perl_can()
358             or die "Perl $] failed age of consent check, died";
359             }
360              
361             That is, it's the opposite of C<< use Modern::Perl >>. It requires your
362             version of Perl to be past the age of consent in your locale.
363              
364             =head1 CAVEATS
365              
366             Most jurisdictions have legal subtleties that this module cannot take into
367             account. Use of this module does not constitute a legal defence.
368              
369             Even if you obtain consent from Perl, there are practical limits to what you
370             could actually do with it, sexually.
371              
372             =head1 INSTALL
373              
374             While this distribution is believed to work in any version of Perl 5, it has
375             only been tested so far in Perl 5.8+. In older versions of Perl, Makefile.PL
376             may not run, but the library can be manually installed by copying
377             C<< lib/Acme/Perl/Consensual.pm >> to an appropriate location.
378              
379             =head1 BUGS
380              
381             Please report any bugs to
382             L.
383              
384             =head1 SEE ALSO
385              
386             L, L.
387              
388             =head1 AUTHOR
389              
390             Toby Inkster Etobyink@cpan.orgE, but MSCHWERN deserves at least a
391             little of the blame.
392              
393             =head1 COPYRIGHT AND LICENCE
394              
395             This software is copyright (c) 2012-2013 by Toby Inkster.
396              
397             This is free software; you can redistribute it and/or modify it under
398             the same terms as the Perl 5 programming language system itself.
399              
400             =head1 DISCLAIMER OF WARRANTIES
401              
402             THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
403             WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
404             MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
405              
406             =cut
407              
408             # Data below reproduced from `perldoc -T -t perlhist`
409              
410             __DATA__