File Coverage

blib/lib/Data/Freq/Record.pm
Criterion Covered Total %
statement 67 71 94.3
branch 29 36 80.5
condition n/a
subroutine 13 13 100.0
pod 6 6 100.0
total 115 126 91.2


line stmt bran cond sub pod time code
1 5     5   23111 use 5.006;
  5         19  
  5         205  
2 5     5   480 use strict;
  5         13  
  5         173  
3 5     5   27 use warnings;
  5         19  
  5         215  
4              
5             package Data::Freq::Record;
6              
7             =head1 NAME
8              
9             Data::Freq::Record - Represents a record added to Data::Freq counting
10              
11             =cut
12              
13 5     5   33 use base 'Exporter';
  5         10  
  5         544  
14 5     5   29 use Carp qw(croak);
  5         10  
  5         296  
15 5     5   937 use Date::Parse qw(str2time);
  5         9133  
  5         5208  
16              
17             our @EXPORT_OK = qw(logsplit);
18              
19             =head1 EXPORT
20              
21             =head2 logsplit
22              
23             Splits a text that represents a line in a log file.
24              
25             use Data::Freq::Record qw(logsplit);
26            
27             logsplit("12.34.56.78 - user1 [01/Jan/2012:01:02:03 +0000] "GET / HTTP/1.1" 200 44");
28            
29             # Returns an array with:
30             # [0]: '12.34.56.78'
31             # [1]: '-'
32             # [2]: '[01/Jan/2012:01:02:03 +0000]'
33             # [3]: '"GET / HTTP/1.1"'
34             # [4]: '200'
35             # [5]: '44'
36              
37             A log line is typically whitespace-separated, while anything inside
38             brackets C<[...]>, braces C<{...}>, parentheses C<(...)>, double quotes C<"...">,
39             or single quotes C<'...'> is considered as one chunk as a whole
40             even if whitespaces may be included inside.
41              
42             The C function is intended to split such a log line into an array.
43              
44             =cut
45              
46             sub logsplit {
47 34     34 1 767 my $log = shift;
48 34         55 my @ret = ();
49            
50 34         699 push @ret, $1 while $log =~ m/ (
51             " (?: \\" | "" | [^"] )* " |
52             ' (?: \\' | '' | [^'] )* ' |
53             \[ (?: \\[\[\]] | \[\[ | \]\] | [^\]] )* \] |
54             \( (?: \\[\(\)] | \(\( | \)\) | [^\)] )* \) |
55             \{ (?: \\[\{\}] | \{\{ | \}\} | [^\}] )* \} |
56             \S+
57             ) /gx;
58            
59 34         184 return @ret;
60             }
61              
62             =head1 METHODS
63              
64             =head2 new
65              
66             Usage:
67              
68             # Text
69             Data::Freq::Record->new("text");
70            
71             Data::Freq::Record->new("an input line from a log file\n");
72             # Line break at the end will be stripped off
73            
74             # Array ref
75             Data::Freq::Record->new(['an', 'array', 'ref']);
76            
77             # Hash ref
78             Data::Freq::Record->new({key => 'hash ref'});
79              
80             Constructs a record object, which carries an input data
81             in the form of a text, an array ref, or a hash ref.
82             Each form of the input (or a converted value) can be retrieved
83             by the L, L, or L function.
84              
85             When an array ref is required via the L() method
86             while a text is given as the input, the array ref is created internally
87             by the L function.
88              
89             When a text is required via the L method
90             while an array ref is given as the input, the text is taken
91             from the first element of the array.
92              
93             The hash form is incompatible with the other forms, and whenever an incompatible
94             form is required, the return value is C.
95              
96             If the text input has a line break at the end, it is stripped off.
97             If the line break should not be stripped off, use an array ref with the first element
98             set to the text.
99              
100             =cut
101              
102             sub new {
103 186     186 1 6049 my ($class, $input) = @_;
104            
105 186         955 my $self = bless {
106             init => undef,
107             text => undef,
108             array => undef,
109             hash => undef,
110             date => undef,
111             date_tried => 0,
112             }, $class;
113            
114 186 50       5779 if (!defined $input) {
    100          
    100          
    50          
115 0         0 $self->{text} = '';
116 0         0 $self->{init} = 'text';
117             } elsif (!ref $input) {
118 121         237 $input =~ s/\r?\n$//;
119 121         218 $self->{text} = $input;
120 121         205 $self->{init} = 'text';
121             } elsif (ref $input eq 'ARRAY') {
122 60         105 $self->{array} = $input;
123 60         117 $self->{init} = 'array';
124             } elsif (ref $input eq 'HASH') {
125 5         13 $self->{hash} = $input;
126 5         11 $self->{init} = 'hash';
127             } else {
128 0         0 croak "invalid argument type: ".ref($input);
129             }
130            
131 186         642 return $self;
132             }
133              
134             =head2 text
135              
136             Retrieves the text form of the input.
137              
138             If the input was an array ref, the first element of the array is returned.
139              
140             =cut
141              
142             sub text {
143 98     98 1 128 my $self = shift;
144 98 100       450 return $self->{text} if defined $self->{text};
145            
146 4 100       16 if (defined $self->{array}) {
147 2         6 $self->{text} = $self->{array}[0];
148 2         10 return $self->{text};
149             }
150            
151 2         24 return undef;
152             }
153              
154             =head2 array
155              
156             Retrieves the array ref form of the input.
157              
158             If the input was a text, it is split by the L function..
159              
160             =cut
161              
162             sub array {
163 158     158 1 202 my $self = shift;
164 158 100       710 return $self->{array} if defined $self->{array};
165            
166 30 100       82 if (defined $self->{text}) {
167 28         62 $self->{array} = [logsplit $self->{text}];
168 28         135 return $self->{array};
169             }
170            
171 2         10 return undef;
172             }
173              
174             =head2 hash
175              
176             Retrieves the hash ref form of the input.
177              
178             =cut
179              
180             sub hash {
181 5     5 1 8 my $self = shift;
182 5 100       31 return $self->{hash} if defined $self->{hash};
183 2         8 return undef;
184             }
185              
186             =head2 date
187              
188             Extracts a date/time from the input and returns the timestamp value.
189              
190             The date/time is retrieved from the array ref form (or from a split text),
191             where the first element enclosed by a pair of brackets C<[...]> is
192             parsed by the L function.
193              
194             =cut
195              
196             sub date {
197 18     18 1 41 my $self = shift;
198 18 50       53 return $self->{date} if $self->{date_tried};
199            
200 18         31 $self->{date_tried} = 1;
201            
202 18 50       40 my $array = $self->array or return undef;
203            
204 18 100       68 if (my $pos = shift) {
205 1         6 my $str = "@$array[@$pos]";
206 1         3 $str =~ s/^ \[ (.*) \] $/$1/x;
207 1 50       6 return $self->{date} = $str if $str !~ /\D/;
208 1         3 return $self->{date} = _str2time($str);
209             }
210            
211 17         34 for my $item (@$array) {
212 44 100       152 if ($item =~ /^ \[ (.*) \] $/x) {
213 17         39 my $t = _str2time($1);
214 17 50       154 return $self->{date} = $t if defined $t;
215             }
216             }
217            
218 0         0 return undef;
219             }
220              
221             sub _str2time {
222 18     18   38 my $str = shift;
223            
224 18 100       62 my $msec = $1 if $str =~ s/[,\.](\d+)$//;
225 18         56 my $t = str2time($str);
226 18 50       4118 return undef unless defined $t;
227            
228 18 100       47 $t += "0.$msec" if $msec;
229 18         89 return $t;
230             }
231              
232             =head1 AUTHOR
233              
234             Mahiro Ando, C<< >>
235              
236             =head1 LICENSE AND COPYRIGHT
237              
238             Copyright 2012 Mahiro Ando.
239              
240             This program is free software; you can redistribute it and/or modify it
241             under the terms of either: the GNU General Public License as published
242             by the Free Software Foundation; or the Artistic License.
243              
244             See http://dev.perl.org/licenses/ for more information.
245              
246             =cut
247              
248             1;