File Coverage

blib/lib/Table/Readable.pm
Criterion Covered Total %
statement 121 121 100.0
branch 49 54 90.7
condition 3 3 100.0
subroutine 8 8 100.0
pod 3 4 75.0
total 184 190 96.8


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