File Coverage

blib/lib/Data/TableData/Object/aohos.pm
Criterion Covered Total %
statement 177 177 100.0
branch 60 68 88.2
condition 5 6 83.3
subroutine 19 19 100.0
pod 14 15 93.3
total 275 285 96.4


line stmt bran cond sub pod time code
1             package Data::TableData::Object::aohos;
2              
3 4     4   59 use 5.010001;
  4         11  
4 4     4   16 use strict;
  4         7  
  4         88  
5 4     4   18 use warnings;
  4         5  
  4         102  
6              
7 4     4   351 use parent 'Data::TableData::Object::Base';
  4         261  
  4         15  
8              
9             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
10             our $DATE = '2022-05-19'; # DATE
11             our $DIST = 'Data-TableData-Object'; # DIST
12             our $VERSION = '0.116'; # VERSION
13              
14             sub new {
15 15     15 1 1259 my ($class, $data, $spec) = @_;
16 15         41 my $self = bless {
17             data => $data,
18             spec => $spec,
19             }, $class;
20 15 100       39 if ($spec) {
21 8         26 $self->{cols_by_idx} = [];
22 8         15 my $ff = $spec->{fields};
23 8         22 for (keys %$ff) {
24 22         45 $self->{cols_by_idx}[ $ff->{$_}{pos} ] = $_;
25             }
26             $self->{cols_by_name} = {
27 8         19 map { $_ => $ff->{$_}{pos} }
  22         50  
28             keys %$ff
29             };
30             } else {
31 7         10 my %cols;
32 7         14 for my $row (@$data) {
33 13         38 $cols{$_}++ for keys %$row;
34             }
35 7         11 my $i = 0;
36 7         16 $self->{cols_by_name} = {};
37 7         13 $self->{cols_by_idx} = [];
38 7         28 for my $k (sort keys %cols) {
39 16         22 $self->{cols_by_name}{$k} = $i;
40 16         23 $self->{cols_by_idx}[$i] = $k;
41 16         24 $i++;
42             }
43             }
44 15         66 $self;
45             }
46              
47             sub row_count {
48 8     8 1 11 my $self = shift;
49 8         11 scalar @{ $self->{data} };
  8         19  
50             }
51              
52             sub row {
53 6     6 1 563 my ($self, $idx) = @_;
54 6         29 $self->{data}[$idx];
55             }
56              
57             sub row_as_aos {
58 22     22 1 613 my ($self, $idx) = @_;
59 22         31 my $row_hos = $self->{data}[$idx];
60 22 100       40 return undef unless $row_hos; ## no critic: Subroutines::ProhibitExplicitReturnUndef
61 21         23 my $cols = $self->{cols_by_idx};
62 21         25 my $row_aos = [];
63 21         32 for my $i (0..$#{$cols}) {
  21         32  
64 63         92 $row_aos->[$i] = $row_hos->{$cols->[$i]};
65             }
66 21         47 $row_aos;
67             }
68              
69             sub row_as_hos {
70 4     4 1 558 my ($self, $idx) = @_;
71 4         16 $self->{data}[$idx];
72             }
73              
74             sub rows {
75 1     1 1 587 my $self = shift;
76 1         7 $self->{data};
77             }
78              
79             sub rows_as_aoaos {
80 1     1 1 571 my $self = shift;
81 1         3 my $data = $self->{data};
82              
83 1         4 my $cols = $self->{cols_by_idx};
84 1         2 my $rows = [];
85 1         30 for my $hos (@{$self->{data}}) {
  1         17  
86 3         6 my $row = [];
87 3         5 for my $i (0..$#{$cols}) {
  3         7  
88 9         15 $row->[$i] = $hos->{$cols->[$i]};
89             }
90 3         6 push @$rows, $row;
91             }
92 1         6 $rows;
93             }
94              
95             sub rows_as_aohos {
96 11     11 1 600 my $self = shift;
97 11         56 $self->{data};
98             }
99              
100             sub uniq_col_names {
101 2     2 1 3 my $self = shift;
102              
103 2         4 my @res;
104             COL:
105 2         3 for my $col (sort keys %{$self->{cols_by_name}}) {
  2         7  
106 4         5 my %mem;
107 4         6 for my $row (@{$self->{data}}) {
  4         7  
108 8 100       16 next COL unless defined $row->{$col};
109 6 100       16 next COL if $mem{ $row->{$col} }++;
110             }
111 1         2 push @res, $col;
112             }
113 2         9 @res;
114             }
115              
116             sub const_col_names {
117 2     2 1 4 my $self = shift;
118              
119 2         3 my @res;
120             COL:
121 2         3 for my $col (sort keys %{$self->{cols_by_name}}) {
  2         6  
122 4         14 my $i = -1;
123 4         7 my $val;
124             my $val_undef;
125 4         5 for my $row (@{$self->{data}}) {
  4         8  
126 9 100       17 next COL unless exists $row->{$col};
127 8         9 $i++;
128 8 100       14 if ($i == 0) {
129 3         5 $val = $row->{$col};
130 3 100       7 $val_undef = 1 unless defined $val;
131             } else {
132 5 100       10 if ($val_undef) {
133 2 50       5 next COL if defined $row->{$col};
134             } else {
135 3 50       5 next COL unless defined $row->{$col};
136 3 100       9 next COL unless $val eq $row->{$col};
137             }
138             }
139             }
140 2         4 push @res, $col;
141             }
142 2         11 @res;
143             }
144              
145             sub del_col {
146 4     4 1 12 my ($self, $name_or_idx) = @_;
147              
148 4         10 my $idx = $self->col_idx($name_or_idx);
149 4 100       12 return undef unless defined $idx; ## no critic: Subroutines::ProhibitExplicitReturnUndef
150              
151 3         6 my $name = $self->{cols_by_idx}[$idx];
152              
153 3         5 for my $row (@{$self->{data}}) {
  3         7  
154 9         14 delete $row->{$name};
155             }
156              
157             # adjust cols_by_{name,idx}
158 3         6 for my $i (reverse 0..$#{$self->{cols_by_idx}}) {
  3         8  
159 6         9 my $name = $self->{cols_by_idx}[$i];
160 6 100       15 if ($i > $idx) {
    100          
161 2         4 $self->{cols_by_name}{$name}--;
162             } elsif ($i == $idx) {
163 3         5 splice @{ $self->{cols_by_idx} }, $i, 1;
  3         7  
164 3         7 delete $self->{cols_by_name}{$name};
165             }
166             }
167              
168             # adjust spec
169 3 50       9 if ($self->{spec}) {
170 3         4 my $ff = $self->{spec}{fields};
171 3         7 for my $name (keys %$ff) {
172 6 100       12 if (!exists $self->{cols_by_name}{$name}) {
173 3         6 delete $ff->{$name};
174             } else {
175 3         5 $ff->{$name}{pos} = $self->{cols_by_name}{$name};
176             }
177             }
178             }
179              
180 3         12 $name;
181             }
182              
183             sub rename_col {
184 4     4 1 940 my ($self, $old_name_or_idx, $new_name) = @_;
185              
186 4         12 my $idx = $self->col_idx($old_name_or_idx);
187 4 100       18 die "Unknown column '$old_name_or_idx'" unless defined($idx);
188 3         18 my $old_name = $self->{cols_by_idx}[$idx];
189 3 50       8 die "Please specify new column name" unless length($new_name);
190 3 100       10 return if $new_name eq $old_name;
191 2 100       16 die "New column name must not be a number" if $new_name =~ /\A\d+\z/;
192              
193             # adjust data
194 1         3 for my $row (@{$self->{data}}) {
  1         3  
195 3         8 $row->{$new_name} = delete($row->{$old_name});
196             }
197              
198 1         2 $self->{cols_by_idx}[$idx] = $new_name;
199 1         4 $self->{cols_by_name}{$new_name} = delete($self->{cols_by_name}{$old_name});
200 1 50       4 if ($self->{spec}) {
201 1         2 my $ff = $self->{spec}{fields};
202 1         4 $ff->{$new_name} = delete($ff->{$old_name});
203             }
204             }
205              
206             sub switch_cols {
207 4     4 1 947 my ($self, $name_or_idx1, $name_or_idx2) = @_;
208              
209 4         12 my $idx1 = $self->col_idx($name_or_idx1);
210 4 100       18 die "Unknown first column '$name_or_idx1'" unless defined($idx1);
211 3         8 my $idx2 = $self->col_idx($name_or_idx2);
212 3 100       15 die "Unknown second column '$name_or_idx2'" unless defined($idx2);
213 2 100       8 return if $idx1 == $idx2;
214              
215 1         6 my $name1 = $self->col_name($name_or_idx1);
216 1         4 my $name2 = $self->col_name($name_or_idx2);
217              
218             # adjust data
219 1         3 for my $row (@{$self->{data}}) {
  1         3  
220 3         8 ($row->{$name1}, $row->{$name2}) = ($row->{$name2}, $row->{$name1});
221             }
222              
223             ($self->{cols_by_idx}[$idx1], $self->{cols_by_idx}[$idx2]) =
224 1         4 ($self->{cols_by_idx}[$idx2], $self->{cols_by_idx}[$idx1]);
225             ($self->{cols_by_name}{$name1}, $self->{cols_by_name}{$name2}) =
226 1         4 ($self->{cols_by_name}{$name2}, $self->{cols_by_name}{$name1});
227 1 50       3 if ($self->{spec}) {
228 1         3 my $ff = $self->{spec}{fields};
229 1         4 ($ff->{$name1}, $ff->{$name2}) = ($ff->{$name2}, $ff->{$name1});
230             }
231             }
232              
233             sub add_col {
234 6     6 1 4995 my ($self, $name, $idx, $spec, $data) = @_;
235              
236             # XXX BEGIN CODE dupe with aoaos
237 6 100       18 die "Column '$name' already exists" if defined $self->col_name($name);
238 5         15 my $col_count = $self->col_count;
239 5 100       49 if (defined $idx) {
240 3 100 100     32 die "Index must be between 0..$col_count"
241             unless $idx >= 0 && $idx <= $col_count;
242             } else {
243 2         5 $idx = $col_count;
244             }
245              
246 3         6 for (keys %{ $self->{cols_by_name} }) {
  3         9  
247 12 100       24 $self->{cols_by_name}{$_}++ if $self->{cols_by_name}{$_} >= $idx;
248             }
249 3         7 $self->{cols_by_name}{$name} = $idx;
250 3         4 splice @{ $self->{cols_by_idx} }, $idx, 0, $name;
  3         9  
251 3 50       6 if ($self->{spec}) {
252 3         6 my $ff = $self->{spec}{fields};
253 3         7 for my $f (values %$ff) {
254 12 100 66     36 $f->{pos}++ if defined($f->{pos}) && $f->{pos} >= $idx;
255             }
256 3 50       9 $ff->{$name} = defined($spec) ? {%$spec} : {};
257 3         6 $ff->{$name}{pos} = $idx;
258             }
259             # XXX BEGIN CODE dupe with aoaos
260              
261 3         6 my $i = 0;
262 3         4 for my $row (@{ $self->{data} }) {
  3         4  
263 9 100       18 $row->{$name} = $data ? $data->[$i] : undef;
264 9         14 $i++;
265             }
266             }
267              
268             sub set_col_val {
269 2     2 0 342 my ($self, $name_or_idx, $value_sub) = @_;
270              
271 2         8 my $col_name = $self->col_name($name_or_idx);
272 2         7 my $col_idx = $self->col_idx($name_or_idx);
273              
274 2 100       14 die "Column '$name_or_idx' does not exist" unless defined $col_name;
275              
276 1         3 for my $i (0..$#{ $self->{data} }) {
  1         3  
277 2         13 my $row = $self->{data}[$i];
278             $row->{$col_name} = $value_sub->(
279             table => $self,
280             row_idx => $i,
281             col_name => $col_name,
282             col_idx => $col_idx,
283 2         6 value => $row->{$col_name},
284             );
285             }
286             }
287              
288             1;
289             # ABSTRACT: Manipulate array of hashes-of-scalars via table object
290              
291             __END__