File Coverage

web/cgi-bin/yatt.lib/YATT/XHF.pm
Criterion Covered Total %
statement 101 106 95.2
branch 34 48 70.8
condition 8 9 88.8
subroutine 19 19 100.0
pod 0 12 0.0
total 162 194 83.5


line stmt bran cond sub pod time code
1             package YATT::XHF;
2              
3             =head1 NAME
4              
5             YATT::XHF - Extended Header Fields format.
6              
7             =cut
8              
9 3     3   5271 use strict;
  3         4  
  3         118  
10 3     3   11 use warnings FATAL => qw(all);
  3         3  
  3         106  
11              
12 3     3   9 use base qw(YATT::Class::Configurable);
  3         4  
  3         249  
13 3     3   12 use YATT::Fields qw(cf_FH cf_filename cf_tokens);
  3         3  
  3         17  
14 3     3   11 use Carp;
  3         3  
  3         171  
15              
16 3     3   853 use YATT::Util::Enum -prefix => '_', qw(NAME VALUE SIGIL);
  3         4  
  3         18  
17              
18             our $cc_name = qr{\w|[\.\-%/]};
19             our $cc_sigil = qr{[:\#,\-\[\]\{\}]};
20             our $cc_tabsp = qr{[\ \t]};
21              
22             our %OPN = qw([ array { hash);
23              
24             sub configure_filename {
25 15     15 0 22 (my MY $self, my ($fn)) = @_;
26 15 50       767 open $self->{cf_FH}, '<', $fn
27             or croak "Can't open file '$fn': $!";
28 15         41 $self->{cf_filename} = $fn;
29 15         47 $self;
30             }
31              
32             sub configure_string {
33 7     7 0 79 (my MY $self, my ($string)) = @_;
34 7 50   1   86 open $self->{cf_FH}, '<', \$string
  1         5  
  1         1  
  1         4  
35             or croak "Can't create string stream: $!";
36 7         665 $self;
37             }
38              
39             sub read_as_hashlist {
40 1     1 0 1 my MY $reader = shift;
41 1         2 local $/ = "";
42 1         2 my $fh = $$reader{cf_FH};
43 1         1 my @result;
44 1         6 while (defined (my $paragraph = <$fh>)) {
45 2 50       4 @{$$reader{cf_tokens}} = $reader->tokenize($paragraph)
  2         6  
46             or next;
47 2         3 push @result, $reader->organize_as_hash($reader->{cf_tokens});
48              
49             }
50 1 50       6 wantarray ? @result : \@result;
51             }
52              
53             sub read_as_hash {
54 203     203 0 315 shift->read_as(hash => @_);
55             }
56              
57             sub read_as {
58 205     205 0 176 (my MY $reader, my ($type)) = @_;
59 205 50       609 my $sub = $reader->can("organize_as_$type")
60             or croak "Unknown read_as type: $type";
61              
62 205         576 local $/ = "";
63 205         207 my $fh = $$reader{cf_FH};
64 205   100     343 until ($$reader{cf_tokens} && @{$$reader{cf_tokens}}) {
  390         918  
65 219 100       1024 defined (my $paragraph = <$fh>) or last;
66 206         274 @{$$reader{cf_tokens}} = $reader->tokenize($paragraph)
  206         716  
67             }
68 205 100 66     352 return unless $$reader{cf_tokens} && @{$$reader{cf_tokens}};
  205         564  
69 192         281 $sub->($reader, $reader->{cf_tokens});
70             }
71              
72             sub organize_as_pairlist {
73 2     2 0 3 (my MY $reader, my ($tokens)) = @_;
74 2         8 my $hash = $reader->organize_as_hash($tokens);
75 2         13 %$hash;
76             }
77              
78             sub organize_as_hash {
79 198     198 0 177 (my MY $reader, my ($tokens)) = @_;
80 198         137 my %result;
81 198         287 while (@$tokens) {
82 639         520 my $desc = shift @$tokens;
83 639         600 my $sigil = pop @$desc;
84 639 100       1173 if (my $type = $OPN{$sigil}) {
    100          
85 94         280 $desc->[_VALUE] = $reader->can("organize_as_$type")
86             ->($reader, $tokens);
87             } elsif ($sigil eq '}') {
88 4         7 last;
89             }
90 635         798 $reader->add_value($result{$reader->decode_name($desc->[_NAME])}
91             , $desc->[_VALUE]);
92             }
93 198         704 \%result;
94             }
95              
96             sub organize_as_array {
97 114     114 0 87 (my MY $reader, my ($tokens)) = @_;
98 114         92 my @result;
99 114         163 while (@$tokens) {
100 313         239 my $desc = shift @$tokens;
101 313         233 my $sigil = pop @$desc;
102 313 50       674 unless ($desc->[_NAME] eq '') {
    100          
    100          
103 0         0 croak "Array can not have name: $desc->[_NAME]";
104             } elsif (my $type = $OPN{$sigil}) {
105 24         66 $desc->[_VALUE] = $reader->can("organize_as_$type")
106             ->($reader, $tokens);
107             } elsif ($sigil eq ']') {
108 114         134 last;
109             }
110 199         352 push @result, $desc->[_VALUE];
111             }
112 114         189 \@result;
113             }
114              
115             sub add_value {
116 635     635 0 427 my MY $reader = shift;
117 635 100       813 unless (defined $_[0]) {
    50          
118 634         1731 $_[0] = $_[1];
119             } elsif (ref $_[0] ne 'ARRAY') {
120 1         7 $_[0] = [$_[0], $_[1]];
121             } else {
122 0         0 push @{$_[0]}, $_[1];
  0         0  
123             }
124             }
125              
126             sub tokenize {
127 212     212 0 152 my MY $reader = shift;
128 212         148 my ($ncomments, @result);
129 212         291 foreach my $token ($reader->split(my $record = shift)) {
130 1027 100       1568 if ($token =~ s{^(?:\#[^\n]*(?:\n|$))+}{}) {
131 49         54 $ncomments++;
132 49 50       107 next if $token eq '';
133             }
134              
135 978 50       4073 unless ($token =~ s{^($cc_name*) ($cc_sigil) (?:($cc_tabsp)|(\n|$))}{}x) {
136 0 0       0 croak "Invalid XHF token: $token"
137             . (defined $reader->{cf_filename} ? " in $reader->{cf_filename}" : "");
138             }
139 978         1599 my ($name, $sigil, $tabsp, $eol) = ($1, $2, $3, $4);
140              
141             # Comment fields are ignored.
142 978 50       1365 $ncomments++, next if $sigil eq "#";
143              
144             # Line continuation.
145 978         1350 $token =~ s/\n[\ \t]/\n/g;
146              
147 978 100       1366 unless (defined $eol) {
    100          
148             # Values are trimmed unless $eol
149 462         1607 $token =~ s/^\s+|\s+$//gs;
150             } elsif ($OPN{$sigil}) {
151             # Prohibit:
152             # name{ foo
153             # name[ foo
154 0         0 croak "Invalid XHF token(container with value): "
155 122 50       173 . join("", grep {defined $_} $name, $sigil, $tabsp, $token)
156             if $token ne "";
157             } else {
158             # Trim leading space for $tabsp eq "\n".
159 394         497 $token =~ s/^[\ \t]//;
160             }
161 978         2019 push @result, [$name, $token, $sigil];
162             }
163              
164             # Comment only paragraph should return nothing.
165 212 100 100     469 return if $ncomments && !@result;
166              
167 198 50       471 wantarray ? @result : \@result;
168             }
169              
170             sub split {
171 212     212 0 253 (my MY $reader, my ($record)) = @_;
172             # XXX: Can avoid copy.
173 212         876 $record =~ s{\n+$}{\n}s;
174 212         3072 split /(?<=\n)(?=[^\ \t])/, $record;
175             }
176              
177             sub decode_name {
178 635     635 0 502 (my MY $reader, my ($name)) = @_;
179 635         507 $name =~ s{%([\da-f]{2})}{pack("C", hex($1))}egxi;
  2         10  
180 635         1217 $name;
181             }
182              
183             1;
184             __END__