File Coverage

blib/lib/HTTP/UA/Parser.pm
Criterion Covered Total %
statement 147 203 72.4
branch 48 66 72.7
condition 6 32 18.7
subroutine 32 42 76.1
pod 5 5 100.0
total 238 348 68.3


line stmt bran cond sub pod time code
1             package HTTP::UA::Parser;
2 10     10   19877 use strict;
  10         16  
  10         346  
3 10     10   38 use warnings;
  10         13  
  10         265  
4 10     10   5866 use YAML::Tiny 'LoadFile';
  10         48921  
  10         20854  
5             our $VERSION = '0.006';
6             my ($REGEX, $PATH, $PARSER);
7             my $PACKAGE = __PACKAGE__;
8            
9             sub new {
10 0     0 1 0 my ($class, $op) = @_;
11 0         0 my ($ua, $path);
12 0 0       0 if (ref $op eq 'HASH'){
13 0         0 $path = $op->{regex};
14 0         0 $ua = $op->{ua};
15 0         0 } else { $ua = $op; }
16            
17 0 0       0 if (!$REGEX){
18 0 0       0 if ($path){
19 0   0     0 $REGEX ||= LoadFile( $path );
20             } else {
21 0         0 $PATH = HTTP::UA::Parser::Utils::getPath();
22 0         0 my $regFile;
23 0 0       0 unless (-e ($regFile = $PATH.'/regexes.yaml')){
24 0         0 die
25             "Can't find regexes.yaml file\n".
26             "you can download/update it using command line by typing\n".
27             " % ua_parser -u\n".
28             "or simply download it from\n".
29             "https://raw.githubusercontent.com/ua-parser/uap-core/master/regexes.yaml".
30             "and include it as an option when construct new HTTP::UA::Parser class\n".
31             "ex ->new({regex => '/full/path/to/regexes.yaml'})";
32             }
33 0         0 $REGEX = LoadFile( $regFile );
34             }
35             }
36            
37 0   0     0 my $self = {
38             user_agent => $ua || $ENV{HTTP_USER_AGENT},
39             path => $PATH
40             };
41            
42 0         0 return bless($self,$class);
43             }
44            
45             sub parse {
46 0     0 1 0 my $self = shift;
47 0         0 $self->{user_agent} = $_[0];
48 0         0 $self->{os} = undef;
49 0         0 $self->{ua} = undef;
50 0         0 $self->{device} = undef;
51 0         0 return $self;
52             }
53            
54             sub os {
55 0     0 1 0 my ($self) = @_;
56 0   0     0 $self->{os} ||= HTTP::UA::Parser::OS->parse($self->{user_agent});
57 0         0 return $self->{os};
58             }
59            
60             sub ua {
61 0     0 1 0 my ($self) = @_;
62 0   0     0 $self->{ua} ||= HTTP::UA::Parser::UA->parse($self->{user_agent});
63 0         0 return $self->{ua};
64             }
65            
66             sub device {
67 0     0 1 0 my ($self) = @_;
68 0   0     0 $self->{device} ||= HTTP::UA::Parser::Device->parse($self->{user_agent});
69 0         0 return $self->{device};
70             }
71            
72             ##=============================================================================
73             ## UA Package
74             ##=============================================================================
75             package HTTP::UA::Parser::UA;
76            
77 9     9   33 sub new {HTTP::UA::Parser::Base::new(@_)}
78            
79             sub parse {
80 0     0   0 my $self = shift;
81 0         0 my $ua = shift;
82 0   0     0 my $parser = $PARSER->{ua} || ($PARSER->{ua} = $self->makeParser($REGEX->{user_agent_parsers}));
83 0         0 return $parser->($ua);
84             }
85            
86             sub makeParser {
87 3     3   6 my $self = shift;
88 3         4 my $regexes = shift;
89 3         8 return HTTP::UA::Parser::Utils::makeParser($regexes, \&_makeParsers);
90             }
91            
92             sub _makeParsers {
93            
94 3     3   4 my ($obj) = shift;
95 3         16 my $regexp = $obj->{regex};
96 3         6 my $famRep = $obj->{family_replacement};
97 3         3 my $majorRep = $obj->{v1_replacement};
98 3         3 my $minorRep = $obj->{v2_replacement};
99 3         3 my $patchRep = $obj->{v3_replacement};
100 3         6 my $qr = HTTP::UA::Parser::Utils::regex($regexp);
101            
102             my $parser = sub {
103 3     3   3 my $str = shift;
104 3         23 my @m = $str =~ $qr;
105 3 50       8 if (!@m) { return undef; }
  0         0  
106 3 100       34 my $family = defined $famRep ? HTTP::UA::Parser::Utils::replace($famRep,qr/\$1/,$m[0]) : $m[0];
107 3 100       52 my $major = defined $majorRep ? $majorRep : $m[1];
108 3 100       6 my $minor = defined $minorRep ? $minorRep : $m[2];
109 3 100       5 my $patch = defined $patchRep ? $patchRep : $m[3];
110 3         9 return ($family, $major, $minor, $patch);
111 3         16 };
112            
113 3         8 return $parser;
114             }
115            
116             ##=============================================================================
117             ## OS Package
118             ##=============================================================================
119             package HTTP::UA::Parser::OS;
120            
121 8     8   37 sub new {HTTP::UA::Parser::Base::new(@_)}
122            
123             sub parse {
124 0     0   0 my $self = shift;
125 0         0 my $ua = shift;
126 0   0     0 my $parser = $PARSER->{os} || ($PARSER->{os} = $self->makeParser($REGEX->{os_parsers}));
127 0         0 return $parser->($ua);
128             }
129            
130             sub makeParser {
131 3     3   7 my $self = shift;
132 3         4 my $regexes = shift;
133 3         9 return HTTP::UA::Parser::Utils::makeParser($regexes, \&_makeParsers);
134             }
135            
136             sub _makeParsers {
137            
138 3     3   4 my ($obj) = shift;
139 3         4 my $regexp = $obj->{regex};
140 3         18 my $famRep = $obj->{os_replacement};
141 3         4 my $majorRep = $obj->{os_v1_replacement};
142 3         4 my $minorRep = $obj->{os_v2_replacement};
143 3         4 my $patchRep = $obj->{os_v3_replacement};
144 3         3 my $patchMinorRep = $obj->{os_v4_replacement};
145 3         8 my $qr = HTTP::UA::Parser::Utils::regex($regexp);
146            
147             my $parser = sub {
148 3     3   4 my $str = shift;
149 3         23 my @m = $str =~ $qr;
150 3 50       6 if (!@m) { return undef; }
  0         0  
151 3 100       12 my $family = $famRep ? HTTP::UA::Parser::Utils::replace($famRep,qr/\$1/,$m[0]) : $m[0];
152 3 100       9 my $major = defined $majorRep ? $majorRep : $m[1];
153 3 100       4 my $minor = defined $minorRep ? $minorRep : $m[2];
154 3 100       4 my $patch = defined $patchRep ? $patchRep : $m[3];
155 3 100       5 my $patchMinor = defined $patchMinorRep ? $patchMinorRep : $m[4];
156 3         11 return ($family, $major, $minor, $patch, $patchMinor);
157 3         15 };
158            
159 3         10 return $parser;
160             }
161            
162             ##=============================================================================
163             ## Device Package
164             ##=============================================================================
165             package HTTP::UA::Parser::Device;
166            
167             sub new {
168 8     8   22 my $class = shift;
169 8   100     37 my $self = {
170             family => $_[0] || 'Other',
171             brand => $_[1],
172             model => $_[2]
173             };
174 8         43 return bless($self, 'HTTP::UA::Parser::Base');
175             }
176            
177             sub toString {
178 0     0   0 my $self = shift;
179 0         0 return $self->family;
180             }
181            
182             sub parse {
183 0     0   0 my $self = shift;
184 0         0 my $ua = shift;
185 0   0     0 my $parser = $PARSER->{device} || ($PARSER->{device} = $self->makeParser($REGEX->{device_parsers}));
186 0         0 return $parser->($ua);
187             }
188            
189             sub makeParser {
190 2     2   3 my $self = shift;
191 2         4 my $regexes = shift;
192 2   50     10 my $makeParser = shift || \&_makeParsers;
193 2         5 my @parsers = map {
194 2         3 $makeParser->($_);
195 2         3 } @{$regexes};
196            
197             my $parser = sub {
198 7     7   18 my $ua = shift;
199 7         7 my @obj;
200 7         12 foreach my $parser (@parsers){
201 7         10 @obj = $parser->($ua);
202 7 100       23 return HTTP::UA::Parser::Device->new(@obj) if $obj[0];
203             }
204            
205 3         8 HTTP::UA::Parser::Device->new();
206 2         8 };
207            
208 2         5 return $parser;
209             }
210            
211             sub _makeParsers {
212 2     2   3 my ($obj) = shift;
213 2         4 my $regexp = $obj->{regex};
214 2         3 my $regexp_flag = $obj->{regex_flag};
215 2         3 my $deviceRep = $obj->{device_replacement};
216 2         3 my $brandRep = $obj->{brand_replacement};
217 2         2 my $modelRep = $obj->{model_replacement};
218 2         6 my $qr = HTTP::UA::Parser::Utils::regex($regexp, $regexp_flag);
219            
220             my $parser = sub {
221 7     7   7 my $str = shift;
222 7         43 my @m = $str =~ $qr;
223 7 100       16 if (!@m) { return undef; }
  3         5  
224 4 50       14 my $family = $deviceRep ? HTTP::UA::Parser::Utils::multiReplace($deviceRep, \@m)
    100          
225             : ($m[0] eq "1" ? undef : $m[0]);
226            
227 4 50       8 my $brand = $brandRep ? HTTP::UA::Parser::Utils::multiReplace($brandRep, \@m)
228             : undef;
229            
230 4 50       10 my $model = $modelRep ? HTTP::UA::Parser::Utils::multiReplace($modelRep, \@m)
    50          
231             : ($m[0] eq "1" ? undef : $m[0]);
232            
233 4         24 return ($family, $brand, $model);
234 2         12 };
235 2         6 return $parser;
236             }
237            
238             ##=============================================================================
239             ## Stringify Package
240             ##=============================================================================
241             package HTTP::UA::Parser::Base;
242            
243             sub new {
244 23     23   26 my $class = shift;
245 23   100     110 my $self = {
246             family => $_[0] || 'Other',
247             major => $_[1],
248             minor => $_[2],
249             patch => $_[3],
250             patchMinor => $_[4]
251             };
252 23         95 return bless($self, __PACKAGE__ );
253             }
254            
255             sub toVersionString {
256 18     18   18 my $self = shift;
257 18         21 my $output = '';
258 18 100       43 if (defined $self->{major}){
259 14         21 $output .= $self->{major};
260 14 100       26 if (defined $self->{minor}){
261 12         15 $output .= '.' . $self->{minor};
262 12 100       22 if (defined $self->{patch}) {
263 10 100       21 if (HTTP::UA::Parser::Utils::startsWithDigit($self->{patch})) { $output .= '.'; }
  8         10  
264 10         13 $output .= $self->{patch};
265 10 100       21 if (defined $self->{patchMinor}) {
266 6 100       9 if (HTTP::UA::Parser::Utils::startsWithDigit($self->{patchMinor})) { $output .= '.'; }
  4         5  
267 6         9 $output .= $self->{patchMinor};
268             }
269             }
270             }
271             }
272 18         56 return $output;
273             }
274            
275             sub toString {
276 4     4   7 my $self = shift;
277 4         10 my $suffix = $self->toVersionString();
278 4 100       14 if ($suffix){
279 2         5 $suffix = ' ' . $suffix;
280             }
281 4         10 return $self->family . $suffix;
282             }
283            
284 14     14   67 sub family { shift->{family} }
285 4     4   21 sub major { shift->{major} }
286 4     4   13 sub minor { shift->{minor} }
287 4     4   17 sub patch { shift->{patch} }
288 4     4   15 sub patchMinor { shift->{patchMinor} }
289 2     2   9 sub brand { shift->{brand} }
290 2     2   6 sub model { shift->{model} }
291            
292             ##=============================================================================
293             ## Utils Package
294             ##=============================================================================
295             package HTTP::UA::Parser::Utils;
296            
297             sub makeParser {
298 6     6   6 my $regexes = shift;
299 6         6 my $makeParser = shift;
300 6         11 my @parsers = map {
301 6         13 $makeParser->($_);
302 6         6 } @{$regexes};
303            
304             my $parser = sub {
305 6     6   636 my $ua = shift;
306 6         12 my @obj;
307 6         9 foreach my $parser (@parsers){
308 6         11 @obj = $parser->($ua);
309 6 50       35 return HTTP::UA::Parser::Base->new(@obj) if $obj[0];
310             }
311            
312 0         0 HTTP::UA::Parser::Base->new();
313 6         21 };
314            
315 6         14 return $parser;
316             }
317            
318             sub replace {
319 2     2   5 my ($stringToReplace,$expr,$replaceWith) = @_;
320 2         13 $stringToReplace =~ s/$expr/$replaceWith/;
321 2         5 return $stringToReplace;
322             }
323            
324             sub multiReplace {
325 1     1   3 my ($stringToReplace, $matches) = @_;
326 1         2 for ($stringToReplace) {
327 1         5 s{
328             \$(\d)
329             }{
330 1 50       4 defined @{$matches}[$1-1] ? @{$matches}[$1-1] : '';
  1         4  
  1         3  
331             }egx;
332 1         5 s{^\s+|\s+$}{};
333             }
334 1 50       4 if ($stringToReplace eq '') {
335 0         0 undef $stringToReplace;
336             }
337 1         2 return $stringToReplace;
338             }
339            
340             # precompile regex
341             sub regex {
342 8     8   13 my ($expr, $flag) = @_;
343 8   50     32 $flag = $flag || '';
344 8 50       19 if ($flag eq "i") {
345 0         0 return qr{$expr}i;
346             }
347 8         127 return qr{$expr};
348             }
349            
350             sub startsWithDigit {
351 16     16   14 my $str = shift;
352 16         55 return $str =~ /^\d/;
353             }
354            
355             sub getPath {
356 0     0     $PATH = $PACKAGE;
357 0           $PATH =~ s/::/\//g;
358 0           $PATH .= '.pm';
359 0           $PATH = $INC{$PATH};
360 0           $PATH =~ s/.pm$//;
361 0           return $PATH;
362             }
363            
364             1;
365            
366             __END__