comparison cpt_psm_plotter/lib/CPT/OutputFiles.pm @ 0:54c7a3ea81e2 draft

Uploaded
author cpt
date Tue, 05 Jul 2022 05:40:36 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:54c7a3ea81e2
1 package CPT::OutputFiles;
2 use Carp;
3 use Moose;
4 use strict;
5 use warnings;
6 use autodie;
7 use File::Spec;
8
9
10 # ABSTRACT: Handles script outputs in a sane way, providing facilities to format data and name files for regular use, or in galaxy.
11
12
13 # A list of acceptable ouput formats. Some/many of these may be missing implementations.
14 # For instance, the pandoc output format is completely unimplemented
15 # These will NEED to be re-worked.
16 has 'acceptable_formats' => (
17 is => 'ro',
18 isa => 'HashRef',
19 default => sub {
20 {
21 'text/tabular' => [qw(TSV TSV_U CSV CSV_U XLS ODS Dumper JSON YAML XLSX)],
22 'genomic/annotated' => [qw(ABI Ace AGAVE ALF AsciiTree BSML BSML_SAX ChadoXML Chaos ChaosXML CTF EMBL EntrezGene Excel Exp Fasta Fastq GAME GCG Genbank Interpro KEGG LargeFasta LaserGene LocusLink PHD PIR PLN Qual Raw SCF SeqXML Strider Swiss Tab TIGR TIGRXML TinySeq ZTR)],
23 'genomic/raw' => [qw(Fasta)],
24 'genomic/interval' => [qw(GFF3)],
25 'text/html' => [qw(HTML)], # Theoretically this will be consumed by text/report
26 'text/report' => [qw(Pandoc)],
27 'text/plain' => [qw(TXT CONF)],
28 'image/svg' => [qw(SVG)],
29 'image/png' => [qw(PNG)],
30 'archive' => [qw(tar.gz zip tar)],
31 'Dummy' => [qw(Dummy)],
32 }
33 }
34 );
35
36 has 'format_mapping' => (
37 is => 'ro',
38 isa => 'HashRef',
39 default => sub {
40 {
41 'TSV' => 'tabular',
42 'CSV' => 'tabular',
43 'TSV_U' => 'tabular',
44 'CSV_U' => 'tabular',
45 'XLS' => 'data',
46 'ODS' => 'data',
47 'Dumper' => 'txt',
48 'JSON' => 'txt',
49 'YAML' => 'txt',
50 'XLSX' => 'data',
51 'Fasta' => 'fasta',
52 'GFF3' => 'interval',
53 'HTML' => 'html',
54 'Pandoc' => 'txt',
55 'TXT' => 'txt',
56 'CONF' => 'txt',
57 'SVG' => 'xml',
58 'PNG' => 'png',
59 'Dummy' => 'data',
60 'tar.gz' => 'tar.gz',
61 'zip' => 'zip',
62 'tar' => 'tar',
63 #Genomic formats
64 'ABI' => 'data',
65 'Ace' => 'txt',
66 'AGAVE' => 'xml',
67 'ALF' => '',
68 'AsciiTree' => 'txt',
69 'BSML' => 'xml',
70 'BSML_SAX' => 'xml',
71 'ChadoXML' => 'xml',
72 'Chaos' => 'xml',
73 'ChaosXML' => 'xml',
74 'CTF' => 'data',
75 'EMBL' => 'txt',
76 'EntrezGene' => 'txt',
77 'Excel' => 'data',
78 'Exp' => 'txt',
79 'Fastq' => 'fastq',
80 'GAME' => 'xml',
81 'GCG' => 'txt',
82 'Genbank' => 'txt',
83 'Interpro' => 'xml',
84 'KEGG' => 'txt',
85 'LargeFasta' => 'txt',
86 'LaserGene' => 'data',
87 'LocusLink' => 'data',
88 'PHD' => 'data',
89 'PIR' => 'data',
90 'PLN' => 'data',
91 'Qual' => 'data',
92 'Raw' => 'txt',
93 'SCF' => 'data',
94 'SeqXML' => 'xml',
95 'Strider' => 'data',
96 'Swiss' => 'txt',
97 'Tab' => 'tabular',
98 'TIGR' => 'xml',
99 'TIGRXML' => 'xml',
100 'TinySeq' => 'xml',
101 'ZTR' => 'data',
102 }
103 }
104 );
105
106 sub valid_formats {
107 my ($self, $format) = @_;
108 return ${$self->acceptable_formats()}{$format};
109 }
110
111 sub get_format_mapping{
112 my ($self, $format) = @_;
113 return ${$self->format_mapping()}{$format};
114 }
115
116
117 # User supplied options
118 has 'name' => ( is => 'ro', isa => 'Str' );
119 has 'GGO' => ( is => 'ro', isa => 'Any' );
120 has 'galaxy' => (is => 'rw', isa => 'Bool');
121
122 # These are extracted on init from from CPT
123 has 'output_id' => (is => 'rw', isa => 'Str');
124 has 'output_label' => (is => 'rw', isa => 'Str');
125 has 'output_opts' => (is => 'rw', isa => 'HashRef');
126 # From galaxy
127 has 'new_file_path' => (is => 'rw', isa => 'Str');
128 has 'files_path' => (is => 'rw', isa => 'Str');
129 has 'files_id' => (is => 'rw', isa => 'Str');
130 # ???
131 has 'parent_filename' => (is => 'rw', isa => 'Str');
132 has 'parent_internal_format' => (is => 'rw', isa => 'Str');
133 has 'parent_default_output_format' => (is => 'rw', isa => 'Str');
134
135 has 'init_called' => (is => 'rw', isa =>'Bool');
136
137 sub initFromArgs {
138 my ($self, %args) = @_;
139
140 # We will only ever care about one (as there is one of these objects
141 # per registered output)
142 my %registered_outputs = %{$self->GGO()->registered_outputs()};
143 # If the output name specified in "name" was not known to registered_outputs
144 if(!defined($self->name())){
145 croak("You must supply a name to the instantiation of CRR");
146 }
147 #if(!defined($registered_outputs{$args{name}})){
148 #croak("The script author tried to call GGO's classyReturnResults method with an output file not mentioned in the outputs section.");
149 #}
150
151 # Carrying on
152 # We grab the pre-specified data regarding that output
153 my %reg_out_params = %{$registered_outputs{$self->name()}};
154 # Store these for future calls of sub/var
155 $self->output_id($self->name());
156 $self->output_label($reg_out_params{description});
157 $self->output_opts($reg_out_params{options});
158
159
160 $self->parent_internal_format($reg_out_params{options}{data_format});
161 $self->parent_default_output_format($reg_out_params{options}{default_format});
162 $self->parent_filename($reg_out_params{options}{default});
163
164 # Special variables
165 # --genemark "${genemark}" --genemark_format "${genemark_format}"
166 # --genemark_files_path "${genemark_files_path}"
167 # --genemark_id "${genemark_id}"
168
169 # If they've specified a filename on the command line, that should
170 # override the default value
171 if(defined $self->GGO->opt->{$self->name()}){
172 $self->parent_filename($self->GGO->opt->{$self->name()});
173 }
174 # If they've specified a {str}_format option on the command line, that
175 # should override the default value
176 if(defined $self->GGO->opt->{$self->name() . '_format'}){
177 $self->parent_default_output_format($self->GGO->opt->{$self->name() . '_format'});
178 }
179
180 # Grab supporting files path (added as new history items)
181 $self->new_file_path($self->GGO->opt->{outfile_supporting});
182 # Copy galaxy specific variables
183 if(defined $self->GGO->opt->{$self->name() . '_files_path'}){
184 $self->files_path($self->GGO->opt->{$self->name() . '_files_path'});
185 }
186 if(defined $self->GGO->opt->{$self->name() . '_id'}){
187 $self->files_id($self->GGO->opt->{$self->name() . '_id'});
188 }
189
190 # If --galaxy has been specified, we need to be aware of this
191 if ( $self->GGO->opt->{galaxy} ) {
192 $self->galaxy(1);
193 }else{
194 $self->galaxy(0);
195 }
196 $self->init_called(1);
197 }
198
199
200
201 has 'times_called' => ( is => 'rw', isa => 'Num', default => sub {0} );
202 has 'naming_strategy' => ( is => 'rw', isa => 'Str', default => "norm" ); #Other options are "var" and "sub"
203
204
205 sub _genCRR {
206 my ($self, %args) = @_;
207 if(!$self->init_called()){
208 $self->initFromArgs(%args);
209 }
210
211 # If the user supplied a custom extension, pull that (useful in
212 # dummy/data output type)
213 if(defined $args{extension}){
214 $self->extension($args{extension});
215 }
216
217 # This is a mandatory parameter
218 if(!defined $args{filename}){
219 $self->given_filename($self->parent_filename());
220 }else{
221 $self->given_filename($args{filename});
222 }
223
224 # Allow overriding default format parameters
225 my $writer = $self->writer_for_format(
226 defined $args{data_format} ? $args{data_format} : $self->parent_internal_format(),
227 defined $args{format_as} ? $args{format_as} : $self->parent_default_output_format(),
228 );
229
230 # Ugh
231 $writer->OutputFilesClass($self);
232 if($args{'data'}){
233 $writer->data( $args{'data'} );
234 }
235 $writer->process_data();
236 $writer->write();
237 my @returned_filenames = @{$writer->used_filenames()};
238 print STDERR join("\n",map{"FN: $_"} @returned_filenames)."\n";
239 $self->bump_times_called();
240 return @returned_filenames;
241 }
242
243
244 sub CRR {
245 my ( $self, %args ) = @_;
246 return $self->_genCRR(%args);
247 }
248
249
250 sub subCRR {
251 my ( $self, %args ) = @_;
252 # Change naming behaviour
253 $self->naming_strategy('sub');
254 return $self->_genCRR(%args);
255 }
256
257
258 sub varCRR {
259 my ( $self, %args ) = @_;
260 # Change naming behaviour
261 $self->naming_strategy('var');
262 return $self->_genCRR(%args);
263 }
264
265
266 sub bump_times_called{
267 my ($self) = @_;
268 $self->times_called($self->times_called() + 1 );
269 return $self->times_called();
270 }
271
272
273 sub writer_for_format{
274 my($self, $format, $requested) = @_;
275
276 # For the specified data_format, grab the acceptable handlers for that format
277 my %acceptable = %{$self->acceptable_formats()};
278 my %acceptable_handlers = map { $_ => 1 } @{ $acceptable{ $format } };
279
280 if (!$acceptable_handlers{$requested} ) {
281 carp(sprintf( "Unacceptable output format choice [%s] for internal"
282 ."data type for type %s. Acceptable formats are [%s]."
283 ."Alternatively, unacceptable output file.", $requested, $format,
284 join( ', ', keys(%acceptable_handlers) ) ));
285 }
286
287 if ( $requested eq 'Dumper' ) {
288 require CPT::Writer::Dumper;
289 return CPT::Writer::Dumper->new();
290 }
291 elsif ( $requested eq 'TSV' ) {
292 require CPT::Writer::TSV;
293 return CPT::Writer::TSV->new();
294 }
295 elsif ( $requested eq 'CSV' ) {
296 require CPT::Writer::CSV;
297 return CPT::Writer::CSV->new();
298 }
299 elsif ( $requested eq 'TSV_U' ) {
300 require CPT::Writer::TSV_U;
301 return CPT::Writer::TSV_U->new();
302 }
303 elsif ( $requested eq 'CSV_U' ) {
304 require CPT::Writer::CSV_U;
305 return CPT::Writer::CSV_U->new();
306 }
307 elsif ( $requested eq 'YAML' ) {
308 require CPT::Writer::YAML;
309 return CPT::Writer::YAML->new();
310 }
311 elsif ( $requested eq 'JSON' ) {
312 require CPT::Writer::JSON;
313 return CPT::Writer::JSON->new();
314 }
315 elsif ( $requested eq 'Pandoc' ) {
316 require CPT::Writer::Pandoc;
317 return CPT::Writer::Pandoc->new();
318 }
319 elsif ( $requested eq 'XLS' ) {
320 require CPT::Writer::Spreadsheet::XLS;
321 return CPT::Writer::Spreadsheet::XLS->new();
322 }
323 elsif ( $requested eq 'XLSX' ) {
324 require CPT::Writer::Spreadsheet::XLSX;
325 return CPT::Writer::Spreadsheet::XLSX->new();
326 }
327 elsif ( $requested eq 'TXT' || $requested eq 'CONF' ) {
328 require CPT::Writer::TXT;
329 return CPT::Writer::TXT->new();
330 }
331 elsif ( $acceptable_handlers{$requested} && $format eq 'genomic/annotated'){
332 require CPT::Writer::Genomic;
333 return CPT::Writer::Genomic->new(format => $requested);
334 }
335 elsif ( $requested eq 'Fasta' ) {
336 require CPT::Writer::Fasta;
337 return CPT::Writer::Fasta->new();
338 }
339 elsif ( $requested eq 'GFF3' ) {
340 require CPT::Writer::GFF3;
341 return CPT::Writer::GFF3->new();
342 }
343 elsif ( $requested eq 'HTML' ) {
344 require CPT::Writer::HTML;
345 return CPT::Writer::HTML->new();
346 }
347 elsif ( $requested eq 'SVG' ) {
348 require CPT::Writer::SVG;
349 return CPT::Writer::SVG->new();
350 }
351 elsif ( $requested eq 'PNG' ) {
352 require CPT::Writer::Dummy;
353 return CPT::Writer::Dummy->new();
354 }
355 elsif ( $requested eq 'Dummy' ) {
356 require CPT::Writer::Dummy;
357 return CPT::Writer::Dummy->new();
358 }
359 elsif ( $requested eq 'tar.gz') {
360 require CPT::Writer::Archive;
361 return CPT::Writer::Archive->new( format => 'tar.gz' );
362 }
363 elsif ( $requested eq 'zip') {
364 require CPT::Writer::Archive;
365 return CPT::Writer::Archive->new( format => 'zip' );
366 }
367 elsif ( $requested eq 'tar') {
368 require CPT::Writer::Archive;
369 return CPT::Writer::Archive->new( format => 'tar' );
370 }
371 else {
372 carp(sprintf("Data Format not yet supported [%s, %s]", $format, $requested));
373 }
374 }
375
376 # File extension
377 has 'extension' => ( is => 'rw', isa => 'Str');
378 # What the user said this file was called.
379 has 'given_filename' => (is => 'rw', isa => 'Str');
380
381
382
383
384 sub generate_galaxy_variable{
385 my ($self) = @_;
386 unless( -d $self->new_file_path()){
387 mkdir($self->new_file_path());
388 }
389 my $filename =File::Spec->catfile(
390 $self->new_file_path(),
391 sprintf( "primary_%s_%s_visible_%s", $self->files_id(), $self->given_filename(), $self->extension())
392 );
393 return $filename;
394 }
395
396
397 sub generate_nongalaxy_variable{
398 my ($self) = @_;
399 my $filename =File::Spec->catfile(
400 sprintf( "%s.%s", $self->given_filename(), $self->extension())
401 );
402 return $filename;
403 }
404
405
406 sub generate_galaxy_subfile {
407 my ($self) = @_;
408 unless( -d $self->files_path()){
409 mkdir($self->files_path());
410 }
411 my $filename =File::Spec->catfile(
412 $self->files_path,
413 sprintf( "%s.%s", $self->given_filename(), $self->extension())
414 );
415 return $filename;
416 }
417
418
419 sub generate_nongalaxy_subfile {
420 my ($self) = @_;
421 # they're pretty much equivalent for now
422 return $self->generate_galaxy_subfile();
423 }
424
425
426 sub get_next_file{
427 my ($self) = @_;
428 my $filename;
429 if ( $self->galaxy() ) {
430 # In which case we want to return the primary output file.
431 if ( $self->times_called() == 0 ) {
432 $filename = $self->parent_filename();
433 }
434 else {
435 if ( $self->naming_strategy eq 'sub' ) {
436 $filename = $self->generate_galaxy_subfile();
437 }
438 elsif($self->naming_strategy eq 'var') {
439 $filename = $self->generate_galaxy_variable();
440 }else{
441 confess("Unknown startegy for multiple output files: " . $self->naming_strategy());
442 }
443 }
444 }
445 else # do NOT use galaxy overrides. Paths should be more...sane
446 {
447 # First time we request, should $filename = the primary value, which
448 # should be the file they specify.
449 if ( $self->times_called() == 0 ) {
450 $filename = $self->given_filename() . '.' . $self->extension();
451 }
452 else {
453 if ( $self->naming_strategy eq 'sub' ) {
454 $filename = $self->generate_nongalaxy_subfile();
455 }
456 elsif($self->naming_strategy eq 'var') {
457 $filename = $self->generate_nongalaxy_variable();
458 }else{
459 confess("Unknown startegy for multiple output files: " . $self->naming_strategy());
460 }
461 }
462 }
463 return $filename;
464 }
465
466 no Moose;
467 1;
468
469 __END__
470
471 =pod
472
473 =encoding UTF-8
474
475 =head1 NAME
476
477 CPT::OutputFiles - Handles script outputs in a sane way, providing facilities to format data and name files for regular use, or in galaxy.
478
479 =head1 VERSION
480
481 version 1.99.4
482
483 =head1 METHODS
484
485 =head2 initFromArgs
486
487 $o->initFromArgs(name => 'GGO_known_output_name', GGO => $GGO);
488
489 Internal method to intialise data structures from the output id provide in C<name> and the data accessible via the C<GGO> object. You B<must> have already called C<< $GGO->getOptions >>
490
491 =head2 classyReturnResults
492
493 # in $GGO->getOptions(
494 outputs => [
495 ['html_page', 'HTML output page',
496 {
497 validate => 'File/Output',
498 default => 'aa', # will produce aa.html
499 data_format => 'text/html',
500 default_format => 'HTML'
501 }
502 ]
503 ['genbank_download', 'Variable number of GBK files',
504 {
505 validate => 'File/Output',
506 default => 'result', # will produce result.gbk
507 data_format => 'genomic/annotated',
508 default_format => 'Genbank'
509 }
510 ]
511 ]
512 # )
513
514 # Then in your script
515 $csv_output = CPT::OutputFiles->new(
516 name => 'html_page',
517 opt => $options,
518 );
519 $csv_output->CRR(
520 data => $data
521 );
522 # Subfile
523 my $loc = $csv_output->subCRR(
524 filename => 'cool_picture',
525 data_format=>'data',
526 extension=>"png"
527 );
528 move($png_file,$loc);
529
530 # You give subfiles a name in case you need to refer to them at any
531 # point in the parent file.
532 $csv_output->subCRR(
533 filename => 'output',
534 data => $svg_object,
535 data_format => 'image/svg',
536 format_as => 'SVG'
537 );
538
539
540 $gbk_output = CPT::OutputFiles->new(
541 name => 'genbank_download',
542 opt => $options,
543 );
544 while(my $individual_genbank = $large_seqio->next){
545 $gbk_output->varCRR(
546 filename => $individual_genbank->seqid(),
547 data => $individual_genbank,
548 );
549 }
550
551 =head2 _genCRR
552
553 _genCRR(extension => 'png', data => $data_ref, data_format => 'Dummy',
554 format_as => 'Dummy', filename => "my-image");
555
556 This is an internal method and should not be called directly. It's the end call of all C<CRR>, C<subCRR>, and C<varCRR>. Those methods should be used instead.
557
558 This method
559
560 =over 4
561
562 =item Stores some parameters
563
564 Specifically C<extension>, C<filename>, C<data_format>, C<format_as>
565
566 =item Creates a CPT::Writer
567
568 =item Calls the writer's C<write> method
569
570 =item returns an array (not arrayref) of filenames
571
572 These were the filenames that were produced in the writing process. This may be useful for data like CSV data where the output writer may produce N differently named files for each sheet of data.
573
574 =back
575
576 =head2 CRR
577
578 $o->CRR(data => $ref);
579
580 Writes data to an appropriately named file. (This is usually the "default" parameter supplied in the definition of this output). You should call this method first.
581
582 =head2 subCRR
583
584 $o->subCRR(data => $ref, filename => 'subreport', extension => 'html', data_format => 'text/html', format_as => 'HTML');
585
586 Writes data to an appropriately named sub file. A subfile is a file that will appear in a folder in the current directory. Subfiles are useful when you want to reference other output files in a primary HTML output or similar. C<subCRR> gives you a method to produce files and have them automatically placed in a sensible location, from which you can reference the files.
587
588 Files are placed in C<< $self->files_path >>. We C<mkdir> this for you, ignoring any errors. If you're paranoid you might want to re-run the mkdir/test for permissions/etc.
589
590 You must provide
591
592 =over 4
593
594 =item filename
595
596 name for the output file. You must generate this or it will be named identically to the parent. (And if you call it twice they will clobber each other silently and without mercy)
597
598 =item extension
599
600 E.g., 'png'
601
602 =item data_format
603
604 Internal data type. One of the standard C<text/html>, C<genomic/raw>, C<genomic/annotated>, etc.
605
606 =item format_as
607
608 You're welcome to provide a way to access the format parameter of subfiles to your users, however this is not done for you as there is no way for this module to know ahead of time how many subfiles you will produce.
609
610 =back
611
612 You may call this method after the first call to CRR or instead of calls to CRR
613
614 =head2 varCRR
615
616 $o->varCRR(data => $ref, filename => 'subreport', extension => 'html', data_format => 'text/html', format_as => 'HTML');
617
618 Writes data to an appropriately named var file. A var file or variable file is much like a subfile, except that in galaxy they will show up as individual history items. Additionally, the default behaviour from the command line is to place all generated files in the current working directory, rather than in a special folder.
619
620 You must provide
621
622 =over 4
623
624 =item filename
625
626 name for the output file. You must generate this or it will be named identically to the parent. (And if you call it twice they will clobber each other silently and without mercy)
627
628 =item extension
629
630 E.g., 'png'
631
632 =item data_format
633
634 Internal data type. One of the standard C<text/html>, C<genomic/raw>, C<genomic/annotated>, etc.
635
636 =item format_as
637
638 You're welcome to provide a way to access the format parameter of subfiles to your users, however this is not done for you as there is no way for this module to know ahead of time how many subfiles you will produce.
639
640 =back
641
642 You may call this method after the first call to CRR or instead of calls to CRR
643
644 =head2 bump_times_called
645
646 $o->bump_times_called();
647
648 Bumps the internal number representing the number of times you've tried to output files for a given output object. This data is used in construction of filenames
649
650 =head2 writer_for_format
651
652 $o->writer_for_format('text/tabular', 'TSV_U');
653
654 Get the appropriate writer class and instantiate it for a given C<data_format> and C<format_as>.
655
656 =head2 generate_galaxy_variable
657
658 $o->generate_galaxy_variable();
659
660 If we need the files to show up as separate History items in galaxy, filenames have to be constructed like this:
661
662 =over 4
663
664 =item F<$filepath/primary_546_output2_visible_bed>
665
666 =item F<$filepath/primary_546_output3_visible_pdf>
667
668 =back
669
670 where filenames consist of 'primary', an ID number (provided in C<outputname_id> on the command lien), a filename, 'visible', and an extension, all joined with C<_>. Additionally C<$filepath> is generally CWD (I think...)
671
672 =head2 generate_nongalaxy_variable
673
674 $o->generate_nongalaxy_variable();
675
676 =over 4
677
678 =item F<$given_filename.$extension>
679
680 =back
681
682 Parameters are taken from the object variables of the same names.
683
684 =head2 generate_galaxy_subfile
685
686 $o->generate_galaxy_subfile();
687
688 =over 4
689
690 =item F<$files_path/$given_filename.$extension>
691
692 =back
693
694 The paths for images and other files will end up looking something like
695 F</home/galaxy/galaxy_dist/database/files/000/dataset_56/img1.jpg> with the galaxy provided C<files_path> prepended to the filename.
696
697 =head2 generate_nongalaxy_subfile
698
699 $o->generate_nongalaxy_subfile();
700
701 See L</generate_galaxy_subfile>. Know that the default for C<< $self->files_path >> is C<"outputname.files_path">. It's only "special" when run from inside galaxy.
702
703 =head2 get_next_file
704
705 $o->get_next_file();
706
707 If it's the first time this method has been called, it constructs a default filename. If the C<galaxy> variable is true, then it's just whatever value was passed. Otherwise it's just C<given_filename> and C<extension> put together. C<given_filename> is taken from C<parent_filename>.
708
709 If it's not the first time it was called, this module expects you to be using L</varCRR> or L</subCRR> to call (which has set C<naming_strategy>). Those will generate appropriate filenames with calls to one of
710
711 =over 4
712
713 =item L</generate_galaxy_subfile>
714
715 =item L</generate_galaxy_variable>
716
717 =item L</generate_nongalaxy_subfile>
718
719 =item L</generate_nongalaxy_variable>
720
721 =back
722
723 based on appropriate variables.
724
725 =head1 AUTHOR
726
727 Eric Rasche <rasche.eric@yandex.ru>
728
729 =head1 COPYRIGHT AND LICENSE
730
731 This software is Copyright (c) 2014 by Eric Rasche.
732
733 This is free software, licensed under:
734
735 The GNU General Public License, Version 3, June 2007
736
737 =cut