File Coverage

lib/BoutrosLab/TSVStream/IO/Role/Writer/Fixed.pm
Criterion Covered Total %
statement 63 74 85.1
branch 10 16 62.5
condition n/a
subroutine 19 23 82.6
pod 1 6 16.6
total 93 119 78.1


line stmt bran cond sub pod time code
1             package BoutrosLab::TSVStream::IO::Role::Writer::Fixed;
2              
3             # safe Perl
4 8     8   27 use warnings;
  8         10  
  8         257  
5 8     8   30 use strict;
  8         38  
  8         122  
6 8     8   26 use Carp;
  8         8  
  8         358  
7              
8 8     8   38 use BoutrosLab::TSVStream::IO::Role::Base::Fixed;
  8         10  
  8         133  
9              
10             =head1 NAME
11              
12             BoutrosLab::TSVStream:Writer
13              
14             =cut
15              
16 8     8   30 use Moose::Role;
  8         7  
  8         56  
17 8     8   27378 use Moose::Util::TypeConstraints;
  8         13  
  8         58  
18 8     8   10869 use namespace::autoclean;
  8         13  
  8         41  
19 8     8   472 use List::MoreUtils qw(all zip);
  8         12  
  8         70  
20 8     8   3038 use Try::Tiny;
  8         9  
  8         4572  
21              
22             with 'BoutrosLab::TSVStream::IO::Role::Base::Fixed';
23              
24             has append => ( is => 'ro', lazy => 1, isa => 'Bool', default => '0' );
25              
26             enum 'WriteHeaderType', [qw(write skip)];
27              
28             has header => (
29             is => 'ro',
30             lazy => 1,
31             isa => 'WriteHeaderType',
32             default => sub { my $self = shift; $self->append ? 'skip' : 'write' }
33             );
34              
35             has pre_headers => (
36             is => 'ro',
37             isa => 'ArrayRef[Str]',
38             default => sub { [] }
39             );
40              
41             has extra_class_params => (
42             is => 'ro',
43             isa => 'ArrayRef[Str]',
44             default => sub { [] }
45             );
46              
47             around BUILDARGS => sub {
48             my $orig = shift;
49             my $class = shift;
50             my $arg = ref($_[0]) ? $_[0] : { @_ };
51              
52             my %valid_arg = (
53             file => 1,
54             handle => 1,
55             header => 1,
56             class => 1,
57             comment => 1,
58             pre_comment => 1,
59             pre_header => 1,
60             header_fix => 1,
61             extra_class_params => 1,
62              
63             pre_headers => 1,
64             append => 1,
65             dyn_fields => 1
66             );
67             $arg->{_valid_arg} = \%valid_arg;
68             $arg->{_open_mode} = $arg->{append} ? '>>' : '>';
69             $class->$orig( $arg );
70             };
71              
72             sub _list_headers {
73 9     9   10 my $self = shift;
74 9         268 return ( $self->fields );
75             }
76              
77             sub BUILD {
78 10     10 0 11 my $self = shift;
79 10 100       319 $self->_write_lines( $self->pre_headers ) if $self->pre_header;
80 10 100       299 if ($self->header eq 'write') {
81 9         9 $self->_write_fields( @{ $self->header_fix->( $self->_list_headers ) } );
  9         22  
82             }
83             }
84              
85             sub _build_object {
86 20     20   25 my $self = shift;
87 20 50       50 my $obj = ref($_[0]) ? shift : [ @_ ];
88 20 50       756 if (ref($obj) eq 'ARRAY') {
    100          
89             $obj = $self->class->new(
90             field_values => $obj,
91 0         0 @{ $self->extra_class_params },
  0         0  
92             $self->_read_config
93             );
94             }
95             elsif (ref($obj) ne $self->class) {
96 4         5 my @altlist;
97             # assume that we got some class of object that is compatible
98             # with our own class. Get its contents and create our own
99             # class object from those contents. This will usually be
100             # done to coerce format conversions.
101             try {
102 4     4   287 @altlist = ( $self->_to_fields($obj) );
103             $obj = $self->class->new(
104             field_values => \@altlist,
105 4         135 @{ $self->extra_class_params },
  4         155  
106             $self->_read_config
107             );
108             }
109             catch {
110 0     0   0 my $error = $_;
111 0         0 $self->_croak(
112             "Arg to write must be a "
113             . $self->class
114             . " object, another object that has the same set of "
115             . "fixed and dynamic fields with compatible contents, "
116             . "or an array of strings, found: "
117             . ref($obj)
118             . " and when trying to convert by field access got error: $error"
119             );
120             }
121 4         51 }
122 20         103 return $obj;
123             }
124              
125             sub write_comments {
126 6     6 0 4 my $self = shift;
127 6 50       172 $self->_write_lines(@_) if $self->comment;
128             }
129              
130             sub write {
131 20     20 1 1996 my $self = shift;
132 20         52 my $obj = $self->_build_object( @_ );
133             # $self->_write_lines( $obj->read_comments ) if $self->comment;
134 20         23 my @list;
135 20         65 @list = ( $self->_to_fields($obj) );
136 20 50       686 $self->_croak( 'Wrong number of fields', @list )
137             unless scalar(@list) == $self->_num_fields;
138 20         58 $self->_write_fields( @list );
139             }
140              
141             sub filter {
142 0     0 0   my ( $self, $filtersub ) = @_;
143 0           return BoutrosLab::TSVStream::IO::Role::Writer::Filter->new(
144             writer => $self,
145             filtersub => $filtersub
146             );
147             }
148              
149             package BoutrosLab::TSVStream::IO::Role::Writer::Filter;
150              
151             # safe Perl
152 8     8   36 use warnings;
  8         13  
  8         210  
153 8     8   36 use strict;
  8         10  
  8         134  
154 8     8   25 use Carp;
  8         11  
  8         410  
155              
156 8     8   28 use Moose;
  8         9  
  8         44  
157              
158             has writer => (
159             is => 'ro',
160             isa => 'Object',
161             required => 1
162             );
163              
164             has filtersub => (
165             is => 'ro',
166             isa => 'CodeRef',
167             required => 1
168             );
169              
170             sub write {
171 0     0 0   my $self = shift;
172 0           my $obj = $self->writer->_build_object( @_ );
173 0 0         $self->writer->write( $obj ) if $self->filtersub->($obj);
174             }
175              
176             sub filter {
177 0     0 0   my ( $self, $filtersub ) = @_;
178 0           return BoutrosLab::TSVStream::IO::Role::Writer::Filter->new(
179             writer => $self,
180             filtersub => $filtersub
181             );
182             }
183              
184             =head1 SYNOPSIS
185              
186             $class->writer( ... );
187              
188             # ($class will use the role BoutrosLab::TSVStream which will provide
189             # the writer method, that method will return a Writer object with:
190             # ...
191             # return BoutrosLab::TSVStream::Writer->new(
192             # handle => $fd, # (required)
193             # class => $class, # (required) class
194             # file => $file, # (optional) used (as filename) in error messages
195             # header => $str, # (optional) one of: write skip (default 'write' unless append)
196             # append => 1, # (optional) if true:
197             # # file is opened for append (if stream not provided)
198             # # header defaults to 'skip'
199             # );
200              
201             while (my $record = $writer->read) {
202             # ... $record is a $class object
203             # ... use $record->field1, $record->field2, etc. - all of the methods of $class object
204             }
205              
206             =head1 DESCRIPTION
207              
208             This object provides an iterator to read through the lines
209             of a data stream (C<$fd>), converting each from a line with
210             tab separated fields into an object of a class (C<$classs>)
211             that has attributes for those fields.
212              
213             Usually, the data stream will start with a line that has the
214             fieldnames in a tab separated list, and the rest of the stream
215             has lines that contain the field values in a tab separated list.
216              
217             Any error diagnostics will refer to the stream using the
218             C<$file> filename if it is provided.
219              
220             The C<$class> class will have a class method named C<_fields>
221             that provides a ref to array of string listing the fields to
222             .be written and their order.
223              
224             A class C<$class> object must be provided for each line.
225             The object will be re-formatted into tab separated format
226             and written out.
227              
228             If C<header> is provided, it can be 'write', or 'skip'.
229             This controls what is done to the handle initially.
230              
231             If 'write' is specified, a header line is written to the stream
232             containing the field names in tab separated format before writing
233             any explicitly provided objects. This is the default. If 'skip'
234             is specified, no header is written.
235              
236             If 'skip' is specified, the stream is not checked for a header
237             line. (You would use this option either if the file does not
238             have a header line, or if you are scanning from the middle of
239             a file handle that is no longer at the start of the file.)
240              
241             =head1 ATTRIBUTES
242              
243             =head2 handle - the filehandle to be read
244              
245             =head2 file - the name of the stream, usually a filename, for diagnostic purposes
246              
247             =head2 class - the class that records transformed into
248              
249             =head2 fields - list of field names, usually provided by class
250              
251             handle, file, class and fields are provided by the ...::IO::Role::Base::Fixed role
252              
253             =head2 append - (optional) boolean to cause writes to append to the stream, causes header to default to 'skip'
254              
255             =head2 header - 'write', or 'skip' (default 'write' normally, 'skip' if 'append' is enabled)
256              
257             The C<'write'> setting causes the first line of the stream to be written
258             with a list of field names. This is the default unless the append option
259             is set.
260              
261             If the C<'skip'> setting is provided, the stream writing will start with
262             a data value. Use this either for writing a stream that is not supposed
263             to have a header line, or else to append additional values to an existing
264             file (of the same type of course). This must normally be asked for
265             explicitly, but it is the default if the append option is set.
266              
267             =head1 BUILDARGS
268              
269             The BUILDARGS method opens a handle if only a file is provided.
270              
271             =head1 BUILD
272              
273             The BUILD method handles any requirements for reading and processing a
274             header line.
275              
276             =head1 METHODS
277              
278             =head2 write - read a line to the stream from a class element
279              
280             #####
281              
282             =head1 AUTHOR
283              
284             John Macdonald - Boutros Lab
285              
286             =head1 ACKNOWLEDGEMENTS
287              
288             Paul Boutros, Phd, PI - Boutros Lab
289              
290             The Ontario Institute for Cancer Research
291              
292             =cut
293              
294             1;
295