File Coverage

blib/lib/Geo/Region.pm
Criterion Covered Total %
statement 69 69 100.0
branch 8 8 100.0
condition n/a
subroutine 18 18 100.0
pod 3 4 75.0
total 98 99 98.9


line stmt bran cond sub pod time code
1             package Geo::Region;
2              
3 2     2   163489 use v5.8.1;
  2         6  
  2         83  
4 2     2   9 use utf8;
  2         5  
  2         12  
5 2     2   42 use Carp qw( carp );
  2         7  
  2         100  
6 2     2   9 use Scalar::Util qw( looks_like_number weaken );
  2         2  
  2         93  
7 2     2   8 use List::Util qw( all any );
  2         2  
  2         207  
8 2     2   1090 use Moo;
  2         24156  
  2         10  
9              
10             our $VERSION = '0.05';
11              
12             my %children_of = (
13             # regions of subregions
14             '001' => [qw( 002 009 019 142 150 )],
15             '002' => [qw( 011 014 015 017 018 )],
16             '003' => [qw( 013 021 029 )],
17             '009' => [qw( 053 054 057 061 QO )],
18             '019' => [qw( 003 005 013 021 029 419 )],
19             '142' => [qw( 030 034 035 143 145 )],
20             '150' => [qw( 039 151 154 155 EU )],
21             '419' => [qw( 005 013 029 )],
22             # regions of countries and territories
23             '005' => [qw( AR BO BR CL CO EC FK GF GY PE PY SR UY VE )],
24             '011' => [qw( BF BJ CI CV GH GM GN GW LR ML MR NE NG SH SL SN TG )],
25             '013' => [qw( BZ CR GT HN MX NI PA SV )],
26             '014' => [qw( BI DJ ER ET KE KM MG MU MW MZ RE RW SC SO TZ UG YT ZM ZW )],
27             '015' => [qw( DZ EA EG EH IC LY MA SD SS TN )],
28             '017' => [qw( AO CD CF CG CM GA GQ ST TD ZR )],
29             '018' => [qw( BW LS NA SZ ZA )],
30             '021' => [qw( BM CA GL PM US )],
31             '029' => [qw( AG AI AN AW BB BL BQ BS CU CW DM DO GD GP HT JM KN KY LC MF MQ MS PR SX TC TT VC VG VI )],
32             '030' => [qw( CN HK JP KP KR MN MO TW )],
33             '034' => [qw( AF BD BT IN IR LK MV NP PK )],
34             '035' => [qw( BN BU ID KH LA MM MY PH SG TH TL TP VN )],
35             '039' => [qw( AD AL BA CS ES GI GR HR IT ME MK MT PT RS SI SM VA XK YU )],
36             '053' => [qw( AU NF NZ )],
37             '054' => [qw( FJ NC PG SB VU )],
38             '057' => [qw( FM GU KI MH MP NR PW )],
39             '061' => [qw( AS CK NU PF PN TK TO TV WF WS )],
40             '143' => [qw( KG KZ TJ TM UZ )],
41             '145' => [qw( AE AM AZ BH CY GE IL IQ JO KW LB NT OM PS QA SA SY TR YD YE )],
42             '151' => [qw( BG BY CZ HU MD PL RO RU SK SU UA )],
43             '154' => [qw( AX DK EE FI FO GB GG IE IM IS JE LT LV NO SE SJ )],
44             '155' => [qw( AT BE CH DD DE FR FX LI LU MC NL )],
45             'EU' => [qw( AT BE BG CY CZ DE DK EE ES FI FR GB GR HR HU IE IT LT LU LV MT NL PL PT RO SE SI SK )],
46             'QO' => [qw( AC AQ BV CC CP CX DG GS HM IO TA TF UM )],
47             );
48              
49             # codes excluded from country list due to being deprecated or grouping container
50             my %noncountries = map { $_ => 1 } qw(
51             AN BU CS DD FX NT SU TP YD YU ZR
52             EU QO
53             );
54              
55             # deprecated aliases
56             my %alias_of = (
57             UK => 'GB',
58             QU => 'EU',
59             );
60              
61             sub coerce_regions {
62             map { $alias_of{$_} || $_ }
63             map { looks_like_number $_ ? sprintf('%03d', $_) : uc }
64             grep { defined }
65             map { ref eq 'ARRAY' ? @$_ : $_ } @_
66             }
67              
68 2     2   4376 use namespace::clean;
  2         20316  
  2         11  
69              
70             has _includes => (
71             is => 'ro',
72             coerce => sub { [ coerce_regions(shift) ] },
73             default => sub { [] },
74             init_arg => 'include',
75             );
76              
77             has _excludes => (
78             is => 'ro',
79             coerce => sub { [ coerce_regions(shift) ] },
80             default => sub { [] },
81             init_arg => 'exclude',
82             );
83              
84             has _children => (
85             is => 'lazy',
86             builder => sub {
87 10     10   499 my $self = shift;
88 10         9 my $build_children;
89              
90 49         80 $build_children = sub { map {
91 69     69   74 $_, exists $children_of{$_}
92 592 100       767 ? $build_children->(@{$children_of{$_}})
93             : ()
94 10         42 } @_ };
95              
96 29         38 my %excludes = map { $_ => 1 }
  10         33  
97 10         16 $build_children->(@{$self->_excludes});
98              
99 507         668 my %children = map { $_ => 1 }
  563         476  
100 10         25 grep { !exists $excludes{$_} }
101 10         16 $build_children->(@{$self->_includes});
102              
103 10         85 weaken $build_children;
104 10         70 return \%children;
105             },
106             );
107              
108             has _parents => (
109             is => 'lazy',
110             builder => sub {
111 7     7   383 my @regions = @{shift->_includes};
  7         30  
112 7         11 my ($build_parents, %count);
113              
114 28         32 $build_parents = sub { map {
115 35     35   111 my $region = $_;
116             $region, $build_parents->(grep {
117 28         259 any { $_ eq $region } @{$children_of{$_}}
  896         1654  
  9240         8212  
  896         1558  
118             } keys %children_of);
119 7         35 } @_ };
120              
121 16         28 my %parents = map { $_ => 1 }
  28         50  
122 7         21 grep { ++$count{$_} == @regions }
123             $build_parents->(@regions);
124              
125 7         44 weaken $build_parents;
126 7         69 return \%parents;
127             },
128             );
129              
130             has _countries => (
131             is => 'lazy',
132             builder => sub { [
133             sort
134 351 100       1196 grep { /^[A-Z]{2}$/ && !exists $noncountries{$_} }
  9         134  
135 9     9   462 keys %{shift->_children}
136             ] },
137             );
138              
139             sub BUILDARGS {
140 11     11 0 22275 my ($class, @args) = @_;
141              
142             # the `include` key is optional for the first argument
143 11 100       59 my %args = @args % 2 ? (include => @args) : @args;
144              
145 11 100       38 if (exists $args{region}) {
146 1         24 carp 'Argument "region" is deprecated; use "include" instead';
147 1         543 $args{include} = delete $args{region};
148             }
149              
150 11         219 return \%args;
151             }
152              
153             sub contains {
154 38     38 1 17240 my ($self, @regions) = @_;
155 38     45   182 return all { exists $self->_children->{$_} } coerce_regions(@regions);
  45         1159  
156             }
157              
158             sub is_within {
159 19     19 1 3444 my ($self, @regions) = @_;
160 19     29   93 return all { exists $self->_parents->{$_} } coerce_regions(@regions);
  29         801  
161             }
162              
163             sub countries {
164 9     9 1 1880 my ($self) = @_;
165 9         10 return @{$self->_countries};
  9         182  
166             }
167              
168             1;
169              
170             __END__