File Coverage

blib/lib/Gaim/Log/Parser.pm
Criterion Covered Total %
statement 132 141 93.6
branch 34 44 77.2
condition 4 8 50.0
subroutine 12 13 92.3
pod 5 7 71.4
total 187 213 87.7


line stmt bran cond sub pod time code
1             ###########################################
2             package Gaim::Log::Parser;
3             ###########################################
4 3     3   58342 use strict;
  3         6  
  3         177  
5 3     3   17 use warnings;
  3         5  
  3         81  
6 3     3   3565 use Log::Log4perl qw(:easy);
  3         1473720  
  3         23  
7 3     3   7205 use DateTime;
  3         11713106  
  3         203  
8 3     3   17939 use Gaim::Log::Message;
  3         16  
  3         127  
9 3     3   23978 use Text::Wrap qw(fill);
  3         21158  
  3         7282  
10              
11             our $VERSION = "0.14";
12              
13             ###########################################
14             sub new {
15             ###########################################
16 6     6 1 3117 my($class, @options) = @_;
17              
18 6         61 my $self = {
19             time_zone => DateTime::TimeZone->new(name => 'local'),
20             @options,
21             };
22              
23 6 50       323504 LOGDIE "Cannot open $self->{file}" unless -f $self->{file};
24              
25 6 50       335 open my $fh, "$self->{file}" or
26             LOGDIE "Cannot open $self->{file}";
27              
28 6         27 $self->{fh} = $fh;
29              
30 6         58 bless $self, $class;
31 6         30 $self->reset();
32              
33 6         45 DEBUG "Parsing logfile $self->{file}";
34              
35             # ./proto/from/to/2005-10-29.230219.txt
36 6 50       156 if($self->{file} =~ m#([^/]+)/([^/]+)/([^/]+)/([^/]+)$#) {
37 6         25 $self->{protocol} = $1;
38 6         21 $self->{from} = $2;
39 6         67 $self->{to} = $3;
40 6 50       339 if($4 =~ /(\d{4})-(\d{2})-(\d{2})\.(\d{2})(\d{2})(\d{2})/) {
41 6         209 my $dt = DateTime->new(year => $1, month => $2, day => $3,
42             hour => $4, minute => $5, second => $6,
43             time_zone => $self->{time_zone},
44             );
45 6         1513897 $self->{dt} = $dt;
46             }
47             } else {
48 0         0 LOGDIE "Please use full path information (something like ",
49             "\".../proto/from/to/2005-10-29.230219.txt\")",
50             " since ", __PACKAGE__, " uses it to generate meta data ",
51             "from it.";
52             }
53              
54 6 50       29 if($self->{offset}) {
55             # If an offset has been specified, leap ahead message
56             # by message (therefore accounting for roll-overs) until
57             # the requested offset has been reached.
58 6         13 my $offset = $self->{offset};
59 6         23 $self->{offset} = tell $self->{fh};
60 6         29 while($offset > $self->{offset}) {
61 0 0       0 $self->next_message() or last;
62             }
63             } else {
64 0         0 $self->{offset} = tell $self->{fh};
65             }
66              
67 6         51 return bless $self, $class;
68             }
69              
70             ###########################################
71             sub as_string {
72             ###########################################
73 1     1 1 359 my($self, $opts) = @_;
74              
75 1         2 my $string;
76              
77 1         3 my $fh = $self->{fh};
78 1         2 my $old_offset = $self->{offset};
79              
80 1         4 $self->reset();
81              
82 1   50     7 local $Text::Wrap::columns = ($opts->{columns} || 70);
83              
84 1         7 while(my $m = $self->next_message()) {
85 7         192 my $content = $m->content();
86 7         17 $content =~ s/\n+/ /g;
87 7         147 $string .= fill("", " ",
88             nice_time($m->date()) . " " .
89             $m->from() . ": " . $content) . "\n\n";
90             }
91              
92             # reset fh
93 1         2 $self->{offset} = $old_offset;
94 1         6 seek $fh, $self->{offset}, 0;
95              
96 1         10 return $string;
97             }
98              
99             ###########################################
100             sub next_message {
101             ###########################################
102 26     26 1 2137 my($self) = @_;
103              
104 26         50 my $fh = $self->{fh};
105 26         95 my $time_match = qr(\d{2}:\d{2}:\d{2}(?: [AP]M)?);
106 26         97 my $date_match = qr(\d{2}/\d{2}/\d{2,4});
107 26         73 my $euro_date_match = qr(\d{2}\.\d{2}\.\d{2,4});
108 26         64 my $iso_date_match = qr(\d{4}-\d{2}-\d{2});
109              
110              
111 26         333 my $line_match_with_time = qr/^\(($time_match)\) (.*)/;
112 26         195 my $line_match_with_date_and_time =
113             qr/^\(($date_match) ($time_match)\) (.*)/;
114 26         220 my $line_match_with_euro_date_and_time =
115             qr/^\(($euro_date_match) ($time_match)\) (.*)/;
116 26         182 my $line_match_with_iso_date_and_time =
117             qr/^\(($iso_date_match) ($time_match)\) (.*)/;
118 26         418 my $line_match = qr($line_match_with_time|
119             $line_match_with_date_and_time|
120             $line_match_with_euro_date_and_time|
121             $line_match_with_iso_date_and_time)x;
122              
123             # Read next line
124 26         217 my $line = <$fh>;
125              
126             # End of file?
127 26 100       76 if(! defined $line) {
128 1         7 DEBUG "End of file $self->{file}";
129 1         7 $self->{fh} = $fh;
130 1         8 return undef;
131             }
132              
133 25         30 my($time, $date, $msg, $day, $month, $year);
134              
135             # Valid line?
136 25 100       2048 if($line =~ /$line_match_with_time/) {
    100          
    100          
    50          
137 19         43 $time = $1;
138 19         38 $msg = $2;
139             } elsif($line =~ /$line_match_with_date_and_time/) {
140 3         6 $date = $1;
141 3         11 ($month, $day, $year) = split m#/#, $date;
142 3         6 $time = $2;
143 3         8 $msg = $3;
144             } elsif($line =~ /$line_match_with_euro_date_and_time/) {
145 2         4 $date = $1;
146 2         8 ($day, $month, $year) = split m#\.#, $date;
147 2         5 $time = $2;
148 2         5 $msg = $3;
149             } elsif($line =~ /$line_match_with_iso_date_and_time/) {
150 1         3 $date = $1;
151 1         4 ($year, $month, $day) = split m#-#, $date;
152 1         2 $time = $2;
153 1         2 $msg = $3;
154             } else {
155 0   0     0 while(defined $line and $line !~ /$line_match/) {
156 0         0 chomp $line;
157 0         0 LOGWARN "Format error in $self->{file}: ",
158             "Line '$line' doesn't match $line_match";
159 0         0 $line = <$fh>;
160             }
161             }
162              
163             # We accepted either 2 or 4 digit years. Hopefully there's no
164             # gaim logs from < 2000 :).
165 25 100       66 if($year) {
166 6 100       17 $year += 2000 unless length $year == 4;
167             }
168              
169 25         57 $self->{offset} = tell $fh;
170              
171             # We've got a message, let's see if there's continuation lines
172 25         165 while(defined($_ = <$fh>)) {
173 27 100       187 if(/$line_match/) {
174             # Next line doesn't look like a continuation line,
175 22         37 last;
176             }
177             # We have a continuation line.
178 5         10 chomp;
179 5         10 $msg .= "\n$_";
180 5         21 $self->{offset} = tell $fh;
181             }
182              
183             # Go back to the previous offset, before we tried searching
184             # for continuation lines
185 25         203 seek $fh, $self->{offset}, 0;
186              
187 25         39 $self->{fh} = $fh;
188              
189             # Check if we have a roll-over
190 25         126 my $dtclone = $self->{dt}->clone();
191              
192 25 100       382 if($date) {
193 6         24 $dtclone = DateTime->new(year => $year,
194             month => $month,
195             day => $day,
196             time_zone => $self->{time_zone}
197             );
198 6         2589 $self->{dt} = $dtclone;
199             }
200              
201 25         52 my $pm = 0;
202 25 100       80 if($time =~ / PM/) {
203 1         2 $pm = 1;
204             }
205 25         46 $time =~ s/ .*//;
206              
207 25         88 my($hour, $minute, $second) = split /:/, $time;
208 25         91 $dtclone->set_hour($hour);
209 25         10706 $dtclone->set_minute($minute);
210 25         9882 $dtclone->set_second($second);
211              
212 25 100       9349 if($pm) {
213 1         6 $dtclone->add(hours => 12);
214             }
215              
216 25 100 100     955 if(!$date and $dtclone->epoch() < $self->{dt}->epoch()) {
217             # Rollover detected. Adjust datetime instance variable
218 9         141 $self->{dt}->add(days => 1);
219 9         5473 $dtclone->add(days => 1);
220             }
221              
222 25         5114 my $sender = $self->{from};
223 25         45 my $receiver = $self->{to};
224              
225             # strip "from_user: " from beginning of message
226 25 50       128 if($msg =~ /^(.*?): /) {
227 25 100       98 if($1 eq $receiver) {
    100          
228             # The other party sent
229 6         16 ($sender, $receiver) = ($receiver, $sender);
230             } elsif($1 ne $sender) {
231             # A different chat user sent
232 7         15 $sender = $1;
233             }
234 25         105 $msg =~ s/^(.*?): //;
235             } else {
236             # No sender specified. This could be a message like
237             # "foo logged out.". Leave sender/receiver as is.
238             }
239              
240 25         77 DEBUG "Creating new message (date=", $dtclone->epoch(), ") msg=",
241             $msg;
242              
243 25         453 return Gaim::Log::Message->new(
244             from => $sender,
245             to => $receiver,
246             protocol => $self->{protocol},
247             content => $msg,
248             date => $dtclone->epoch(),
249             );
250             }
251              
252             ###########################################
253             sub offset {
254             ###########################################
255 0     0 0 0 my($self) = @_;
256              
257 0         0 return $self->{offset};
258             }
259              
260             ###########################################
261             sub datetime {
262             ###########################################
263 1     1 1 3 my($self) = @_;
264              
265 1         31 return $self->{dt};
266             }
267              
268             ###########################################
269             sub reset {
270             ###########################################
271 7     7 1 20 my($self) = @_;
272              
273 7         28 my $fh = $self->{fh};
274 7         221 seek $fh, 0, 0;
275              
276             # "Conversation with foo at 2005-10-29 23:02:19
277             # on bar (protocol)"
278 7         101 my $first_line = <$fh>;
279              
280 7         22 $self->{offset} = tell $fh;
281              
282 7         16 1;
283             }
284              
285             ###########################################
286             sub nice_time {
287             ###########################################
288 7     7 0 8 my($time) = @_;
289              
290 7 50       18 $time = time() unless defined $time;
291              
292 7         337 my ($sec,$min,$hour,$mday,$mon,$year,
293             $wday,$yday,$isdst) = localtime($time);
294              
295 7         188 return sprintf("%d/%02d/%02d %02d:%02d:%02d",
296             $year+1900, $mon+1, $mday,
297             $hour, $min, $sec);
298             }
299              
300             1;
301              
302             __END__