File Coverage

blib/lib/Geo/Region.pm
Criterion Covered Total %
statement 68 68 100.0
branch 10 10 100.0
condition 3 3 100.0
subroutine 16 16 100.0
pod 3 4 75.0
total 100 101 99.0


line stmt bran cond sub pod time code
1             package Geo::Region;
2              
3 2     2   132130 use v5.8.1;
  2         4  
  2         62  
4 2     2   7 use utf8;
  2         5  
  2         9  
5 2     2   33 use Carp qw( carp );
  2         6  
  2         111  
6 2     2   8 use Scalar::Util qw( looks_like_number weaken );
  2         2  
  2         68  
7 2     2   7 use List::Util qw( all any );
  2         2  
  2         130  
8 2     2   838 use Moo;
  2         20917  
  2         9  
9              
10             our $VERSION = '0.07';
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             QU => 'EU',
58             UK => 'GB',
59             );
60              
61             sub coerce_region {
62             my ($region) = @_;
63              
64             return sprintf('%03d', $region)
65             if looks_like_number $region;
66              
67             return $alias_of{uc $region}
68             || uc $region;
69             }
70              
71             sub coerce_regions {
72             my ($regions) = @_;
73              
74             return [
75             map { coerce_region($_) }
76             ref $regions eq 'ARRAY' ? @$regions : $regions
77             ];
78             }
79              
80 2     2   4904 use namespace::clean;
  2         17229  
  2         9  
81              
82             has _includes => (
83             is => 'ro',
84             coerce => sub { coerce_regions(shift) },
85             default => sub { [] },
86             init_arg => 'include',
87             );
88              
89             has _excludes => (
90             is => 'ro',
91             coerce => sub { coerce_regions(shift) },
92             default => sub { [] },
93             init_arg => 'exclude',
94             );
95              
96             has _children => (
97             is => 'lazy',
98             builder => sub {
99 11     11   318 my $self = shift;
100 11         8 my $build_children;
101              
102 50         68 $build_children = sub { map {
103 72     72   48 $_, exists $children_of{$_}
104 596 100       669 ? $build_children->(@{$children_of{$_}})
105             : ()
106 11         32 } @_ };
107              
108 29         29 my %excludes = map { $_ => 1 }
  11         22  
109 11         11 $build_children->(@{$self->_excludes});
110              
111 511         549 my %children = map { $_ => 1 }
  567         455  
112 11         17 grep { !exists $excludes{$_} }
113 11         11 $build_children->(@{$self->_includes});
114              
115 11         87 weaken $build_children;
116 11         44 return \%children;
117             },
118             );
119              
120             has _parents => (
121             is => 'lazy',
122             builder => sub {
123 7     7   305 my @regions = @{shift->_includes};
  7         19  
124 7         5 my ($build_parents, %count);
125              
126 28         22 $build_parents = sub { map {
127 35     35   71 my $region = $_;
128             $region, $build_parents->(grep {
129 28         81 any { $_ eq $region } @{$children_of{$_}}
  896         1002  
  9240         5829  
  896         1053  
130             } keys %children_of);
131 7         22 } @_ };
132              
133 16         22 my %parents = map { $_ => 1 }
  28         32  
134 7         13 grep { ++$count{$_} == @regions }
135             $build_parents->(@regions);
136              
137 7         30 weaken $build_parents;
138 7         23 return \%parents;
139             },
140             );
141              
142             has _countries => (
143             is => 'lazy',
144             builder => sub { [
145             sort
146 355 100       995 grep { /^[A-Z]{2}$/ && !exists $noncountries{$_} }
  10         119  
147 10     10   320 keys %{shift->_children}
148             ] },
149             );
150              
151             sub BUILDARGS {
152 12     12 0 14701 my ($class, @args) = @_;
153              
154             # constructor arguments passed as hashref
155 12 100 100     52 return $args[0]
156             if @args == 1
157             && ref $args[0] eq 'HASH';
158              
159             # the `include` key is optional for the first argument
160 11 100       31 my %args = @args % 2 ? (include => @args) : @args;
161              
162 11 100       23 if (exists $args{region}) {
163 1         18 carp 'Argument "region" is deprecated; use "include" instead';
164 1         408 $args{include} = delete $args{region};
165             }
166              
167 11         157 return \%args;
168             }
169              
170             sub contains {
171 37     37 1 5358 my ($self, $region) = @_;
172 37         677 return exists $self->_children->{ coerce_region($region) };
173             }
174              
175             sub is_within {
176 17     17 1 55 my ($self, $region) = @_;
177 17         268 return exists $self->_parents->{ coerce_region($region) };
178             }
179              
180             sub countries {
181 10     10 1 285 my ($self) = @_;
182 10         10 return @{$self->_countries};
  10         189  
183             }
184              
185             1;
186              
187             __END__