File Coverage

blib/lib/App/ygeo.pm
Criterion Covered Total %
statement 89 119 74.7
branch 26 52 50.0
condition 7 38 18.4
subroutine 15 18 83.3
pod 1 2 50.0
total 138 229 60.2


line stmt bran cond sub pod time code
1             # ABSTRACT: Extract companies data from Yandex Maps to csv file
2              
3             package App::ygeo;
4             $App::ygeo::VERSION = '0.02';
5              
6              
7 1     1   58384 use strict;
  1         2  
  1         21  
8 1     1   4 use warnings;
  1         2  
  1         20  
9 1     1   503 use Text::CSV;
  1         14183  
  1         31  
10 1     1   6 use Carp;
  1         1  
  1         34  
11 1     1   326 use Yandex::Geo;
  1         39859  
  1         26  
12 1     1   6 use Yandex::Geo::Company;
  1         2  
  1         15  
13 1     1   3 use utf8;
  1         2  
  1         2  
14              
15 1     1   18 use feature 'say';
  1         2  
  1         444  
16              
17             $SIG{__DIE__} = sub {
18             my $trace = Carp::longmess( $_[0] );
19             $trace =~ s/^(.+\n)\s*at\s+.+\s+line\s+\d+\.\s*\n/$1/;
20             die $trace;
21             };
22              
23             sub new {
24 0     0 0 0 my ( $class, %params ) = @_;
25             croak "No yandex maps api key provided"
26 0 0 0     0 unless defined $params{apikey} && length $params{apikey};
27 0         0 bless {%params}, $class;
28             }
29              
30             # Split array-like properties into separate column names
31             # Used for generation csv header or filling data
32              
33             # it's impossible to analyse real data cause it can be undef
34              
35             # Return
36             # array with new column names if column with array data must be splited to separate columns
37             # or just string with column name in other cases
38              
39             # Only properties with ARRAY definition can be splited
40              
41             sub _col_names_arr_if_split {
42 52     52   968 my ( $p, $properties, $split_columns ) = @_;
43              
44             # Return name of column if its type is string
45 52 100       63 if ( _isin( $p, $properties->{string} ) ) {
    50          
46 41         51 return $p;
47             }
48             elsif ( _isin( $p, $properties->{array} ) ) {
49              
50 11 100       17 if ( defined $split_columns->{$p} ) {
51              
52             # Return name of column if column_split value is 1
53 6 100       17 return $p if ( $split_columns->{$p} == 1 );
54              
55 3         4 my @a;
56 3         6 for my $i ( 1 .. $split_columns->{$p} ) {
57 9         16 push @a, $p . '_' . $i;
58             }
59              
60             # Return array of new columns names if corresponded column_split
61 3         8 return \@a;
62             }
63              
64             # Return name of column if its type is array
65 5         10 return $p;
66              
67             }
68             else {
69 0         0 croak
70             'Properties hash is passed bad, please check consistency of string and array keys';
71             }
72             }
73              
74             # Simple printing without header and each array-type property (e.g. phones and linkls) of L in newline
75             # Uses L
76             sub _print {
77 0     0   0 my ( $text_csv, $fh, $items_array ) = @_;
78              
79 0         0 for my $company (@$items_array) {
80 0         0 $text_csv->print( $fh, $company->to_array );
81             }
82              
83 0         0 return 1;
84             }
85              
86             # Print with headers and split phones column_split
87             # Order is like in L C<{all}>
88             # Return 0 if data in rows may be inconsistent
89              
90             sub _print2 {
91 2     2   2148 my ( $text_csv, $fh, $items_array, $columns_split ) = @_;
92              
93 2         4 my $result_flag = 1;
94 2 50       6 croak "Empty result, nothing to print" unless scalar @$items_array > 0;
95              
96 2 50       3 $columns_split = { phones => 3 } unless defined $columns_split;
97 2         7 my $properties = Yandex::Geo::Company->properties;
98              
99             # TO-DO: + validate_properties
100              
101             # Print header
102 2         19 my @header;
103 2         3 for my $p ( @{ $properties->{all} } ) {
  2         3  
104 24         26 my $cols = _col_names_arr_if_split( $p, $properties, $columns_split );
105 24 100       38 push @header, $cols if ( ref $cols eq '' );
106 24 100       33 push @header, @$cols if ( ref $cols eq 'ARRAY' );
107             }
108 2         7 $text_csv->print( $fh, \@header );
109              
110             # Print data
111 1     1   5 no warnings 'utf8';
  1         2  
  1         554  
112 2         8 for my $company (@$items_array) {
113              
114 2         3 my @row;
115              
116 2         2 for my $p ( @{ $properties->{all} } ) {
  2         3  
117              
118 24         28 my $columns =
119             _col_names_arr_if_split( $p, $properties, $columns_split );
120              
121             # Case: string column, e.g. phones
122 24 100 100     308 if ( ref($columns) eq '' && ref( $company->$p ) eq '' ) {
123 20         285 push @row, $company->$p;
124              
125             # if defined $company->$p;
126             # push @row, undef unless defined $company->$p; # correct processing of empty values
127 20         69 next;
128             }
129              
130             # Case: column with array datatype but not splited, e.g. links
131 4 100 66     50 if ( ref($columns) eq '' && ref( $company->$p ) eq 'ARRAY' ) {
132              
133             # you can define custom post-processors like
134 3 100       16 if ( $p eq 'links' ) {
135 2         2 push @row, scalar @{ $company->$p };
  2         21  
136 2         7 next;
137             }
138              
139 1         2 push @row, join( "\n", @{ $company->$p } );
  1         24  
140              
141             # if defined $company->$p;
142             # push @row, undef unless defined $company->$p;
143 1         7 next;
144             }
145              
146             # Case: splited column
147 1 50 33     16 if ( ref($columns) eq 'ARRAY' && ref( $company->$p ) eq 'ARRAY' ) {
148 1         5 my $fact_size = scalar @{ $company->$p }; # check to not
  1         12  
149 1         4 my $max_size_acc_split = scalar @$columns;
150              
151 1         2 my $size = _lower( $max_size_acc_split, $fact_size );
152 1         14 push @row, $company->$p->[ $_ - 1 ] for ( 1 .. $size );
153              
154 1 50       29 if ( $fact_size < $max_size_acc_split ) {
155 0         0 my $l = $max_size_acc_split - $fact_size;
156 0         0 push @row, undef for ( 1 .. $l );
157             }
158 1         3 next;
159             }
160              
161             }
162              
163 2 50       5 if ( scalar @header != scalar @row ) {
164 0         0 carp "Row may be formatted wrong, there must be "
165             . scalar @header
166             . " columns, but it is "
167             . scalar @row;
168 0         0 $result_flag = 0;
169             }
170              
171 2         14 $text_csv->print( $fh, \@row );
172              
173             }
174              
175 2         14 return $result_flag;
176             }
177              
178             sub _isin($$) {
179 63     63   63 my ( $val, $array_ref ) = @_;
180              
181 63 50 33     132 return 0 unless $array_ref && defined $val;
182 63         69 for my $v (@$array_ref) {
183 352 100       429 return 1 if $v eq $val;
184             }
185              
186 11         18 return 0;
187             }
188              
189             # Return lower value from two values
190             sub _lower {
191 2     2   1472 my ( $val1, $val2 ) = @_;
192 2 50       9 return ( $val1 > $val2 ) ? $val2 : $val1;
193             }
194              
195              
196             sub get_and_print {
197 0     0 1   my ( $self, %params ) = @_;
198              
199 0           my $text = $params{text};
200 0 0 0       croak "No search text defined" unless defined $text && length $text;
201              
202 0   0       my $city = $self->{city} || $params{city};
203 0   0       my $csv_filename = $params{csv_filename} || $params{text} . '.csv';
204              
205 0 0         my $csv = Text::CSV->new()
206             or die "Cannot use CSV: " . Text::CSV->error_diag();
207 0           $csv->eol("\012");
208 0           $csv->sep_char(";");
209              
210 0 0         open my $fh, ">:encoding(utf8)", $csv_filename or die "$csv_filename: $!";
211              
212             my $yndx_geo = Yandex::Geo->new(
213             apikey => $self->{apikey},
214             only_city => $city,
215 0   0       results => $params{results_limit} || 500
216             );
217              
218 0           my $res = $yndx_geo->y_companies($text);
219              
220 1     1   13 no warnings 'utf8';
  1         2  
  1         88  
221             say "Search: $text in city: $city"
222 0 0 0       if ( $self->{verbose} || $params{verbose} );
223             say "Yandex Maps API key: $self->{apikey}"
224 0 0 0       if ( $self->{verbose} || $params{verbose} );
225             say "Companies found: " . scalar @$res
226 0 0 0       if ( $self->{verbose} || $params{verbose} );
227              
228 0           _print2( $csv, $fh, $res, { phones => 3 } );
229              
230 0 0         close $fh or die "$csv_filename: $!";
231              
232 1     1   5 no warnings 'utf8';
  1         1  
  1         104  
233             say "Data was written in $csv_filename"
234 0 0 0       if ( $self->{verbose} || $params{verbose} );
235              
236 0           return 1;
237             }
238              
239             1;
240              
241             __END__