File Coverage

blib/lib/Geo/CEP.pm
Criterion Covered Total %
statement 68 99 68.6
branch 10 38 26.3
condition 1 9 11.1
subroutine 17 21 80.9
pod 2 3 66.6
total 98 170 57.6


line stmt bran cond sub pod time code
1             package Geo::CEP;
2             # ABSTRACT: Resolve Brazilian city data for a given CEP
3              
4              
5 2     2   59330 use strict;
  2         3  
  2         66  
6 2     2   15 use utf8;
  2         4  
  2         14  
7 2     2   44 use warnings qw(all);
  2         3  
  2         80  
8              
9 2     2   1733 use integer;
  2         17  
  2         8  
10              
11 2     2   57 use Carp qw(carp confess croak);
  2         3  
  2         154  
12 2     2   1694 use File::ShareDir qw(dist_file);
  2         22880  
  2         171  
13 2     2   2581 use IO::File;
  2         23012  
  2         267  
14 2     2   2215 use Memoize;
  2         5132  
  2         103  
15 2     2   1874 use Moo;
  2         47782  
  2         15  
16 2     2   5378 use MooX::Types::MooseLike::Base qw(:all);
  2         13955  
  2         1306  
17 2     2   33 use Scalar::Util qw(looks_like_number);
  2         2  
  2         101  
18 2     2   2405 use Text::CSV;
  2         29299  
  2         20  
19              
20             our $VERSION = '1.0'; # VERSION
21              
22             has csv => (is => 'ro', isa => InstanceOf['Text::CSV'], default => sub { Text::CSV->new }, lazy => 1);
23              
24              
25             has data => (is => 'rwp', isa => FileHandle);
26             has index => (is => 'rwp', isa => FileHandle);
27              
28              
29             has length => (is => 'rwp', isa => Int, default => sub { 0 });
30              
31              
32             my %states = (
33             AC => 'Acre',
34             AL => 'Alagoas',
35             AM => 'Amazonas',
36             AP => 'Amapá',
37             BA => 'Bahia',
38             CE => 'Ceará',
39             DF => 'Distrito Federal',
40             ES => 'Espírito Santo',
41             GO => 'Goiás',
42             MA => 'Maranhão',
43             MG => 'Minas Gerais',
44             MS => 'Mato Grosso do Sul',
45             MT => 'Mato Grosso',
46             PA => 'Pará',
47             PB => 'Paraíba',
48             PE => 'Pernambuco',
49             PI => 'Piauí',
50             PR => 'Paraná',
51             RJ => 'Rio de Janeiro',
52             RN => 'Rio Grande do Norte',
53             RO => 'Rondônia',
54             RR => 'Roraima',
55             RS => 'Rio Grande do Sul',
56             SC => 'Santa Catarina',
57             SE => 'Sergipe',
58             SP => 'São Paulo',
59             TO => 'Tocantins',
60             );
61              
62             has states => (
63             is => 'ro',
64             isa => HashRef[Str],
65             default => sub { \%states }
66             );
67              
68              
69             my $idx_len = length(pack('N*', 1 .. 2));
70              
71             has idx_len => (is => 'ro', isa => Int, default => sub { $idx_len });
72              
73              
74             sub BUILD {
75 1     1 0 207 my ($self) = @_;
76              
77 1         8 $self->csv->column_names([qw(cep_initial cep_final state city ddd lat lon)]);
78              
79 1         283 my $data = IO::File->new(dist_file('Geo-CEP', 'cep.csv'), '<:encoding(latin1)');
80 1 50       15186 confess "Error opening CSV: $!" unless defined $data;
81 1         9 $self->_set_data($data);
82              
83 1         531 my $index = IO::File->new(dist_file('Geo-CEP', 'cep.idx'), O_RDONLY);
84 1 50       364 confess "Error opening index: $!" unless defined $index;
85 1         8 $self->_set_index($index);
86              
87 1 50       555 my $size = $index->sysseek(0, SEEK_END)
88             or confess "Can't tell(): $!";
89              
90 1 50 33     20 confess 'Inconsistent index size'
91             if not $size
92             or $size % $idx_len;
93 1         6 $self->_set_length($size / $idx_len);
94              
95 1         594 return;
96             }
97              
98             sub import {
99 2     2   18 my (undef, @args) = @_;
100              
101 2 100       8 if (grep { $_ eq 'memoize' } @args) {
  1         6  
102             memoize $_
103 1         5 for qw(_bsearch _fetch_row _find _get_idx);
104             }
105              
106 2         4353 return;
107             }
108             # Retorna o registro no arquivo CSV; uso interno.
109             sub _get_idx {
110 0     0   0 my ($self, $n, $want_offset) = @_;
111              
112 0         0 my $buf = '';
113 0 0       0 $self->index->sysseek($n * $idx_len, SEEK_SET)
114             or confess "Can't seek(): $!";
115              
116 0 0       0 $self->index->sysread($buf, $idx_len)
117             or confess "Can't read(): $!";
118              
119 0         0 my ($cep, $offset) = unpack 'N*' => $buf;
120 0 0       0 return defined $want_offset
121             ? $offset
122             : $cep;
123             }
124              
125             # Efetua a busca binária (implementação não-recursiva); uso interno.
126             sub _bsearch {
127 0     0   0 my ($self, $hi, $val) = @_;
128 0         0 my ($lo, $cep, $mid) = qw(0 0 0);
129              
130             return
131 0 0 0     0 if ($self->_get_idx($lo) > $val)
132             or ($self->_get_idx($hi) < $val);
133              
134 0         0 while ($lo <= $hi) {
135 0         0 $mid = ($lo + $hi) / 2;
136 0         0 $cep = $self->_get_idx($mid);
137 0 0       0 if ($val < $cep) {
    0          
138 0         0 $hi = $mid - 1;
139             } elsif ($val > $cep) {
140 0         0 $lo = $mid + 1;
141             } else {
142 0         0 last;
143             }
144             }
145              
146 0 0       0 --$mid if $cep > $val;
147 0         0 return $self->_get_idx($mid, 1);
148             }
149              
150              
151             # Lê e formata o registro a partir do cep.csv; uso interno.
152             sub _fetch_row {
153 0     0   0 my ($self, $offset) = @_;
154              
155 2     2   2058 no integer;
  2         6  
  2         15  
156              
157 0 0       0 $self->data->seek($offset, SEEK_SET)
158             or confess "Can't seek(): $!";
159              
160 0         0 my $row = $self->csv->getline_hr($self->data);
161             return
162 0 0 0     0 if 'HASH' ne ref $row
163             or not defined $row->{state};
164              
165 0 0       0 my %res = map {
166 0         0 $_ =>
167             looks_like_number($row->{$_})
168             ? 0 + sprintf('%.7f', $row->{$_})
169             : $row->{$_}
170             } qw(state city ddd lat lon cep_initial cep_final);
171 0         0 $res{state_long}= $states{$res{state}};
172              
173 0         0 return \%res;
174             }
175              
176              
177             sub _find {
178 0     0   0 my ($self, $cep) = @_;
179 0         0 my $offset = $self->_bsearch($self->length - 1, $cep);
180 0 0       0 if (defined $offset) {
181 0         0 return $self->_fetch_row($offset);
182             } else {
183 0         0 return;
184             }
185             }
186              
187             sub find {
188 9612     9612 1 11965049 my ($self, $cep) = @_;
189 9612         28295 $cep =~ s/\D//gsx;
190 9612         271213 return $self->_find(substr($cep, 0, 8));
191             }
192              
193              
194             sub list {
195 1     1 1 1770 my ($self) = @_;
196              
197 1 50       9 $self->index->sysseek(0, SEEK_SET)
198             or confess "Can't seek(): $!";
199              
200 1         10 my %list;
201             my $buf;
202 1         12 while ($self->index->sysread($buf, $idx_len)) {
203 20756         233177 my (undef, $offset) = unpack 'N*' => $buf;
204 20756         438004 my $row = $self->_fetch_row($offset);
205 20756 100       467984 $list{$row->{city} . '/' . $row->{state}} = $row
206             if defined $row;
207             }
208             $self->csv->eof
209 1 50       25 or croak $self->csv->error_diag;
210              
211 1         17 return \%list;
212             }
213              
214              
215             1;
216              
217             __END__