File Coverage

blib/lib/Text/CSV/Track.pm
Criterion Covered Total %
statement 250 254 98.4
branch 108 124 87.1
condition 3 3 100.0
subroutine 26 26 100.0
pod 11 11 100.0
total 398 418 95.2


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Text::CSV::Track - module to work with .csv file that stores some value(s) per identificator
4              
5             =head1 VERSION
6              
7             This documentation refers to version 0.7.
8              
9             =head1 SYNOPSIS
10              
11             use Text::CSV::Track;
12            
13             #create object
14             my $access_time = Text::CSV::Track->new({ file_name => $file_name, ignore_missing_file => 1 });
15            
16             #set single value
17             $access_time->value_of($login, $access_time);
18              
19             #fetch single value
20             print $access_time->value_of($login);
21              
22             #set multiple values
23             $access_time->value_of($login, $access_time);
24            
25             #fetch multiple values
26             my @fields = $access_time->value_of($login);
27            
28             #save changes
29             $access_time->store();
30            
31             #print out all the identificators we have
32             foreach my $login (sort $access_time->ident_list()) {
33             print "$login\n";
34             }
35              
36             #getting muticolumn by hash
37             $track_object = Text::CSV::Track->new({
38             file_name => $file_name
39             , hash_names => [ qw{ col coool } ]
40             });
41             my %hash = %{$track_object->hash_of('ident')};
42             print "second column is: ", $hash{'coool'}, "\n";
43              
44             #setting multicolumn by hash
45             $track_object->hash_of('ident2', { coool => 333 } );
46              
47             #header lines
48             $track_object = Text::CSV::Track->new({
49             file_name => $file_name,
50             header_lines => \@header_lines,
51             ignore_missing_file => 1,
52             });
53              
54             =head1 DESCRIPTION
55              
56             The module manipulates csv file:
57              
58             "identificator","value1"
59             ...
60              
61             It is designet to work when multiple processes access the same file at
62             the same time. It uses lazy initialization. That mean that the file is
63             read only when it is needed. There are three scenarios:
64              
65             1. Only reading of values is needed. In this case first ->value_of() also
66             activates the reading of file. File is read while holding shared flock.
67             Then the lock is released.
68              
69             2. Only setting of values is needed. In this case ->value_of($ident,$val)
70             calls just saves the values to the hash. Then when ->store() is called
71             it activates the reading of file. File is read while holding exclusive flock.
72             The identifications that were stored in the hash are replaced, the rest
73             is kept.
74              
75             3. Both reading and setting values is needed. In this case 'full_time_lock'
76             flag is needed. The exclusive lock will be held from the first read until
77             the object is destroied. While the lock is there no other process that uses
78             flock can read or write to this file.
79              
80             When setting and getting only single value value_of($ident) will return scalar.
81             If setting/getting multiple columns then an array.
82              
83             =head1 METHODS
84              
85             =over 4
86              
87             =item new()
88              
89             new({
90             file_name => 'filename.csv',
91             ignore_missing_file => 1,
92             full_time_lock => 1,
93             auto_store => 1,
94             ignore_badly_formated => 1,
95             header_lines => 3, #or [ '#heading1', '#heading2', '#heading3' ]
96             footer_lines => 3, #or [ '#footer1', '#footer2', '#footer3' ]
97             hash_names => [ qw{ column1 column2 } ],
98             single_column => 1,
99             trunc => 1,
100             replace_new_lines_with => '|',
101             identificator_column_number => 0,
102              
103             #L paramteres
104             sep_char => q{,},
105             escape_char => q{\\},
106             quote_char => q{"},
107             always_quote => 0,
108             binary => 0,
109             })
110            
111             All flags are optional.
112              
113             'file_name' is used to read old results and then store the updated ones
114              
115             If 'ignore_missing_file' is set then the lib will just warn that it can not
116             read the file. store() will use this name to store the results.
117              
118             If 'full_time_lock' is set the exclusive lock will be held until the object is
119             not destroyed. use it when you need both reading the values and changing the values.
120             If you need just read OR change then you don't need to set this flag. See description
121             about lazy initialization.
122              
123             If 'auto_store' is on then the store() is called when object is destroied
124              
125             If 'ignore_badly_formated_lines' in on badly formated lines from input are ignored.
126             Otherwise the modules calls croak.
127              
128             'header_lines' specifies how many lines of csv are the header lines. They will
129             be skipped during the reading of the file and rewritten during the storing to the
130             file. After first read of value the ->header_lines becomes array ref of header lines.
131             Optionaly you can set array ref and set the header lines.
132              
133             'hash_names' specifies hash names fro hash_of() function.
134              
135             'single_column' files that store just the identificator for line. In this case
136             during the read 1 is set as the second column. During store that one is dropped
137             so single column will be stored back.
138              
139             'trunc' don't read previous file values. Header lines will persist.
140              
141             'replace_new_lines_with' [\n\r]+ are replaced by this character if defined. By
142             default it is '|'. It is a good idea to replace new lines because they are not
143             handled by Text::CSV_XS on read.
144              
145             'identificator_column_number'. If identificator is in different column than the
146             first one set this value. Column are numbered starting with 0 like in an
147             @array. ->value_of and ->hash_of are indexed as it the identificator column
148             was not there.
149              
150             See L for 'sep_char', 'escape_char', 'quote_char', 'always_quote', 'binary'
151              
152             =item value_of()
153              
154             Is used to both store or retrieve the value. if called with one argument
155             then it is a read. if called with two arguments then it will update the
156             value. The update will be done ONLY if the supplied value is bigger.
157              
158             =item hash_of()
159              
160             Returns hash of values. Names for the hash values are taken from hash_names parameter.
161              
162             =item store()
163              
164             when this one is called it will write the changes back to file.
165              
166             =item store_as_xml()
167              
168             this will write to the file but the values will be excel xml formated. Combined with
169             proper header and footer lines this can generate excel readable xml file.
170              
171             =item ident_list()
172              
173             will return the array of identificators
174              
175             =item output_row_of($ident, $type)
176              
177             $type is one of csv or xml.
178              
179             Returns one row of data for given identificator.
180              
181             =item csv_line_of($identificator)
182              
183             Calls $self->output_row_of($identificator, 'csv').
184              
185             =item header_lines()
186              
187             Set or get header lines.
188              
189             =item footer_lines()
190              
191             Set or get footer lines.
192              
193             =item finish()
194              
195             Called by destructor to clean up thinks. Calls store() if auto_atore is on
196             and closes csv filehandle.
197              
198             =cut
199              
200             =back
201              
202             =head1 TODO
203              
204             - ident_list() should return number of non undef rows in scalar context
205             - strategy for Track ->new({ strategy => sub { $a > $b } })
206             - then rewrite max/min to use it this way
207             - constraints for columns
208             - shell executable to copy, dump csv file or extract data from it
209             - allow having extended csv with header names in every file key=value;key2=value2
210             - atomic writes
211             - allow extended csv lines, lines that look like:
212             key=value1,key5=value2,key2=value3
213              
214             =head1 SEE ALSO
215              
216             L, L, Module Trac - L
217              
218             =head1 AUTHOR
219              
220             Jozef Kutej
221              
222             =cut
223              
224              
225              
226             package Text::CSV::Track;
227              
228             our $VERSION = '0.8';
229 4     4   46015 use 5.006;
  4         15  
  4         154  
230              
231 4     4   20 use strict;
  4         7  
  4         116  
232 4     4   18 use warnings;
  4         7  
  4         132  
233              
234 4     4   20 use base qw(Class::Accessor::Fast);
  4         9  
  4         3826  
235             __PACKAGE__->mk_accessors(
236             qw(
237             file_name
238             _file_fh
239             _rh_value_of
240             _lazy_init
241             ignore_missing_file
242             full_time_lock
243             auto_store
244             _no_lock
245             ignore_badly_formated
246             _csv_format
247             header_lines
248             footer_lines
249             hash_names
250             single_column
251             trunc
252             replace_new_lines_with
253             identificator_column_number
254              
255             sep_char
256             escape_char
257             quote_char
258             always_quote
259             binary
260             type
261             )
262             );
263              
264 4     4   17485 use FindBin;
  4         4872  
  4         209  
265              
266 4     4   5194 use Text::CSV_XS;
  4         8597213  
  4         296  
267 4     4   3663 use Carp::Clan;
  4         15650  
  4         27  
268 4     4   4310 use English qw(-no_match_vars);
  4         18648  
  4         25  
269 4     4   1858 use Fcntl ':flock'; # import LOCK_* constants
  4         7  
  4         462  
270 4     4   27 use Fcntl ':seek'; # import SEEK_* constants
  4         8  
  4         468  
271 4     4   3967 use List::MoreUtils qw { first_index };
  4         4993  
  4         342  
272 4     4   3951 use IO::Handle; #must be because file_fh->input_line_number function
  4         37568  
  4         306811  
273              
274              
275             sub new {
276 62     62 1 22870 my $class = shift;
277 62         155 my $ra_arg = shift;
278              
279             #build object from parent
280 62         348 my $self = $class->SUPER::new($ra_arg);
281              
282             #create empty pointers
283 62         820 $self->{_rh_value_of} = {};
284 62 100       267 $self->{header_lines} = [] if not defined $self->{header_lines};
285 62 100       216 $self->{footer_lines} = [] if not defined $self->{footer_lines};
286            
287 62         163 return $self;
288             }
289              
290             sub output_row_of {
291 1557     1557 1 2165 my $self = shift;
292 1557         8227 my $identificator = shift;
293 1557         1474 my $type = shift;
294            
295             #combine values for csv file
296 1557         2556 my @fields = $self->value_of($identificator);
297              
298             #removed entry
299 1557 100 100     7250 return undef if (@fields == 1) and (not defined $fields[0]);
300              
301             #if in single column mode remove '1' from the start of the fields
302 1553 100       3956 shift(@fields) if $self->single_column;
303            
304             #remove new lines
305 1553 50       11199 if (defined $self->replace_new_lines_with) {
306 1553         8429 my $replacement = $self->replace_new_lines_with;
307 1553         6220 foreach my $field (@fields) {
308 1558 100       2837 next if not defined $field;
309 1556         5362 $field =~ s{[\n\r]+}{$replacement}sg;
310             }
311             }
312            
313             #add identificator to the values
314 1553         4125 splice(@fields, $self->identificator_column_number, 0, $identificator);
315            
316 1553 100       8033 if ($type eq 'csv') {
    50          
317 1549 100       3774 croak "invalid value to store to an csv file - ", $self->_csv_format->error_input(),"\n"
318             if (not $self->_csv_format->combine(@fields));
319              
320 1548         28982 return $self->_csv_format->string();
321             }
322             elsif ($type eq 'xml') {
323 4         7 my $xml_line = ''."\n";
324 4         6 $xml_line .= ' '.$identificator.''."\n";
325 4         9 foreach my $col_value ($self->value_of($identificator)) {
326 4 50       8 $col_value = '' if not defined $col_value;
327 4         12 $xml_line .= ' '.$col_value.''."\n";
328             }
329 4         8 $xml_line .= '';
330              
331 4         10 return $xml_line;
332             }
333             else {
334 0         0 croak "unknow output format";
335             }
336             }
337              
338              
339             sub csv_line_of {
340 1     1 1 610 my $self = shift;
341 1         3 my $identificator = shift;
342            
343 1         4 return $self->output_row_of($identificator, 'csv');
344             }
345              
346              
347             sub value_of {
348 6948     6948 1 10884 my $self = shift;
349 6948         7685 my $identificator = shift;
350 6948         6773 my $is_set = 0; #by default get
351              
352             #if we have one more parameter then it is set
353 6948         6728 my $value;
354 6948 100       23816 if (@_ >= 1) {
355 2733         2728 $is_set = 1;
356 2733         3586 $value = \@_;
357             }
358              
359             #check if we have identificator
360 6948 50       12839 return if not $identificator;
361            
362             #value_of hash
363 6948         17694 my $rh_value_of = $self->_rh_value_of;
364              
365             #lazy initialization is needed for get
366 6948 100       34358 $self->_init() if not $is_set;
367              
368             #switch between set and get variant
369             #set
370 6947 100       26863 if ($is_set) {
371 2733         18200 $rh_value_of->{$identificator} = $value;
372             }
373             #get
374             else {
375 4214 100       12081 return undef if not defined $rh_value_of->{$identificator};
376            
377             #if we have more then one field return array
378 1642 100       1644 if (@{$rh_value_of->{$identificator}} > 1) {
  1642         3345  
379 21         22 return @{$rh_value_of->{$identificator}};
  21         92  
380             }
381             #otherwise return one and only value from array as scallar
382             else {
383 1621         1711 return ${$rh_value_of->{$identificator}}[0];
  1621         5090  
384             }
385             }
386             }
387              
388              
389             sub hash_of {
390 8     8 1 3580 my $self = shift;
391 8         15 my $identificator = shift;
392 8         10 my $is_set = 0; #by default get
393              
394 8 50       25 croak "'hash_names' parameter not set" if not defined $self->hash_names;
395 8         43 my @hash_names = @{$self->hash_names};
  8         19  
396 8         47 my @fields = $self->value_of($identificator);
397              
398             #if we have one more parameter then it is set
399 8         12 my $rh;
400 8 100       22 if (@_ >= 1) {
401 3         5 $is_set = 1;
402 3         4 $rh = shift;
403            
404 3 50       11 croak "not a hash reference as set argument" if ref $rh ne 'HASH';
405             }
406            
407 8 100       17 if ($is_set) {
408 3         3 foreach my $key (keys %{$rh}) {
  3         10  
409 5     18   39 my $index = first_index { $_ eq $key } @hash_names;
  18         23  
410            
411 5 100       25 croak "no such hash key name '$key'" if $index == -1;
412              
413 4         10 $fields[$index] = $rh->{$key};
414             }
415            
416             #save back the fields
417 2         9 $self->value_of($identificator, @fields);
418             }
419             else {
420 5         6 my %hash;
421 5         11 foreach my $name (@hash_names) {
422 22         47 $hash{$name} = shift @fields;
423             }
424            
425 5         46 return \%hash;
426             }
427             }
428              
429              
430             sub store_as_xml {
431 1     1 1 3 my $self = shift;
432              
433 1         8 return $self->store(1);
434             }
435              
436              
437             sub store {
438 36     36 1 2023 my $self = shift;
439 36         59 my $store_as_xml = shift;
440              
441             #lazy initialization
442 36         79 $self->_init();
443              
444             #get local variables from self hash
445 36         163 my $rh_value_of = $self->_rh_value_of;
446 36         210 my $file_name = $self->file_name;
447 36         209 my $full_time_lock = $self->full_time_lock;
448 36         8598 my $file_fh = $self->_file_fh;
449              
450 36 50       179 if (not $full_time_lock) {
451 36 50       2044 open($file_fh, "+>>", $file_name) or croak "can't write to file '$file_name' - $OS_ERROR";
452            
453             #lock and truncate the access store file
454 36 50       283 flock($file_fh, LOCK_EX) or croak "can't lock file '$file_name' - $OS_ERROR\n";
455             }
456            
457             #loop through identificators and store to array only if all works fine file will be overwritten
458 36         46 my @file_lines;
459 36         102 foreach my $identificator (sort $self->ident_list()) {
460 1556         1797 my $file_line;
461 1556 100       2551 if (defined $store_as_xml) {
462 4         11 $file_line = $self->output_row_of($identificator, 'xml');
463             }
464             else {
465 1552         3116 $file_line = $self->output_row_of($identificator, 'csv');
466             }
467              
468             #skip removed entries
469 1555 100       21266 next if not $file_line;
470            
471 1551         3877 push(@file_lines, $file_line."\n");
472             }
473              
474             #truncate the file so that we can store new results
475 35 50       2734 truncate($file_fh, 0) or croak "can't truncate file '$file_name' - $OS_ERROR\n";
476            
477             #write header lines
478 35         51 foreach my $header_line (@{$self->header_lines}) {
  35         105  
479 31         30 print {$file_fh} $header_line, "\n";
  31         97  
480             }
481              
482             #write csv lines
483 35         69 foreach my $line (@file_lines) {
484             #print the line to csv file
485 1551         1335 print {$file_fh} $line;
  1551         3605  
486             }
487            
488             #write footer lines
489 35         54 foreach my $footer_line (@{$self->footer_lines}) {
  35         99  
490 12         12 print {$file_fh} $footer_line, "\n";
  12         24  
491             }
492              
493 35         1889 close($file_fh);
494             }
495              
496             #lazy initialization
497             sub _init {
498 4361     4361   4408 my $self = shift;
499            
500 4361 100       9116 return if $self->_lazy_init;
501              
502             #prevent from reexecuting
503 62         381 $self->_lazy_init(1);
504            
505             #default values
506 62 100       515 $self->replace_new_lines_with('|') if not exists $self->{'replace_new_lines_with'};
507 62 100       1029 $self->binary(1) if not exists $self->{'binary'};
508 62 100       522 $self->identificator_column_number(0) if not exists $self->{'identificator_column_number'};
509            
510             #get local variables from self hash
511 62         419 my $rh_value_of = $self->_rh_value_of;
512 62         366 my $file_name = $self->file_name;
513 62         346 my $ignore_missing_file = $self->ignore_missing_file;
514 62         313 my $full_time_lock = $self->full_time_lock;
515 62         305 my $_no_lock = $self->_no_lock;
516 62         221 my $header_lines_count;
517             my $header_lines_from_file;
518 0         0 my $footer_lines_count;
519 0         0 my $footer_lines_from_file;
520              
521 62 100       299 if (ref $self->{header_lines} eq 'ARRAY') {
522 50         62 $header_lines_count = scalar @{$self->header_lines};
  50         117  
523 50         75 $header_lines_from_file = 0;
524             }
525             else {
526             #initialize header_lines with array of empty strings if header_lines is number
527 12         18 $header_lines_count = $self->{header_lines};
528 12         29 $self->header_lines([ map {""} (1 .. $header_lines_count) ]);
  34         78  
529 12         15 $header_lines_from_file = 1;
530             }
531            
532 62 100       153 if (ref $self->{footer_lines} eq 'ARRAY') {
533 56         64 $footer_lines_count = scalar @{$self->footer_lines};
  56         114  
534 56         79 $footer_lines_from_file = 0;
535             }
536             else {
537             #initialize footer_lines with array of empty strings if footer_lines is number
538 6         7 $footer_lines_count = $self->{footer_lines};
539 6         12 $self->footer_lines([ map {""} (1 .. $footer_lines_count) ]);
  18         32  
540 6         9 $footer_lines_from_file = 1;
541             }
542            
543             #Text::CSV_XS variables
544 62 100       244 my $sep_char = defined $self->sep_char ? $self->sep_char : q{,};
545 62 100       431 my $escape_char = defined $self->escape_char ? $self->escape_char : q{\\};
546 62 100       416 my $quote_char = defined $self->quote_char ? $self->quote_char : q{"};
547 62         363 my $always_quote = $self->always_quote;
548 62         274 my $binary = $self->binary;
549            
550             #done with initialization if file_name empty
551 62 100       289 return if not $file_name;
552              
553             #define csv format
554 59         529 $self->_csv_format(Text::CSV_XS->new({
555             sep_char => $sep_char,
556             escape_char => $escape_char,
557             quote_char => $quote_char,
558             always_quote => $always_quote,
559             binary => $binary,
560             }));
561              
562             #default open mode is reading
563 59         9170 my $open_mode = '<';
564            
565             #if full_time_lock is set do open for writting
566 59 100       146 if ($full_time_lock) {
567 3 50       11 if ($ignore_missing_file) {
568 0         0 $open_mode = '+>>';
569             }
570             else {
571 3         8 $open_mode = '+<';
572             }
573             }
574              
575             #open file with old stored values and handle error
576 59         71 my $file_fh;
577 59 100       2606 if (not open($file_fh, $open_mode, $file_name)) {
578 7 100       19 if ($ignore_missing_file) {
579 6         14 $OS_ERROR = undef;
580 6         29 return;
581             }
582             else {
583 1         19 croak "can't read file '$file_name' - $OS_ERROR";
584             }
585             }
586            
587             #do exclusive lock if full time lock
588 52 100       160 if ($full_time_lock) {
    50          
589 3 50       24 flock($file_fh, LOCK_EX) or croak "can't lock file '$file_name' - $OS_ERROR\n";
590 3         19 seek($file_fh, 0, SEEK_SET);
591             }
592             #internal flag. used from within the same module if file is already locked
593             elsif ($_no_lock) {
594             }
595             #otherwise shared lock is enought
596             else {
597 49 50       408 flock($file_fh, LOCK_SH) or croak "can't lock file '$file_name' - $OS_ERROR\n";
598             }
599            
600 52         77 my $lines_count = 0;
601 52         4701 $lines_count++ while (<$file_fh>);
602              
603             #reset file position
604 52         278 seek($file_fh, 0, SEEK_SET);
605 52         262 $file_fh->input_line_number(0);
606              
607             #create hash of identificator => 1
608 52         1266 my %identificator_exist = map { $_ => 1 } $self->ident_list;
  24         81  
609              
610             #parse lines and store values in the hash
611             LINE:
612 52         488 while (my $line = <$file_fh>) {
613 2625         2862 chomp($line);
614 2625         2412 $lines_count--;
615            
616             #skip header lines and save them for store()
617 2625 100       4752 if ($header_lines_count) {
618             #save header line if not defined
619 33 100       65 ${$self->header_lines}[$file_fh->input_line_number-1] = $line if $header_lines_from_file;
  30         51  
620            
621             #decrease header lines code so then we will know when there is an end of headers
622 33         377 $header_lines_count--;
623            
624 33         92 next;
625             }
626              
627             #skip footer lines and save them for store()
628 2592 100       4527 if ($lines_count < $footer_lines_count) {
629             #save footer lines if not defined
630 15 50       29 ${$self->footer_lines}[$footer_lines_count - $lines_count - 1] = $line if $footer_lines_from_file;
  15         27  
631            
632 15         64 next;
633             }
634            
635             #skip reading of values if in 'trunc' mode
636 2577 100       6332 next if $self->trunc;
637            
638             #verify line. if incorrect skip with warning
639 2567 100       14370 if (!$self->_csv_format->parse($line)) {
640 3         101 my $msg = "badly formated '$file_name' csv line " . $file_fh->input_line_number() . " - '$line'.\n";
641              
642             #by default croak on bad line
643 3 100       68 croak $msg if not $self->ignore_badly_formated;
644            
645             #if ignore_badly_formated_lines is on just print warning
646 2         3401 warn $msg;
647            
648 2         33 next;
649             }
650            
651             #extract fields
652 2564         59641 my @fields = $self->_csv_format->fields();
653 2564         26558 my $identificator = splice(@fields, $self->identificator_column_number, 1);
654            
655             #if in single column mode insert '1' to the fields
656 2564 100       13803 unshift(@fields, 1) if $self->single_column;
657              
658             #save present fields
659 2564         16343 my @old_fields = $self->value_of($identificator);
660            
661             #set the value from file
662 2564         5140 $self->value_of($identificator, @fields);
663              
664             #set the value from before values from file was read !needed becouse of the strategy!
665 2564 100       12549 $self->value_of($identificator, @old_fields) if $identificator_exist{$identificator};
666             }
667              
668             #if full time lock then store file handle
669 51 100       122 if ($full_time_lock) {
670 3         14 $self->_file_fh($file_fh);
671             }
672             #otherwise release shared lock and close file
673             else {
674 48 50       463 flock($file_fh, LOCK_UN) if not $_no_lock;
675 48         1040 close($file_fh);
676             }
677             }
678              
679              
680             sub ident_list {
681 105     105 1 210 my $self = shift;
682              
683             #lazy initialization
684 105         257 $self->_init();
685              
686             #get local variables from self hash
687 104         681 my $rh_value_of = $self->_rh_value_of;
688              
689 104         528 return keys %{$rh_value_of};
  104         1261  
690             }
691              
692              
693             sub header_lines {
694 130     130 1 242 my $self = shift;
695              
696             #set
697 130 100       254 if (@_ >= 1) {
698 12         26 $self->{header_lines} = shift;
699             } else
700             #get
701             {
702             #if _header_lines then do lazy init and get the header lines from file
703 118 100       333 $self->_init if (ref $self->{header_lines} ne 'ARRAY');
704            
705 118         365 return $self->{header_lines};
706             }
707            
708             }
709              
710              
711             sub footer_lines {
712 119     119 1 152 my $self = shift;
713              
714             #set
715 119 100       251 if (@_ >= 1) {
716 9         18 $self->{footer_lines} = shift;
717             } else
718             #get
719             {
720             #if footer_lines is not array then do lazy init and get the footer lines from file
721 110 100       361 $self->_init if (ref $self->{footer_lines} ne 'ARRAY');
722            
723 110         283 return $self->{footer_lines};
724             }
725             }
726              
727              
728             sub finish {
729 62     62 1 86 my $self = shift;
730              
731             #call store if in auto_store mode
732 62 100       207 $self->store() if $self->auto_store;
733              
734             #get local variables from self hash
735 62         501 my $file_fh = $self->_file_fh;
736              
737 62 100       317 if (defined $file_fh) {
738 3         67 close($file_fh);
739             }
740              
741 62         157 $self->_file_fh(undef);
742             }
743              
744             sub DESTROY {
745 62     62   6487 my $self = shift;
746              
747 62         175 $self->finish();
748             }
749              
750             1;
751              
752             =head1 AUTHOR
753              
754             Jozef Kutej - Ejozef@kutej.netE
755              
756             =head1 COPYRIGHT AND LICENSE
757              
758             Copyright (C) 2006 by Jozef Kutej
759              
760             This library is free software; you can redistribute it and/or modify
761             it under the same terms as Perl itself, either Perl version 5.8.4 or,
762             at your option, any later version of Perl 5 you may have available.
763              
764             =cut