File Coverage

blib/lib/Data/Header/Fields.pm
Criterion Covered Total %
statement 228 234 97.4
branch 81 100 81.0
condition 16 32 50.0
subroutine 43 44 97.7
pod 0 15 0.0
total 368 425 86.5


line stmt bran cond sub pod time code
1             package Data::Header::Fields;
2              
3 5     5   224570 use warnings;
  5         16  
  5         256  
4 5     5   24 use strict;
  5         6  
  5         136  
5              
6 5     5   1526 use IO::Any;
  5         63943  
  5         143  
7 5     5   50 use Carp 'croak';
  5         6  
  5         321  
8 5     5   3309 use String::Escape ();
  5         29974  
  5         253  
9 5     5   54 use List::MoreUtils 'uniq', 'all';
  5         8  
  5         50  
10              
11             use overload
12 5         57 '""' => \&as_string,
13             'cmp' => \&cmp,
14 5     5   2860 ;
  5         8  
15              
16             our $VERSION = '0.05';
17              
18             sub new {
19 24     24 0 52 my $class = shift;
20             return bless {
21             tight_folding => 0,
22 66     66   231 key_cmp => sub { $_[0] cmp $_[1] },
23             @_
24 24         395 }, $class;
25             }
26              
27             sub _lines {
28 194     194   243 my $self = shift;
29            
30 194 100       469 $self->{_lines} = shift
31             if (@_);
32            
33 194 100       441 $self->{_lines} = []
34             if (not $self->{_lines});
35            
36 194         539 return $self->{_lines};
37             }
38              
39             sub key_cmp {
40 62     62 0 99 my $self = shift;
41 62 50       1659 $self->{'key_cmp'} = shift
42             if @_;
43            
44 62         155 return $self->{'key_cmp'};
45             }
46              
47             sub tight_folding {
48 158     158 0 213 my $self = shift;
49            
50 158 50       293 $self->{tight_folding} = shift
51             if (@_);
52            
53 158 100       436 return 0
54             if not ref $self;
55            
56 93         310 return $self->{tight_folding};
57             }
58              
59             sub decode {
60 17     17 0 50 my $self = shift;
61 17         31 my $any = shift;
62            
63 17         250 my @lines = $self->_read_lines($any);
64              
65 17 100 50     319 my $line_ending = (
66             ($lines[0] || '') =~ m/\r\n \Z/xms
67             ? "\r\n"
68             : "\n"
69             );
70 17         87 $self->line_ending($line_ending);
71            
72 101         312 @lines = (
73             map {
74 17         42 Data::Header::Fields::Line->new(
75             'line' => $_,
76             'parent' => $self,
77             );
78             } @lines
79             );
80            
81 17 100       65 if (ref $self) {
82 16         90 $self->_lines(\@lines);
83 16         97 return $self;
84             }
85            
86 1         5 return \@lines;
87             }
88              
89             sub _read_lines {
90 11     11   20 my $self = shift;
91 11         16 my $any = shift;
92            
93 11         87 my $fh = IO::Any->read($any);
94            
95             # put folded lines to an array http://tools.ietf.org/html/rfc2822#section-2.2.3
96 11         1023 my @lines;
97 11         64 while (my $line = <$fh>) {
98             # folded line
99 23 100       658 if (($line =~ m/^\s/xms)) {
100             # ignore if the first line starts with white space
101 6 50       21 next if not @lines;
102            
103 6         16 $lines[-1] .= $line;
104 6         29 next;
105             }
106 17         74 push @lines, $line;
107             }
108            
109 11         157 close $fh;
110              
111 11         147 return @lines;
112             }
113              
114             *as_string = *encode;
115             sub encode {
116 35     35 0 38143 my $self = shift;
117 35   66     150 my $lines = shift || (ref $self ? $self->_lines : undef);
118            
119             # no additional arguments
120 35 100       87 if (@_ == 0) {
121 17         28 my $text = '';
122 17         66 $self->encode($lines, \$text);
123 17         211 return $text;
124             }
125            
126 18         23 my $any = shift;
127            
128 18         131 my $fh = IO::Any->write($any);
129 18         1768 foreach my $line (@{$lines}) {
  18         51  
130 79         1293 print $fh $line->as_string;
131             }
132            
133 18         504 close $fh;
134            
135 18         196 return $self;
136             }
137              
138             sub get_fields {
139 59     59 0 78 my $self = shift;
140 59 50       142 my $field_name = shift or croak 'field_name argument is mandatory';
141            
142 59         150 my $key_cmp = $self->key_cmp;
143             return (
144 210         420 grep {
145 59         137 $key_cmp->($field_name, $_->key) == 0
146 59         86 } @{$self->_lines}
147             );
148             }
149              
150             sub get_field {
151 53     53 0 78 my $self = shift;
152 53 50       264 my $field_name = shift or croak 'field_name argument is mandatory';
153 53         85 my @extra_args = @_;
154            
155 53         168 my @fields = $self->get_fields($field_name, @extra_args);
156 53 50       145 croak 'more then one header field with name "'.$field_name.'"'
157             if @fields > 1;
158            
159 53         144 return $fields[0];
160             }
161              
162             sub get_value {
163 53     53 0 2696 my $self = shift;
164 53 50       151 my $key = shift or croak 'key argument is mandatory';
165 53         90 my @extra_args = @_;
166              
167 53         185 my $field = $self->get_field($key, @extra_args);
168 53 100       166 return undef if not defined $field;
169 46         122 return $field->value;
170             }
171              
172             sub update_values {
173 2     2 0 3 my $self = shift;
174 2 50       7 my $key = shift or croak 'key argument is mandatory';
175 2         4 my $value = shift;
176              
177 2         64 my $key_cmp = $self->key_cmp;
178 6 100       12 my @lines = (
179             map {
180 2         5 ($key_cmp->($_->key, $key) == 0 ? $_->value($value) : ());
181 6         17 $_;
182 2         5 } @{$self->_lines}
183             );
184            
185 2         6 return $self;
186             }
187              
188             sub rm_fields {
189 1     1 0 2 my $self = shift;
190 1 50       8 my (@field_names) = (@_) or croak 'field_names argument is mandatory';
191              
192 1         4 my $key_cmp = $self->key_cmp;
193 5         13 my @lines = (
194             grep {
195 1         3 my $key = $_->key;
196 5     12   39 all { $key_cmp->($key, $_) != 0 } @field_names;
  12         18  
197 1         3 } @{$self->_lines}
198             );
199            
200 1         5 $self->_lines(\@lines);
201            
202 1         3 return $self;
203             }
204              
205             sub set_value {
206 6     6 0 21 my $self = shift;
207 6 50       21 my $key = shift or croak 'key argument is mandatory';
208 6         9 my $value = shift;
209              
210 6         26 my @fields = $self->get_fields($key);
211 6 100       29 if (@fields == 1) {
    50          
212 2         6 $self->update_values($key, $value);
213             }
214             elsif (@fields == 0) {
215 4         7 push @{$self->_lines}, Data::Header::Fields::Line->new(
  4         11  
216             'key' => $key,
217             'value' => $value,
218             'parent' => $self,
219             );
220             }
221             else {
222 0         0 croak 'more then one header field with name "'.$key.'"';
223             }
224            
225            
226 6         29 return $self;
227             }
228              
229             sub cmp {
230 2     2 0 353 my $a = shift;
231 2         6 my $b = shift;
232            
233 2 50 33     47 $a = $a->encode if ref $a and $a->can('encode');
234 2 50 33     14 $b = $b->encode if ref $b and $b->can('encode');
235            
236 2         60 return $a cmp $b;
237             }
238              
239             sub keys {
240 4     4 0 50 my $self = shift;
241 4   66     45 my $lines = shift || (ref $self ? $self->_lines : []);
242            
243             return
244 6         29 uniq
245             map {
246 4         20 $_->key
247 4         9 } @{$lines}
248             ;
249             }
250              
251             sub line_ending {
252 54     54 0 83 my $self = shift;
253            
254 54 100       159 return "\n"
255             if not ref $self;
256            
257 41 100       108 if (@_) {
258 16         343 $self->{line_ending} = shift;
259             }
260 41 100       165 $self->{line_ending} = "\n"
261             if (not $self->{line_ending});
262            
263 41         144 return $self->{line_ending};
264             }
265              
266             sub push_line {
267 0     0 0 0 my $self = shift;
268 0         0 my $line = shift;
269              
270 0         0 my $lines = $self->_lines;
271 0         0 push(@$lines, $line);
272              
273 0         0 return $self;
274             }
275              
276             1;
277              
278             package Data::Header::Fields::Value;
279              
280 5     5   7215 use Scalar::Util 'weaken', 'isweak';
  5         9  
  5         480  
281              
282             use overload
283 5         48 '""' => \&as_string,
284             'cmp' => \&cmp,
285 5     5   28 ;
  5         7  
286              
287             sub new {
288 202     202   7416 my $class = shift;
289 202         248 my $value = shift;
290            
291 202 100       415 if (@_ == 0) {
292 68 50       157 if (not ref $value) {
293 68         199 $value = { 'value' => $value };
294             }
295             }
296             else {
297 134         465 $value = { $value, @_ };
298             }
299            
300 202         531 my $self = bless { 'parent' => $class->_default_parent, %{$value} }, $class;
  202         1253  
301            
302 202 100 66     1813 weaken($self->{'parent'})
303             if (ref($self->{'parent'}) && !isweak($self->{'parent'}));
304            
305 202         861 return $self;
306             }
307              
308             sub as_string {
309 158     158   695 my $self = shift;
310              
311             # remove folding
312 158         285 my $line = $self->{value};
313 158 100       394 if ($self->parent->parent->tight_folding) {
314 86         377 $line =~ s/\n\s//xmsg;
315             }
316             else {
317 72         237 $line =~ s/\n(\s)/$1/xmsg;
318             }
319 158         740 $line =~ s/\r?\n$//;
320 158         469 $line = String::Escape::unprintable($line);
321            
322 158         1544 return $line;
323             }
324              
325             sub cmp {
326 25     25   3122 my $a = shift;
327 25         49 my $b = shift;
328            
329 25 50 33     328 $a = $a->as_string if ref $a and $a->can('as_string');
330 25 100 66     108 $b = $b->as_string if ref $b and $b->can('as_string');
331            
332 25         526 return $a cmp $b;
333             }
334              
335             sub _default_parent {
336 263     263   533 return 'Data::Header::Fields::Line';
337             }
338              
339             sub parent {
340 166     166   177 my $self = shift;
341 166 100       381 $self->{'parent'} = shift
342             if @_;
343            
344 166 100       682 return (ref $self->{'parent'} ? $self->{'parent'} : $self->_default_parent);
345             }
346              
347             sub value {
348 7     7   22 my $self = shift;
349            
350 7 100       21 if (@_) {
351 5         11 $self->{'value'} = shift;
352 5         17 $self->parent->line_changed;
353             }
354            
355 7         33 return $self->{'value'};
356             }
357             1;
358              
359             package Data::Header::Fields::Line;
360              
361 5     5   3018 use Scalar::Util 'blessed', 'weaken', 'isweak';
  5         9  
  5         517  
362              
363             use overload
364 5         37 '""' => \&as_string,
365             'cmp' => \&cmp,
366 5     5   39 ;
  5         14  
367              
368             sub new {
369 193     193   298 my $class = shift;
370 193         216 my $line = shift;
371 193         427 my @args = @_;
372            
373 193 100       476 if (@args > 0) {
374 190         623 $line = { $line, @args };
375             }
376            
377 193 100       730 if (not ref $line) {
378 2         7 $line = { 'line' => $line };
379             }
380            
381 193         473 $line = { 'parent' => $class->_default_parent, %{$line} };
  193         783  
382            
383 193 100       686 if (exists $line->{'line'}) {
384             # reblessing the line object
385 183 100       589 if (blessed $line->{'line'}) {
386 72         144 my $self = delete $line->{'line'};
387 72         79 foreach my $key (keys %{$line}) {
  72         172  
388 72         219 $self->{$key} = $line->{$key};
389             }
390 72         416 return bless $self, $class;
391             }
392             else {
393 111         263 my $line_string = delete $line->{'line'};
394 111         196 $line->{'original_line'} = $line_string;
395 111         397 my ($key, $value) = split(/:/, $line_string, 2);
396 111         195 $line->{'key'} = $key;
397 111         362 $line->{'value'} = Data::Header::Fields::Value->new(
398             'value' => $value,
399             'parent' => $line,
400             );
401             }
402             }
403            
404 121 100 66     799 weaken($line->{'parent'})
405             if (ref($line->{'parent'}) && !isweak($line->{'parent'}));
406            
407 121         559 return bless $line, $class;
408             }
409              
410             sub key {
411 789     789   882 my $self = shift;
412 789 100       1455 $self->{'key'} = shift
413             if @_;
414            
415 789         3551 return $self->{'key'};
416             }
417             sub value {
418 200     200   301 my $self = shift;
419 200 100       425 $self->line_changed->{'value'} = shift
420             if @_;
421            
422 200         1073 return $self->{'value'};
423             }
424              
425             sub line_changed {
426 16     16   55 my $self = shift;
427 16 100       68 delete $self->{'original_line'}
428             if ref $self;
429 16         28 return $self;
430             }
431              
432             sub as_string {
433 188     188   228 my $self = shift;
434            
435 188 100       414 if (exists $self->{'original_line'}) {
436 178         236 my $original_line = $self->{'original_line'};
437            
438             # make sure the line has line_ending, even the original one could be created without using ->new()
439 178 100       635 $original_line .= $self->parent->line_ending
440             if $original_line !~ m/ \n \Z /xms;
441            
442 178         515 return $original_line;
443             }
444              
445 10         28 my ($key, $value) = ($self->key, $self->value);
446 10         47 $value = String::Escape::printable($value);
447              
448 10         145 my $line = join(':', $key, $value);
449            
450 10 50       59 $line .= $self->parent->line_ending
451             if $line !~ m/\n$/xms;
452            
453 10         53 return $line;
454             }
455              
456             sub cmp {
457 3     3   516 my $a = shift;
458 3         5 my $b = shift;
459            
460 3 50 33     39 $a = $a->as_string if ref $a and $a->can('as_string');
461 3 50 33     11 $b = $b->as_string if ref $b and $b->can('as_string');
462            
463 3         71 return $a cmp $b;
464             }
465              
466             sub _default_parent {
467 262     262   522 return 'Data::Header::Fields';
468             }
469              
470             sub parent {
471 226     226   300 my $self = shift;
472 226 50       420 $self->{'parent'} = shift
473             if @_;
474            
475 226 100       951 return (ref $self ? $self->{'parent'} : $self->_default_parent);
476             }
477              
478              
479             1;
480              
481              
482             __END__