File Coverage

blib/lib/Table/Simple.pm
Criterion Covered Total %
statement 106 120 88.3
branch 37 50 74.0
condition 3 6 50.0
subroutine 19 19 100.0
pod 6 6 100.0
total 171 201 85.0


line stmt bran cond sub pod time code
1             package Table::Simple;
2              
3 6     6   1841526 use Moose;
  6         1398419  
  6         48  
4 6     6   43505 use Moose::Util::TypeConstraints;
  6         13  
  6         53  
5 6     6   15321 use namespace::autoclean;
  6         24272  
  6         39  
6              
7 6     6   3392 use Tie::IxHash;
  6         25898  
  6         245  
8 6     6   51 use Carp qw(carp);
  6         13  
  6         285  
9 6     6   40 use overload;
  6         12  
  6         56  
10              
11 6     6   2735 use Table::Simple::Column;
  6         20  
  6         8798  
12              
13             our $VERSION = 0.03;
14              
15             =head1 NAME
16              
17             Table::Simple - Easily output perl object attributes to an ASCII table
18              
19             =head1 SYNOPSIS
20              
21             use Table::Simple;
22             use Table::Simple::Output;
23             use My::Data;
24             use My::Data::Collection;
25              
26             my $collection = new My::Data::Collection;
27             my $data = My::Data->new( a => 1, b => "foo", baz => 0.1 );
28             $collection->add($data1);
29              
30             # Lather, rinse, repeat last two lines.
31              
32             my $table = new Table::Simple;
33              
34             foreach my $data ( $collection->get_data ) {
35             $table->extract_row( $data );
36             }
37              
38             my $table_output = Table::Simple::Output->new( table => $table );
39             $table_output->print_table;
40              
41             =head1 DESCRIPTION
42              
43             Oh good grief, another table formatter? Really?
44              
45             Yes, and I had a good reason - I didn't find anything that did what I wanted,
46             which was to lazily extract attribute names and values from objects without
47             me having to tell the formatter what they were.
48              
49             So, given one or more perl objects (either a plain old blessed hashref or
50             a Moose object) this module will pull the attribute names and values and
51             then output them into a formmatted ASCII table. This might be useful to you
52             if you want to take a bunch of perl objects and say, dump them into Markdown
53             for ultra lazy wiki pages which document the states of various things. (That's
54             what I will be using this module for myself.)
55              
56             I also wanted to use Moose in a project which wouldn't take a LOT of time to
57             complete, but wasn't just a trivial contrived exercise either.
58              
59             This module is well behaved by skipping attributes which begin with an
60             underscore and prevents you from adding columns after you've processed
61             any rows.
62              
63             =head2 ATTRIBUTES
64              
65             =over 4
66              
67             =item type
68              
69             This attribute stores the type of a passed object, so you can't combine
70             objects of type "Foo" with type "Bar."
71              
72             This is set automatically, so you normally shouldn't need to manipulate it.
73              
74             =back
75              
76             =cut
77              
78             has 'type' => (
79             is => 'rw',
80             isa => 'Str',
81             );
82              
83              
84             =over 4
85              
86             =item row_count
87              
88             This attribute stores the number of rows that have been processed by the
89             table so far. It's a read-only attribute.
90              
91             =back
92              
93             =cut
94              
95             has 'row_count' => (
96             traits => ['Counter'],
97             is => 'ro',
98             isa => 'Int',
99             default => 0,
100             handles => {
101             '_inc_row_count' => 'inc',
102             },
103             );
104              
105             =over 4
106              
107             =item columns
108              
109             This attribute is a collection of L<Table::Simple::Column> objects which
110             represent the attribute names of the perl objects being processed.
111              
112             This attribute has a number of methods which permit you to manipulate
113             how columns are interpreted and formatted for output.
114              
115             =back
116              
117             =cut
118              
119              
120             has 'columns' => (
121             is => 'ro',
122             isa => 'Tie::IxHash',
123             builder => '_columns_builder',
124             handles => {
125             'get_columns' => 'Values',
126             'reorder_columns' => 'Reorder',
127             'delete_column' => 'Delete',
128             'has_columns' => 'Length',
129             'get_column_names' => 'Keys',
130             },
131             );
132              
133             =over 4
134              
135             =item name
136              
137             You can optionally supply a name to the table which will be the title of a
138             table in the output phase.
139              
140             =back
141              
142             =cut
143              
144             has 'name' => (
145             is => 'rw',
146             isa => 'Str',
147             );
148              
149             =head2 METHODS
150              
151             =over 4
152              
153             =item get_columns
154              
155             This method gets all columns, preserving the order in which they were
156             added to the collection.
157              
158             =item reorder_columns
159              
160             This method changes the order of columns. B<NOTE:> Any columns which are
161             omitted will be deleted!
162              
163             =item delete_column
164              
165             Delete the given column from the collection.
166              
167             =item has_columns
168              
169             This method returns true if the collection has any columns. (See has_column to
170             test whether a specific column exists.)
171              
172             =item get_column_names
173              
174             This method returns the names of all columns, preserving the order in which
175             they were added to the collection.
176              
177              
178             =item has_column
179              
180             This method returns true if the columns attribute contains the column name
181             given.
182              
183             =back
184              
185             =cut
186              
187             sub has_column {
188 20     20 1 35 my $self = shift;
189 20         29 my $column_name = shift;
190              
191 20 100       591 return 1 if defined $self->columns->Indices($column_name);
192 10         99 return 0;
193             }
194              
195             =over 4
196              
197             =item get_column
198              
199             This method gets the L<Table::Simple::Column> object with the given name.
200              
201             =back
202              
203             =cut
204              
205             sub get_column {
206 12     12 1 1222 my $self = shift;
207 12         21 my $column_name = shift;
208              
209 12 100       40 if ( $self->has_column($column_name) ) {
210 10         343 return $self->columns->Values($self->columns->Indices($column_name));
211             }
212             else {
213 2         357 carp "$column_name does not exist.";
214 2         212 return;
215             }
216             }
217              
218             =over 4
219              
220             =item add_column
221              
222             This method adds a L<Table::Simple::Column> object to the columns collection.
223             You normally shouldn't need to do this.
224              
225             =back
226              
227             =cut
228              
229             sub add_column {
230 8     8 1 19 my $self = shift;
231 8         14 my $arg = shift;
232              
233 8 50 33     63 if ( not ( blessed($arg) && $arg->isa("Table::Simple::Column") ) ) {
234 0         0 carp "The add_column method only accepts Table::Simple::Column objects.\n";
235 0         0 return;
236             }
237              
238 8         248 $self->columns->Push($arg->name() => $arg);
239             }
240              
241             sub _columns_builder {
242 6     6   13 my $self = shift;
243              
244 6         42 return Tie::IxHash->new(@_);
245             }
246              
247             =over 4
248              
249             =item extract_columns
250              
251             Given a perl object, this method extracts the non-private attribute names
252             (that is, those which do not start with an underscore) and creates
253             L<Table::Simple::Column> objects for them. It preserves the order in
254             which columns were added to the collection.
255              
256             It will complain if you pass an argument that isn't blessed, or if you
257             try to extract columns after you've added rows.
258              
259             =back
260              
261             =cut
262              
263             sub extract_columns {
264 7     7 1 19 my $self = shift;
265 7         35 my $arg = shift;
266              
267 7 100       227 if ( not defined $self->type() ) {
268 4         16 $self->set_type($arg);
269             }
270              
271 7 100       40 if ( ! blessed($arg) ) {
272 1         88 carp "Your argument is not blessed.";
273 1         93 return;
274             }
275              
276 6 50       151 if ( blessed($arg) ne $self->type() ) {
277 0         0 carp "Your argument is not of type " . $self->type() . "\n";
278 0         0 return;
279             }
280              
281 6 100       161 if ( $self->row_count > 0 ) {
282 2         242 carp "You already added rows to this table.\n";
283 2         165 return;
284             }
285              
286 4         9 my $rv;
287              
288 4 100       19 if ( $self->_is_moose_object($arg) ) {
    50          
289 3         12 $rv = $self->_extract_columns_moose($arg);
290             }
291             elsif ( overload::StrVal($arg) =~ /HASH/ ) {
292 1         11 $rv = $self->_extract_columns_hashref($arg);
293             }
294             else {
295 0         0 carp "I don't know how to process your argument.\n";
296 0         0 return;
297             }
298              
299 4         75 return $rv;
300             }
301              
302             =over 4
303              
304             =item set_type
305              
306             This method sets the type attribute based on the perl object's package name.
307              
308             =back
309              
310             =cut
311              
312             sub set_type {
313 4     4 1 8 my $self = shift;
314 4         8 my $arg = shift;
315              
316 4 50       30 if ( not blessed $arg ) {
317 0         0 carp "$arg does not appear to be a blessed object.\n";
318 0         0 return;
319             }
320              
321 4         124 $self->type(blessed $arg);
322             }
323              
324             sub _extract_columns_moose {
325 3     3   7 my $self = shift;
326 3         6 my $object = shift;
327              
328 3         23 foreach my $attribute_name ( $object->meta->get_attribute_list ) {
329 9 100       250 next if $self->_is_private_attribute( $attribute_name );
330              
331 6         11 my $column;
332              
333 6 50       18 if ( not $self->has_column( $attribute_name ) ) {
334 6         188 $column = new Table::Simple::Column(name => $attribute_name);
335             }
336 6 50       32 $self->add_column($column) if defined $column;
337             }
338              
339 3         56 return $self->has_columns;
340              
341             }
342              
343             sub _extract_columns_hashref {
344 1     1   2 my $self = shift;
345 1         2 my $hashref = shift;
346              
347 1         2 foreach my $key ( keys %{ $hashref } ) {
  1         5  
348 3 100       52 next if $self->_is_private_attribute( $key );
349              
350 2         4 my $column;
351              
352 2 50       6 if ( not $self->has_column( $key ) ) {
353 2         61 $column = new Table::Simple::Column(name => $key);
354             }
355 2 50       9 $self->add_column($column) if defined $column;
356             }
357              
358 1         6 return $self->has_columns;
359             }
360              
361             =over 4
362              
363             =item extract_row
364              
365             This method extract row values from attribute names in a given perl object.
366              
367             If you haven't already set the table type, or extract columns, this method
368             will automagically do that.
369              
370             It returns the current row count.
371              
372             =back
373              
374             =cut
375              
376             sub extract_row {
377 8     8 1 2076 my $self = shift;
378 8         14 my $object = shift;
379              
380 8 100       31 if ( ! $self->has_columns ) {
381 1         23 $self->extract_columns($object);
382             }
383              
384 8 50       291 if ( blessed $object ne $self->type() ) {
385 0         0 carp "Your object is not of type " . $self->type() . "\n";
386 0         0 return;
387             }
388              
389 8         276 $self->_inc_row_count;
390              
391 8         31 foreach my $column ( $self->get_columns ) {
392 16         173 my $value;
393 16 100       43 if ( $self->_is_moose_object($object) ) {
394 12         345 $value = $self->_get_value_using_introspection
395             ( $object, $column->name() );
396             }
397             else {
398             $value = ref $object->{$column->name}
399             ? CORE::dump($object->{$column->name})
400 4 50       118 : $object->{$column->name}
401             ;
402             }
403 16 50       132 $column->add_row($value) if defined $value;
404             }
405              
406 8         227 return $self->row_count;
407             }
408              
409             sub _is_private_attribute {
410 13     13   26 my $self = shift;
411 13         19 my $attr_name = shift;
412              
413 13 100       53 if ( $attr_name =~ /^_+/ ) {
414 5         26 return 1;
415             }
416             else {
417 8         27 return 0;
418             }
419             }
420              
421             sub _is_moose_object {
422 24     24   4496 my $self = shift;
423 24         75 my $object = shift;
424              
425 24 100 66     264 return 1 if ( blessed($object) && $object->can("meta") );
426 6         28 return 0;
427             }
428              
429             sub _get_value_using_introspection {
430 12     12   23 my $self = shift;
431 12         19 my $object = shift;
432 12         16 my $attribute_name = shift;
433              
434 12 50       43 if ( not $object->meta->has_attribute($attribute_name) ) {
435 0         0 carp "Your object doesn't seem to have an attribute named $attribute_name.\n";
436 0         0 return;
437             }
438              
439 12         376 my $mop_attribute = $object->meta->get_attribute($attribute_name);
440              
441 12 50       306 if ( not $mop_attribute->has_read_method ) {
442 0         0 carp "$attribute_name doesn't seem to have a read method!\n";
443 0         0 return;
444             }
445              
446 12         139 my $read_method = $mop_attribute->get_read_method;
447              
448 12         295 return $object->$read_method();
449              
450             }
451              
452             =head1 LICENSE
453              
454             Copyright (C) 2010 Mark Allen
455              
456             This program is free software; you can redistribute it and/or modify it
457             under the terms of either: the GNU General Public License as published
458             by the Free Software Foundation; or the Artistic License.
459              
460             See http://dev.perl.org/licenses/ for more information.
461              
462             =head1 AUTHOR
463              
464             Mark Allen <mrallen1@yahoo.com>
465              
466             =head1 SEE ALSO
467              
468             L<Moose>, L<Table::Simple::Column>, L<Table::Simple::Output>
469              
470             =cut
471              
472             __PACKAGE__->meta->make_immutable();
473             1;