r878@llin: dpavlin | 2006-08-26 14:00:08 +0200
[webpac2] / lib / WebPAC / Common.pm
index fbc241b..6264700 100644 (file)
@@ -3,7 +3,11 @@ package WebPAC::Common;
 use warnings;
 use strict;
 
-use Log::Log4perl qw(get_logger :levels);
+use Log::Log4perl qw/get_logger :levels/;
+use Time::HiRes qw/time/;
+
+# If ture, enable logging debug
+my $log_debug = 0;
 
 =head1 NAME
 
@@ -11,165 +15,91 @@ WebPAC::Common - internal methods called from other WebPAC modules
 
 =head1 VERSION
 
-Version 0.01
+Version 0.02
 
 =cut
 
-our $VERSION = '0.01';
-
-#my $LOOKUP_REGEX = '\[[^\[\]]+\]';
-#my $LOOKUP_REGEX_SAVE = '\[([^\[\]]+)\]';
-my $LOOKUP_REGEX = 'lookup{[^\{\}]+}';
-my $LOOKUP_REGEX_SAVE = 'lookup{([^\{\}]+)}';
+our $VERSION = '0.02';
 
 =head1 SYNOPSYS
 
 This module defines common functions, and is used as base for other, more
 specific modules.
 
- my $webpac = new WebPAC::Common(
-       filter => {
-               'filter_name_1' => sub {
-                       # filter code
-                       return length($_);
-               }, ...
-       },
-  }
-
 =head1 FUNCTIONS
 
-=head2 fill_in
-
-Workhourse of all: takes record from in-memory structure of database and
-strings with placeholders and returns string or array of with substituted
-values from record.
-
- my $text = $webpac->fill_in($rec,'v250^a');
-
-Optional argument is ordinal number for repeatable fields. By default,
-it's assume to be first repeatable field (fields are perl array, so first
-element is 0).
-Following example will read second value from repeatable field.
-
- my $text = $webpac->fill_in($rec,'Title: v250^a',1);
+=head2 progress_bar
 
-This function B<does not> perform parsing of format to inteligenty skip
-delimiters before fields which aren't used.
+Draw progress bar on STDERR.
 
-This method will automatically decode UTF-8 string to local code page
-if needed.
+ $webpac->progress_bar($current, $max);
 
 =cut
 
-sub fill_in {
+sub progress_bar {
        my $self = shift;
 
+       my ($curr,$max) = @_;
+
        my $log = $self->_get_logger();
 
-       my $rec = shift || $log->logconfess("need data record");
-       my $format = shift || $log->logconfess("need format to parse");
-       # iteration (for repeatable fields)
-       my $i = shift || 0;
+       $log->logconfess("no current value!") if (! $curr);
+       $log->logconfess("no maximum value!") if (! $max);
 
-       $log->logdie("infitite loop in format $format") if ($i > ($self->{'max_mfn'} || 9999));
+       if ($curr > $max) {
+               $max = $curr;
+               $log->debug("overflow to $curr");
+       }
 
-       # FIXME remove for speedup?
-       $log->logconfess("need HASH as first argument!") if ($rec !~ /HASH/o);
+       $self->{'last_pcnt'} ||= 1;
+       $self->{'start_t'} ||= time();
 
-       if (utf8::is_utf8($format)) {
-               $format = $self->_x($format);
-       }
+       my $p = int($curr * 100 / $max) || 1;
 
-       my $found = 0;
-
-       my $eval_code;
-       # remove eval{...} from beginning
-       $eval_code = $1 if ($format =~ s/^eval{([^}]+)}//s);
-
-       my $filter_name;
-       # remove filter{...} from beginning
-       $filter_name = $1 if ($format =~ s/^filter{([^}]+)}//s);
-
-       # do actual replacement of placeholders
-       # repeatable fields
-       $format =~ s/v(\d+)(?:\^(\w))?/$self->get_data(\$rec,$1,$2,$i,\$found)/ges;
-       # non-repeatable fields
-       $format =~ s/s(\d+)(?:\^(\w))?/$self->get_data(\$rec,$1,$2,0,\$found)/ges;
-
-       if ($found) {
-               $log->debug("format: $format");
-               if ($eval_code) {
-                       my $eval = $self->fill_in($rec,$eval_code,$i);
-                       return if (! $self->_eval($eval));
-               }
-               if ($filter_name && $self->{'filter'}->{$filter_name}) {
-                       $log->debug("filter '$filter_name' for $format");
-                       $format = $self->{'filter'}->{$filter_name}->($format);
-                       return unless(defined($format));
-                       $log->debug("filter result: $format");
-               }
-               # do we have lookups?
-               if ($format =~ /$LOOKUP_REGEX/o) {
-                       $log->debug("format '$format' has lookup");
-                       return $self->lookup($format);
-               } else {
-                       return $format;
-               }
-       } else {
-               return;
+       # reset on re-run
+       if ($p < $self->{'last_pcnt'}) {
+               $self->{'last_pcnt'} = $p;
+               $self->{'start_t'} = time();
        }
-}
-
 
-=head2 get_data
+       if ($p != $self->{'last_pcnt'}) {
 
-Returns value from record.
+               my $t = time();
+               my $rate = ($curr / ($t - $self->{'start_t'} || 1));
+               my $eta = ($max-$curr) / ($rate || 1);
+               printf STDERR ("%5d [%-38s] %-5d %0.1f/s %s\r",$curr,"=" x ($p/3)."$p%>", $max, $rate, $self->fmt_time($eta));
+               $self->{'last_pcnt'} = $p;
+               $self->{'last_curr'} = $curr;
+       }
+       print STDERR "\n" if ($p == 100);
+}
 
- my $text = $self->get_data(\$rec,$f,$sf,$i,\$found);
+=head2 fmt_time
 
-Arguments are:
-record reference C<$rec>,
-field C<$f>,
-optional subfiled C<$sf>,
-index for repeatable values C<$i>.
+Format time (in seconds) for display.
 
-Optinal variable C<$found> will be incremeted if there
-is field.
+ print $webpac->fmt_time(time());
 
-Returns value or empty string.
+This method is called by L<progress_bar> to display remaining time.
 
 =cut
 
-sub get_data {
+sub fmt_time {
        my $self = shift;
 
-       my ($rec,$f,$sf,$i,$found) = @_;
-
-       if ($$rec->{$f}) {
-               return '' if (! $$rec->{$f}->[$i]);
-               no strict 'refs';
-               if ($sf && $$rec->{$f}->[$i]->{$sf}) {
-                       $$found++ if (defined($$found));
-                       return $$rec->{$f}->[$i]->{$sf};
-               } elsif ($$rec->{$f}->[$i]) {
-                       $$found++ if (defined($$found));
-                       # it still might have subfield, just
-                       # not specified, so we'll dump all
-                       if ($$rec->{$f}->[$i] =~ /HASH/o) {
-                               my $out;
-                               foreach my $k (keys %{$$rec->{$f}->[$i]}) {
-                                       $out .= $$rec->{$f}->[$i]->{$k}." ";
-                               }
-                               return $out;
-                       } else {
-                               return $$rec->{$f}->[$i];
-                       }
-               }
-       } else {
-               return '';
-       }
+       my $t = shift || 0;
+       my $out = "";
+
+       my ($ss,$mm,$hh) = gmtime($t);
+       $out .= "${hh}h" if ($hh);
+       $out .= sprintf("%02d:%02d", $mm,$ss);
+       $out .= "  " if ($hh == 0);
+       return $out;
 }
 
+#
+#
+#
 
 =head1 INTERNAL METHODS
 
@@ -202,45 +132,6 @@ sub _eval {
        return $ret || undef;
 }
 
-=head2 _sort_by_order
-
-Sort xml tags data structure accoding to C<order=""> attribute.
-
-=cut
-
-sub _sort_by_order {
-       my $self = shift;
-
-       my $va = $self->{'import_xml'}->{'indexer'}->{$a}->{'order'} ||
-               $self->{'import_xml'}->{'indexer'}->{$a};
-       my $vb = $self->{'import_xml'}->{'indexer'}->{$b}->{'order'} ||
-               $self->{'import_xml'}->{'indexer'}->{$b};
-
-       return $va <=> $vb;
-}
-
-=head2 _x
-
-Convert string from UTF-8 to code page defined in C<import_xml>.
-
- my $text = $webpac->_x('utf8 text');
-
-Default application code page is C<ISO-8859-2>. You will probably want to
-change that when creating new instance of object based on this one.
-
-=cut
-
-sub _x {
-       my $self = shift;
-       my $utf8 = shift || return;
-
-       # create UTF-8 convertor for import_xml files
-       $self->{'utf2cp'} ||= Text::Iconv->new('UTF-8' ,$self->{'code_page'} || 'ISO-8859-2');
-
-       return $self->{'utf2cp'}->convert($utf8) ||
-               $self->_get_logger()->logwarn("can't convert '$utf8'");
-}
-
 =head2 _init_logger
 
 This function will init C<Log::Log4perl> using provided configuration file.
@@ -248,19 +139,54 @@ This function will init C<Log::Log4perl> using provided configuration file.
   $webpac->_init_logger('/path/to/log.conf');
 
 If no path to configuration file is given, dummy empty configuration
-will be create.
+will be created. If any mode which inherits from this one is called
+with C<debug> flag, it will turn logging to debug level.
+
+This function will also read C<log_conf> value from current object and try
+to read that as configuration file if it exists, if it doesn't it will
+fallback to default C<conf/log.conf>.
+
+You can disable all logging by adding C<no_log> to constructor of WebPAC
+object. Object in C<Test::Exception> class will disable logging
+automatically.
 
 =cut
 
 sub _init_logger {
        my $self = shift;
        my $file = shift;
-       if ($file) {
+       $file ||= $self->{'log_conf'};
+       $file = 'conf/log.conf';
+       my $name = (caller(2))[3] || caller;
+
+       my $conf = q( );
+       if ($self->{'no_log'}) {
+               warn "# $name disabled logging\n" if ($log_debug);
+       } elsif ($self->{'debug'}) {
+               $conf = << '_log4perl_';
+
+log4perl.rootLogger=INFO, SCREEN
+
+log4perl.logger.WebPAC.=DEBUG
+
+log4perl.appender.SCREEN=Log::Log4perl::Appender::Screen
+log4perl.appender.SCREEN.layout=PatternLayout
+log4perl.appender.SCREEN.layout.ConversionPattern=%d %p> %F{1}:%L %M - %m%n
+
+_log4perl_
+               warn "# $name is using debug logger\n" if ($log_debug);
+       } elsif ($name =~ m/Test::Exception/o) {
+               warn "# disabled logging for Text::Exception\n" if ($log_debug);
+       } elsif (-e $file) {
+               warn "# $name is using $file logger\n" if ($log_debug);
                Log::Log4perl->init($file);
+               return 1;
        } else {
-               my $conf = q( );
-               Log::Log4perl->init( \$conf );
+               warn "# $name is using null logger\n" if ($log_debug);
        }
+       Log::Log4perl->init( \$conf );
+
+       return 1;
 }
 
 
@@ -276,13 +202,32 @@ method
 sub _get_logger {
        my $self = shift;
 
-       $self->{'_logger_ok'} ||= $self->_init_logger;
+       my $name = (caller(2))[3] || caller;
+       $self->{'_logger_'} ||= $self->_init_logger;
 
-       my $name = (caller(1))[3] || caller;
-       return get_logger($name);
+       my $log = get_logger( $name );
+       warn "# get_logger( $name ) level ", $log->level, "\n" if ($log_debug);
+       return $log;
 }
 
 
+=head2 _log
+
+Quick cludge to make logging object available to scripts which
+use webpac line this:
+
+  my $log = _new WebPAC::Common()->_get_logger();
+
+=cut
+
+sub _new {
+       my $class = shift;
+       my $self = {@_};
+       bless($self, $class);
+
+       $self ? return $self : return undef;
+}
+
 =head1 LOGGING
 
 Logging in WebPAC is performed by L<Log::Log4perl> with config file