File Coverage

blib/lib/Table/Simple.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package Table::Simple;
2              
3 1     1   23098 use Moose;
  0            
  0            
4             use Moose::Util::TypeConstraints;
5             use namespace::autoclean;
6              
7             use Tie::IxHash;
8             use Carp qw(carp);
9             use overload;
10              
11             use Table::Simple::Column;
12              
13             our $VERSION = 0.02;
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 prevent 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 omitted will be deleted!
161              
162             =item delete_column
163              
164             Delete the given column from the collection.
165              
166             =item has_columns
167              
168             This method returns true if the collection has any columns. (See has_column to test whether a specific column exists.)
169              
170             =item get_column_names
171              
172             This method returns the names of all columns, preserving the order in which
173             they were added to the collection.
174              
175              
176             =item has_column
177              
178             This method returns true if the columns attribute contains the column name
179             given.
180              
181             =back
182              
183             =cut
184              
185             sub has_column {
186             my $self = shift;
187             my $column_name = shift;
188              
189             return 1 if defined $self->columns->Indices($column_name);
190             return 0;
191             }
192              
193             =over 4
194              
195             =item get_column
196              
197             This method gets the L<Table::Simple::Column> object with the given name.
198              
199             =back
200              
201             =cut
202              
203             sub get_column {
204             my $self = shift;
205             my $column_name = shift;
206              
207             if ( $self->has_column($column_name) ) {
208             return $self->columns->Values($self->columns->Indices($column_name));
209             }
210             else {
211             carp "$column_name does not exist.";
212             return;
213             }
214             }
215              
216             =over 4
217              
218             =item add_column
219              
220             This method adds a L<Table::Simple::Column> object to the columns collection.
221             You normally shouldn't need to do this.
222              
223             =back
224              
225             =cut
226              
227             sub add_column {
228             my $self = shift;
229             my $arg = shift;
230              
231             if ( not ( blessed($arg) && $arg->isa("Table::Simple::Column") ) ) {
232             carp "The add_column method only accepts Table::Simple::Column objects.\n";
233             return;
234             }
235              
236             $self->columns->Push($arg->name() => $arg);
237             }
238              
239             sub _columns_builder {
240             my $self = shift;
241              
242             return Tie::IxHash->new(@_);
243             }
244              
245             =over 4
246              
247             =item extract_columns
248              
249             Given a perl object, this method extracts the non-private attribute names
250             (that is, those which do not start with an underscore) and creates
251             L<Table::Simple::Column> objects for them. It preserves the order in
252             which columns were added to the collection.
253              
254             It will complain if you pass an argument that isn't blessed, or if you
255             try to extract columns after you've added rows.
256              
257             =back
258              
259             =cut
260              
261             sub extract_columns {
262             my $self = shift;
263             my $arg = shift;
264              
265             if ( not defined $self->type() ) {
266             $self->set_type($arg);
267             }
268              
269             if ( ! blessed($arg) ) {
270             carp "Your argument is not blessed.";
271             return;
272             }
273              
274             if ( blessed($arg) ne $self->type() ) {
275             carp "Your argument is not of type " . $self->type() . "\n";
276             return;
277             }
278              
279             if ( $self->row_count > 0 ) {
280             carp "You already added rows to this table.\n";
281             return;
282             }
283              
284             my $rv;
285              
286             if ( $self->_is_moose_object($arg) ) {
287             $rv = $self->_extract_columns_moose($arg);
288             }
289             elsif ( overload::StrVal($arg) =~ /HASH/ ) {
290             $rv = $self->_extract_columns_hashref($arg);
291             }
292             else {
293             carp "I don't know how to process your argument.\n";
294             return;
295             }
296              
297             return $rv;
298             }
299              
300             =over 4
301              
302             =item set_type
303              
304             This method sets the type attribute based on the perl object's package name.
305              
306             =back
307              
308             =cut
309              
310             sub set_type {
311             my $self = shift;
312             my $arg = shift;
313              
314             if ( not blessed $arg ) {
315             carp "$arg does not appear to be a blessed object.\n";
316             return;
317             }
318              
319             $self->type(blessed $arg);
320             }
321              
322             sub _extract_columns_moose {
323             my $self = shift;
324             my $object = shift;
325              
326             foreach my $attribute_name ( $object->meta->get_attribute_list ) {
327             next if $self->_is_private_attribute( $attribute_name );
328              
329             my $column;
330              
331             if ( not $self->has_column( $attribute_name ) ) {
332             $column = new Table::Simple::Column(name => $attribute_name);
333             }
334             $self->add_column($column) if defined $column;
335             }
336              
337             return $self->has_columns;
338              
339             }
340              
341             sub _extract_columns_hashref {
342             my $self = shift;
343             my $hashref = shift;
344              
345             foreach my $key ( keys %{ $hashref } ) {
346             next if $self->_is_private_attribute( $key );
347              
348             my $column;
349              
350             if ( not $self->has_column( $key ) ) {
351             $column = new Table::Simple::Column(name => $key);
352             }
353             $self->add_column($column) if defined $column;
354             }
355              
356             return $self->has_columns;
357             }
358              
359             =over 4
360              
361             =item extract_row
362              
363             This method extract row values from attribute names in a given perl object.
364              
365             If you haven't already set the table type, or extract columns, this method
366             will automagically do that.
367              
368             It returns the current row count.
369              
370             =back
371              
372             =cut
373              
374             sub extract_row {
375             my $self = shift;
376             my $object = shift;
377              
378             if ( ! $self->has_columns ) {
379             $self->extract_columns($object);
380             }
381              
382             if ( blessed $object ne $self->type() ) {
383             carp "Your object is not of type " . $self->type() . "\n";
384             return;
385             }
386              
387             $self->_inc_row_count;
388              
389             foreach my $column ( $self->get_columns ) {
390             my $value;
391             if ( $self->_is_moose_object($object) ) {
392             $value = $self->_get_value_using_introspection
393             ( $object, $column->name() );
394             }
395             else {
396             $value = ref $object->{$column->name}
397             ? dump($object->{$column->name})
398             : $object->{$column->name}
399             ;
400             }
401             $column->add_row($value) if defined $value;
402             }
403              
404             return $self->row_count;
405             }
406              
407             sub _is_private_attribute {
408             my $self = shift;
409             my $attr_name = shift;
410              
411             if ( $attr_name =~ /^_+/ ) {
412             return 1;
413             }
414             else {
415             return 0;
416             }
417             }
418              
419             sub _is_moose_object {
420             my $self = shift;
421             my $object = shift;
422              
423             return 1 if ( blessed($object) && $object->can("meta") );
424             return 0;
425             }
426              
427             sub _get_value_using_introspection {
428             my $self = shift;
429             my $object = shift;
430             my $attribute_name = shift;
431              
432             if ( not $object->meta->has_attribute($attribute_name) ) {
433             carp "Your object doesn't seem to have an attribute named $attribute_name.\n";
434             return;
435             }
436              
437             my $mop_attribute = $object->meta->get_attribute($attribute_name);
438              
439             if ( not $mop_attribute->has_read_method ) {
440             carp "$attribute_name doesn't seem to have a read method!\n";
441             return;
442             }
443              
444             my $read_method = $mop_attribute->get_read_method;
445              
446             return $object->$read_method();
447              
448             }
449              
450             =head1 LICENSE
451              
452             Copyright (C) 2010 Mark Allen
453              
454             This program is free software; you can redistribute it and/or modify it
455             under the terms of either: the GNU General Public License as published
456             by the Free Software Foundation; or the Artistic License.
457              
458             See http://dev.perl.org/licenses/ for more information.
459              
460             =head1 AUTHOR
461              
462             Mark Allen <mrallen1@yahoo.com>
463              
464             =head1 SEE ALSO
465              
466             L<Moose>, L<Table::Simple::Column>, L<Table::Simple::Output>
467              
468             =cut
469              
470             __PACKAGE__->meta->make_immutable();
471             1;