File Coverage

blib/lib/Table/Readable.pm
Criterion Covered Total %
statement 117 117 100.0
branch 47 52 90.3
condition 3 3 100.0
subroutine 8 8 100.0
pod 3 4 75.0
total 178 184 96.7


line stmt bran cond sub pod time code
1             package Table::Readable;
2 6     6   381297 use warnings;
  6         54  
  6         188  
3 6     6   29 use strict;
  6         10  
  6         454  
4             require Exporter;
5             our @ISA = qw(Exporter);
6             our @EXPORT_OK = qw/read_table write_table read_table_hash/;
7             our %EXPORT_TAGS = (all => \@EXPORT_OK);
8             our $VERSION = '0.04';
9 6     6   40 use Carp;
  6         12  
  6         7156  
10              
11             sub read_file
12             {
13 14     14 0 25 my ($file) = @_;
14 14         19 my @rv;
15 14 100   2   533 open my $in, "<:encoding(utf8)", $file or croak "Error opening '$file': $!";
  2         13  
  2         2  
  2         13  
16 13         1596 while (<$in>) {
17 131         469 push @rv, $_;
18             }
19 13 50       190 close $in or croak $!;
20 13         99 return @rv;
21             }
22              
23             sub read_table
24             {
25 20     20 1 42202 my ($list_file, %options) = @_;
26 20         60 my @table;
27 20         44 my $row = {};
28 20         42 push @table, $row;
29 20         34 my $mode = "single-line";
30 20         35 my $mkey;
31              
32             my @lines;
33 20 100       59 if ($options{scalar}) {
34 6         25 @lines = split /\n/, $list_file;
35 6         13 for (@lines) {
36 12         21 $_ .= "\n";
37             }
38 6         32 $lines[-1] =~ s/\n$//;
39             }
40             else {
41 14         34 @lines = read_file ($list_file);
42             }
43 19         38 my $count = 0;
44 19         37 for (@lines) {
45              
46 142         169 $count++;
47              
48 142 100       256 if ($mode ne 'multi-line') {
49              
50             # Detect the first line of a cell of the table whose
51             # information spans several lines of the input file.
52              
53 100 100       224 if (/^%%\s*([^:]+):\s*$/) {
54 6         21 $mode = "multi-line";
55 6         13 $mkey = $1;
56 6         10 next;
57             }
58             }
59              
60             # Continue to process a table cell whose information spans
61             # several lines of the input file.
62              
63 136 100       214 if ($mode eq "multi-line") {
64 42 100       90 if (/^%%\s*$/) {
65 6         11 $mode = "single-line";
66 6 50       17 if ($row->{$mkey}) {
67             # Strip leading and trailing whitespace
68 6         107 $row->{$mkey} =~ s/^\s+|\s+$//g;
69             # Strip leading and trailing slashes
70 6         82 $row->{$mkey} =~ s/^\\|\\$//g;
71             }
72 6         10 $mkey = undef;
73 6         10 next;
74             }
75 36         65 $row->{$mkey} .= $_;
76 36         47 next;
77             }
78 94 100       171 if (/^\s*#.*/) {
79              
80             # Skip comments.
81              
82 2         4 next;
83             }
84 92 100       322 if (/([^:]+):\s*(.*?)\s*$/) {
85              
86             # Key / value pair on a single line.
87              
88 67         130 my $key = $1;
89 67         92 my $value = $2;
90              
91             # If there are any spaces in the key, substitute them with
92             # underscores.
93              
94 67         124 $key =~ s/\s/_/g;
95 67 100       132 if ($row->{$key}) {
96 1         109 croak "$list_file:$count: duplicate for key $key\n";
97             }
98             # Strip leading and trailing slashes
99 66         142 $value =~ s/^\\|\\$//g;
100 66         138 $row->{$key} = $value;
101 66         109 next;
102             }
103 25 100       79 if (/^\s*$/) {
104              
105             # A blank line signifies the end of a row.
106              
107 24 100       69 if (keys %$row > 0) {
108 20         32 $row = {};
109 20         36 push @table, $row;
110             }
111 24         47 next;
112             }
113 1         5 my $file_line = "$list_file:$count:";
114 1 50       4 if ($options{scalar}) {
115 1         2 $file_line = "$count:";
116             }
117 1         12 warn "$file_line unmatched line '$_'\n";
118             }
119             # Deal with the case of whitespace at the end of the file.
120 18         36 my $last_row = $table[-1];
121 18 100       51 if (keys %$last_row == 0) {
122 7         13 pop @table;
123             }
124 18 100       239 croak "read_table returns an array" unless wantarray ();
125 16         76 return @table;
126             }
127              
128             sub read_table_hash
129             {
130 3     3 1 1969 my ($list_file, $key, %options) = @_;
131 3         8 my @table = read_table ($list_file, %options);
132 3         4 my %hash;
133 3         5 my $i = -1;
134 3         4 for my $entry (@table) {
135 9         9 $i++;
136 9         15 my $ekey = $entry->{$key};
137 9 100       14 if (! $ekey) {
138 2         165 carp "No $key entry for element $i of $list_file";
139 2         13 next;
140             }
141 7 100       11 if ($hash{$ekey}) {
142 2         275 carp "Table entries for $key are not unique, duplicate at $i";
143 2         15 next;
144             }
145 5         12 $hash{$ekey} = $entry;
146             }
147 3         10 return \%hash;
148             }
149              
150             # Maximum length of a single-line entry.
151              
152             our $maxlen = 75;
153              
154             sub write_table
155             {
156 7     7 1 5987 my ($list, $file) = @_;
157 7 100       30 if (ref $list ne 'ARRAY') {
158 1         214 carp "First argument to 'write_table' must be array reference";
159 1         9 return;
160             }
161 6         11 my $n = 0;
162 6         16 for my $i (@$list) {
163 7 100       19 if (ref $i ne 'HASH') {
164 1         145 carp "Elements of first argument to 'write_table' must be hash references";
165 1         35 return;
166             }
167 6         21 for my $k (keys %$i) {
168 12 100       30 if (ref $i->{$k}) {
169 1         104 carp "Non-scalar value in key $k of element $n";
170 1         28 return;
171             }
172             }
173 5         12 $n++;
174             }
175 4         10 my $text = '';
176 4         9 for (@$list) {
177 5         26 for my $k (sort keys %$_) {
178 11         18 my $v = $_->{$k};
179 11 100 100     56 if (length ($v) + length ($k) > $maxlen ||
180             $v =~ /\n/) {
181 2         9 $text .= "%%$k:\n$v\n%%\n";
182             }
183             else {
184 9         27 $text .= "$k: $v\n";
185             }
186             }
187 5         11 $text .= "\n";
188             }
189 4 100       14 if ($file) {
    100          
190 2 50       258 open my $out, ">:encoding(utf8)", $file or croak "Can't open $file for writing: $!";
191 2         193 print $out $text;
192 2 50       168 close $out or croak $!;
193             }
194             elsif (defined (wantarray ())) {
195 1         5 return $text;
196             }
197             else {
198 1         5 print $text;
199             }
200             }
201              
202             1;