/[webpac2]/trunk/lib/WebPAC/Common.pm
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Annotation of /trunk/lib/WebPAC/Common.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 10 - (hide annotations)
Sat Jul 16 20:35:30 2005 UTC (18 years, 9 months ago) by dpavlin
File size: 8135 byte(s)
ISIS input is finished, low_mem option has code (and not only documentation :-)

1 dpavlin 3 package WebPAC::Common;
2    
3     use warnings;
4     use strict;
5    
6     use Log::Log4perl qw(get_logger :levels);
7    
8     =head1 NAME
9    
10     WebPAC::Common - internal methods called from other WebPAC modules
11    
12     =head1 VERSION
13    
14     Version 0.01
15    
16     =cut
17    
18     our $VERSION = '0.01';
19    
20 dpavlin 6 =head1 SYNOPSYS
21 dpavlin 4
22 dpavlin 6 This module defines common functions, and is used as base for other, more
23     specific modules.
24    
25     my $webpac = new WebPAC::Common(
26     filter => {
27     'filter_name_1' => sub {
28     # filter code
29     return length($_);
30     }, ...
31     },
32     }
33    
34 dpavlin 4 =head1 FUNCTIONS
35    
36     =head2 fill_in
37    
38     Workhourse of all: takes record from in-memory structure of database and
39     strings with placeholders and returns string or array of with substituted
40     values from record.
41    
42     my $text = $webpac->fill_in($rec,'v250^a');
43    
44     Optional argument is ordinal number for repeatable fields. By default,
45     it's assume to be first repeatable field (fields are perl array, so first
46     element is 0).
47     Following example will read second value from repeatable field.
48    
49     my $text = $webpac->fill_in($rec,'Title: v250^a',1);
50    
51     This function B<does not> perform parsing of format to inteligenty skip
52     delimiters before fields which aren't used.
53    
54     This method will automatically decode UTF-8 string to local code page
55     if needed.
56    
57     =cut
58    
59     sub fill_in {
60     my $self = shift;
61    
62     my $log = $self->_get_logger();
63    
64     my $rec = shift || $log->logconfess("need data record");
65     my $format = shift || $log->logconfess("need format to parse");
66     # iteration (for repeatable fields)
67     my $i = shift || 0;
68    
69     $log->logdie("infitite loop in format $format") if ($i > ($self->{'max_mfn'} || 9999));
70    
71     # FIXME remove for speedup?
72     $log->logconfess("need HASH as first argument!") if ($rec !~ /HASH/o);
73    
74     if (utf8::is_utf8($format)) {
75     $format = $self->_x($format);
76     }
77    
78     my $found = 0;
79    
80     my $eval_code;
81     # remove eval{...} from beginning
82     $eval_code = $1 if ($format =~ s/^eval{([^}]+)}//s);
83    
84     my $filter_name;
85     # remove filter{...} from beginning
86     $filter_name = $1 if ($format =~ s/^filter{([^}]+)}//s);
87    
88     # do actual replacement of placeholders
89     # repeatable fields
90     $format =~ s/v(\d+)(?:\^(\w))?/$self->get_data(\$rec,$1,$2,$i,\$found)/ges;
91     # non-repeatable fields
92     $format =~ s/s(\d+)(?:\^(\w))?/$self->get_data(\$rec,$1,$2,0,\$found)/ges;
93    
94     if ($found) {
95     $log->debug("format: $format");
96     if ($eval_code) {
97     my $eval = $self->fill_in($rec,$eval_code,$i);
98     return if (! $self->_eval($eval));
99     }
100     if ($filter_name && $self->{'filter'}->{$filter_name}) {
101     $log->debug("filter '$filter_name' for $format");
102     $format = $self->{'filter'}->{$filter_name}->($format);
103     return unless(defined($format));
104     $log->debug("filter result: $format");
105     }
106     # do we have lookups?
107 dpavlin 7 if ($self->{'lookup'}) {
108 dpavlin 4 return $self->lookup($format);
109     } else {
110     return $format;
111     }
112     } else {
113     return;
114     }
115     }
116    
117    
118     =head2 get_data
119    
120     Returns value from record.
121    
122     my $text = $self->get_data(\$rec,$f,$sf,$i,\$found);
123    
124     Arguments are:
125     record reference C<$rec>,
126     field C<$f>,
127     optional subfiled C<$sf>,
128     index for repeatable values C<$i>.
129    
130     Optinal variable C<$found> will be incremeted if there
131     is field.
132    
133     Returns value or empty string.
134    
135     =cut
136    
137     sub get_data {
138     my $self = shift;
139    
140     my ($rec,$f,$sf,$i,$found) = @_;
141    
142     if ($$rec->{$f}) {
143     return '' if (! $$rec->{$f}->[$i]);
144     no strict 'refs';
145     if ($sf && $$rec->{$f}->[$i]->{$sf}) {
146     $$found++ if (defined($$found));
147     return $$rec->{$f}->[$i]->{$sf};
148     } elsif ($$rec->{$f}->[$i]) {
149     $$found++ if (defined($$found));
150     # it still might have subfield, just
151     # not specified, so we'll dump all
152     if ($$rec->{$f}->[$i] =~ /HASH/o) {
153     my $out;
154     foreach my $k (keys %{$$rec->{$f}->[$i]}) {
155     $out .= $$rec->{$f}->[$i]->{$k}." ";
156     }
157     return $out;
158     } else {
159     return $$rec->{$f}->[$i];
160     }
161     }
162     } else {
163     return '';
164     }
165     }
166    
167    
168 dpavlin 9 =head2 progress_bar
169    
170     Draw progress bar on STDERR.
171    
172     $webpac->progress_bar($current, $max);
173    
174     =cut
175    
176     sub progress_bar {
177     my $self = shift;
178    
179     my ($curr,$max) = @_;
180    
181     my $log = $self->_get_logger();
182    
183     $log->logconfess("no current value!") if (! $curr);
184     $log->logconfess("no maximum value!") if (! $max);
185    
186     if ($curr > $max) {
187     $max = $curr;
188     $log->debug("overflow to $curr");
189     }
190    
191     $self->{'last_pcnt'} ||= 1;
192     $self->{'start_t'} ||= time();
193    
194     my $p = int($curr * 100 / $max) || 1;
195    
196     # reset on re-run
197     if ($p < $self->{'last_pcnt'}) {
198     $self->{'last_pcnt'} = $p;
199     $self->{'start_t'} = time();
200     }
201    
202     if ($p != $self->{'last_pcnt'}) {
203    
204     my $t = time();
205     my $rate = ($curr / ($t - $self->{'start_t'} || 1));
206     my $eta = ($max-$curr) / ($rate || 1);
207     printf STDERR ("%5d [%-38s] %-5d %0.1f/s %s\r",$curr,"=" x ($p/3)."$p%>", $max, $rate, $self->fmt_time($eta));
208     $self->{'last_pcnt'} = $p;
209     $self->{'last_curr'} = $curr;
210     }
211     print STDERR "\n" if ($p == 100);
212     }
213    
214     =head2 fmt_time
215    
216     Format time (in seconds) for display.
217    
218     print $webpac->fmt_time(time());
219    
220     This method is called by L<progress_bar> to display remaining time.
221    
222     =cut
223    
224     sub fmt_time {
225     my $self = shift;
226    
227     my $t = shift || 0;
228     my $out = "";
229    
230     my ($ss,$mm,$hh) = gmtime($t);
231     $out .= "${hh}h" if ($hh);
232     $out .= sprintf("%02d:%02d", $mm,$ss);
233     $out .= " " if ($hh == 0);
234     return $out;
235     }
236    
237     #
238     #
239     #
240    
241 dpavlin 3 =head1 INTERNAL METHODS
242    
243     Here is a quick list of internal methods, mostly useful to turn debugging
244     on them (see L<LOGGING> below for explanation).
245    
246     =cut
247    
248     =head2 _eval
249    
250     Internal function to eval code without C<strict 'subs'>.
251    
252     =cut
253    
254     sub _eval {
255     my $self = shift;
256    
257     my $code = shift || return;
258    
259     my $log = $self->_get_logger();
260    
261     no strict 'subs';
262     my $ret = eval $code;
263     if ($@) {
264     $log->error("problem with eval code [$code]: $@");
265     }
266    
267     $log->debug("eval: ",$code," [",$ret,"]");
268    
269     return $ret || undef;
270     }
271    
272     =head2 _sort_by_order
273    
274     Sort xml tags data structure accoding to C<order=""> attribute.
275    
276     =cut
277    
278     sub _sort_by_order {
279     my $self = shift;
280    
281     my $va = $self->{'import_xml'}->{'indexer'}->{$a}->{'order'} ||
282     $self->{'import_xml'}->{'indexer'}->{$a};
283     my $vb = $self->{'import_xml'}->{'indexer'}->{$b}->{'order'} ||
284     $self->{'import_xml'}->{'indexer'}->{$b};
285    
286     return $va <=> $vb;
287     }
288    
289     =head2 _x
290    
291     Convert string from UTF-8 to code page defined in C<import_xml>.
292    
293     my $text = $webpac->_x('utf8 text');
294    
295     Default application code page is C<ISO-8859-2>. You will probably want to
296     change that when creating new instance of object based on this one.
297    
298     =cut
299    
300     sub _x {
301     my $self = shift;
302     my $utf8 = shift || return;
303    
304     # create UTF-8 convertor for import_xml files
305     $self->{'utf2cp'} ||= Text::Iconv->new('UTF-8' ,$self->{'code_page'} || 'ISO-8859-2');
306    
307     return $self->{'utf2cp'}->convert($utf8) ||
308     $self->_get_logger()->logwarn("can't convert '$utf8'");
309     }
310    
311     =head2 _init_logger
312    
313     This function will init C<Log::Log4perl> using provided configuration file.
314    
315     $webpac->_init_logger('/path/to/log.conf');
316    
317 dpavlin 6 If no path to configuration file is given, dummy empty configuration
318 dpavlin 10 will be created. If any mode which inherits from this one is called
319     with C<debug> flag, it will turn logging to debug level.
320 dpavlin 6
321 dpavlin 3 =cut
322    
323     sub _init_logger {
324     my $self = shift;
325     my $file = shift;
326     if ($file) {
327     Log::Log4perl->init($file);
328     } else {
329     my $conf = q( );
330 dpavlin 10 if ($self->{'debug'}) {
331     $conf = << '_log4perl_';
332    
333     log4perl.rootLogger=INFO, SCREEN
334    
335     log4perl.logger.WebPAC.=DEBUG
336    
337     log4perl.appender.SCREEN=Log::Log4perl::Appender::Screen
338     log4perl.appender.SCREEN.layout=PatternLayout
339     log4perl.appender.SCREEN.layout.ConversionPattern=%d %p> %F{1}:%L %M - %m%n
340    
341     _log4perl_
342     }
343 dpavlin 3 Log::Log4perl->init( \$conf );
344     }
345     }
346    
347    
348     =head2 _get_logger
349    
350     Get C<Log::Log4perl> object with a twist: domains are defined for each
351     method
352    
353     my $log = $webpac->_get_logger();
354    
355     =cut
356    
357     sub _get_logger {
358     my $self = shift;
359    
360     $self->{'_logger_ok'} ||= $self->_init_logger;
361    
362     my $name = (caller(1))[3] || caller;
363     return get_logger($name);
364     }
365    
366    
367     =head1 LOGGING
368    
369     Logging in WebPAC is performed by L<Log::Log4perl> with config file
370     C<log.conf>.
371    
372     Methods defined above have different levels of logging, so
373     it's descriptions will be useful to turn (mostry B<debug> logging) on
374     or off to see why WabPAC isn't perforing as you expect it (it might even
375     be a bug!).
376    
377     B<This is different from normal Log4perl behaviour>. To repeat, you can
378     also use method names, and not only classes (which are just few)
379     to filter logging.
380    
381    

  ViewVC Help
Powered by ViewVC 1.1.26