File Coverage

blib/lib/Text/Printf.pm
Criterion Covered Total %
statement 188 215 87.4
branch 86 100 86.0
condition 16 22 72.7
subroutine 24 27 88.8
pod 8 11 72.7
total 322 375 85.8


line stmt bran cond sub pod time code
1             =for gpg
2             -----BEGIN PGP SIGNED MESSAGE-----
3             Hash: SHA1
4              
5             =head1 NAME
6              
7             Text::Printf - A simple, lightweight text fill-in class.
8              
9             =head1 VERSION
10              
11             This documentation describes version 1.03 of Text::Printf, June 9, 2008.
12              
13             =cut
14              
15             package Text::Printf;
16              
17 9     9   243654 use strict;
  9         22  
  9         372  
18 9     9   46 use warnings;
  9         19  
  9         243  
19 9     9   8256 use Readonly;
  9         29547  
  9         672  
20              
21             $Text::Printf::VERSION = '1.03';
22 9     9   121 use vars '$DONTSET';
  9         19  
  9         566  
23             Readonly::Scalar $DONTSET => []; # Unique identifier
24              
25             # Always export the $DONTSET variable
26             # Always export the *printf subroutines
27             sub import
28             {
29 9     9   92 my ($pkg) = caller;
30 9     9   47 no strict 'refs';
  9         18  
  9         1349  
31 9         20 *{$pkg.'::DONTSET'} = \$DONTSET;
  9         63  
32 9         23 *{$pkg.'::tprintf'} = \&tprintf;
  9         45  
33 9         19 *{$pkg.'::tsprintf'} = \&tsprintf;
  9         20320  
34             }
35              
36             # Declare exception classes
37             use Exception::Class
38             (
39 9         151 'Text::Printf::X' =>
40             { description => 'Generic Text::Printf exception',
41             },
42             'Text::Printf::X::ParameterError' =>
43             { isa => 'Text::Printf::X',
44             description => 'Error in parameters to Text::Printf method',
45             },
46             'Text::Printf::X::OptionError' =>
47             { isa => 'Text::Printf::X',
48             fields => 'name',
49             description => 'A bad option was passed to a Text::Printf method',
50             },
51             'Text::Printf::X::KeyNotFound' =>
52             { isa => 'Text::Printf::X',
53             fields => 'symbols',
54             description => 'Could not resolve one or more symbols in template text',
55             },
56             'Text::Printf::X::NoText' =>
57             { isa => 'Text::Printf::X',
58             description => 'No text to expand',
59             },
60             'Text::Printf::X::InternalError' =>
61             { isa => 'Text::Printf::X',
62             fields => 'additional_info',
63             description => 'Internal Text::Printf error. Please contact the author.'
64             },
65 9     9   8567 );
  9         111849  
66              
67             # Early versions of Exception::Class didn't define this useful subroutine
68             if (!defined &Exception::Class::Base::caught)
69             {
70             # Class method to help caller catch exceptions
71 9     9   32635 no warnings qw(once redefine);
  9         18  
  9         26692  
72             *Exception::Class::Base::caught = sub
73             {
74             my $class = shift;
75             return Exception::Class->caught($class);
76             }
77             }
78              
79             # Croak-like location of error
80             sub Text::Printf::X::location
81             {
82 52     52   70 my ($pkg,$file,$line);
83 52         66 my $caller_level = 0;
84 52         67 while (1)
85             {
86 156         963 ($pkg,$file,$line) = caller($caller_level++);
87 156 100 66     772 last if $pkg !~ /\A Text::Printf/x && $pkg !~ /\A Exception::Class/x
88             }
89 52         239 return "at $file line $line";
90             }
91              
92             # Die-like location of error
93             sub Text::Printf::X::InternalError::location
94             {
95 0     0   0 my $self = shift;
96 0         0 return "at " . $self->file() . " line " . $self->line()
97             }
98              
99             # Override full_message, to report location of error in caller's code.
100             sub Text::Printf::X::full_message
101             {
102 52     52   62494 my $self = shift;
103              
104 52         209 my $msg = $self->message;
105 52 50       325 return $msg if substr($msg,-1,1) eq "\n";
106              
107 52         221 $msg =~ s/[ \t]+\z//; # remove any trailing spaces (is this necessary?)
108 52         198 return $msg . q{ } . $self->location() . qq{\n};
109             }
110              
111             # Comma formatting. From the Perl Cookbook.
112             sub commify ($)
113             {
114 7     7 0 16 my $rev_num = reverse shift; # The number to be formatted, reversed.
115 7         50 $rev_num =~ s/(\d\d\d)(?=\d)(?!\d*\.)/$1,/g;
116 7         19 return scalar reverse $rev_num;
117             }
118              
119              
120             ## Constructor
121             # $object = Text::Printf->new($boilerplate, $options);
122             sub new
123             {
124 42     42 1 14773 my $class = shift;
125 42         64 my $self = \do { my $anonymous_scalar };
  42         83  
126 42         98 bless $self, $class;
127 42         121 $self->_initialize(@_);
128 32         99 return $self;
129             }
130              
131              
132             { # encapsulation enclosure
133              
134             # Attributes
135             my %boilerplate_for;
136             my %delimiters_for;
137             my %regex_for;
138             my %value_hashes_for;
139             my %defaults_for;
140             my %bad_keys_of;
141              
142             ## Initializer
143             # $obj->_initialize($boilerplate, $options);
144             sub _initialize
145             {
146 42     42   60 my $self = shift;
147              
148             # Check whether any attribute has a value from another, earlier object.
149             # This should never happen, if DESTROY is working, and nobody calls
150             # _initialize on an already-initialized object.
151             {
152 42         52 my @occupied;
  42         51  
153 42 50       182 push @occupied, '%boilerplate_for' if exists $boilerplate_for {$self};
154 42 50       125 push @occupied, '%delimiters_for' if exists $delimiters_for {$self};
155 42 50       118 push @occupied, '%regex_for' if exists $regex_for {$self};
156 42 50       110 push @occupied, '%value_hashes_for' if exists $value_hashes_for{$self};
157 42 50       102 push @occupied, '%defaults_for' if exists $defaults_for {$self};
158 42 50       98 push @occupied, '%bad_keys_of' if exists $bad_keys_of {$self};
159              
160 42 50       129 Text::Printf::X::InternalError->throw(
161             message => 'Internal programing error: contact author.',
162             additional_info => join(', ', @occupied))
163             if @occupied;
164             }
165              
166             # Check number and type of parameters
167             # Legal possibilities: (), ($scalar), ($hashref), ($scalar, $hashref);
168 42         121 my $whoami = ref($self) . " constructor";
169              
170 42         70 my $boilerplate;
171             my $options_ref;
172              
173 42 100       119 if (@_ == 1)
    100          
    100          
174             {
175 26         41 my $arg = shift;
176 26 50       72 Text::Printf::X::ParameterError->throw('Text may not be set to an undefined value')
177             if !defined $arg;
178              
179 26         41 my $ref = ref $arg;
180 26 100       62 if ($ref eq '')
    50          
181             {
182 25         41 $boilerplate = $arg;
183             }
184             elsif ($ref eq 'HASH')
185             {
186 1         2 $options_ref = $arg;
187             }
188             else
189             {
190 0         0 $ref = _decode_ref($arg);
191 0         0 Text::Printf::X::ParameterError->throw(
192             "Solo argument to $whoami should be scalar or hashref, not $ref");
193             }
194             }
195             elsif (@_ == 2)
196             {
197 12         16 my $arg = shift;
198 12 50       25 Text::Printf::X::ParameterError->throw('Text may not be set to an undefined value')
199             if !defined $arg;
200              
201 12         14 my $ref = ref $arg;
202 12 100       26 if ($ref eq '')
203             {
204 11         14 $boilerplate = $arg;
205             }
206             else
207             {
208 1         5 $ref = _decode_ref($arg);
209 1         8 Text::Printf::X::ParameterError->throw(
210             "First argument to $whoami should be a scalar, not $ref");
211             }
212              
213 11         31 $arg = shift;
214 11         19 $ref = ref($arg);
215 11 100       26 if ($ref ne 'HASH')
216             {
217 1         5 $ref = _decode_ref($arg);
218 1         22 Text::Printf::X::ParameterError->throw(
219             "Second argument to $whoami must be hash ref, not $ref");
220             }
221 10         15 $options_ref = $arg;
222             }
223             elsif (@_ > 2)
224             {
225 1         8 Text::Printf::X::ParameterError->throw("Too many parameters to $whoami");
226             }
227              
228              
229 39         222 $boilerplate_for{$self} = $boilerplate;
230 39 100       106 if (exists $options_ref->{delimiters})
231             {
232 11         17 my $delim = $options_ref->{delimiters};
233              
234 11 100       45 Text::Printf::X::OptionError->throw(
235             message => "Bad option to $whoami\n"
236             . "delimiter value must be array reference",
237             name => 'delimiter')
238             unless ref($delim) eq 'ARRAY';
239              
240 10 100       26 Text::Printf::X::OptionError->throw(
241             message => "Bad option to $whoami\n"
242             . "delimiter arrayref must have exactly two values",
243             name => 'delimiter')
244             unless @$delim == 2;
245              
246 9         26 my ($ref0, $ref1) = (ref ($delim->[0]), ref ($delim->[1]));
247 9 100 100     92 Text::Printf::X::OptionError->throw(
      100        
      66        
248             message => "Bad option to $whoami\n"
249             . "delimiter values must be strings or regexes",
250             name => 'delimiter')
251             unless ($ref0 eq q{} || $ref0 eq 'Regexp')
252             && ($ref1 eq q{} || $ref1 eq 'Regexp');
253              
254 4 100       24 $delimiters_for{$self} = [ $ref0? $delim->[0] : quotemeta($delim->[0]),
    100          
255             $ref0? $delim->[1] : quotemeta($delim->[1]) ];
256             }
257             else
258             {
259 28         95 $delimiters_for{$self} = [ quotemeta('{{'), quotemeta('}}') ];
260             }
261              
262             # $1 is the keyword plus its delimiters; $2 is the keyword by itself.
263             # $3 is the printf format, if any; $4 is the extended format.
264 32         1031 $regex_for{$self} =
265             qr/( # $1: capture whole expression
266             $delimiters_for{$self}[0] # Opening delimiter
267             (\w+) # $2: keyword
268             (?: : # Maybe a colon and...
269             %? ( (?: \+ (?=[^+]{2}) )? [-<>]? \+?
270             [\d.]* [A-Za-z]{1,2} ) # $3: ...a printf format
271             (?: : # and maybe another colon
272             ([,\$]+) )? # $4: and extended format chars
273             )?
274             $delimiters_for{$self}[1] # Closing delimiter
275             )/xsm;
276              
277 32         101 return;
278             }
279              
280             sub DESTROY
281             {
282 42     42   10721 my $self = shift;
283              
284             # Free up the hash entries we're using.
285 42         102 delete $boilerplate_for {$self};
286 42         101 delete $delimiters_for {$self};
287 42         104 delete $regex_for {$self};
288 42         70 delete $value_hashes_for{$self};
289 42         66 delete $defaults_for {$self};
290 42         1107 delete $bad_keys_of {$self};
291             }
292              
293             # Stack up hash values for later substitution
294             sub pre_fill
295             {
296 5     5 1 4020 my $self = shift;
297              
298             # Validate the parameters
299 5         14 foreach my $arg (@_)
300             {
301 7 100       42 Text::Printf::X::ParameterError->throw("Argument to pre_fill() is not a hashref")
302             if ref $arg ne 'HASH';
303             }
304 4         7 push @{ $value_hashes_for{$self} }, @_;
  4         16  
305 4         10 return;
306             }
307              
308             # Stack up hash values for later substitution
309             sub default
310             {
311 5     5 1 3603 my $self = shift;
312              
313             # Validate the parameters
314 5         15 foreach my $arg (@_)
315             {
316 9 100       46 Text::Printf::X::ParameterError->throw("Argument to default() is not a hashref")
317             if ref $arg ne 'HASH';
318             }
319 4         10 push @{ $defaults_for{$self} }, @_;
  4         18  
320 4         13 return;
321             }
322              
323             # Clear any pre-stored hashes
324             sub clear_values
325             {
326 1     1 1 4 my $self = shift;
327 1         3 $value_hashes_for{$self} = [];
328 1         3 $defaults_for {$self} = [];
329 1         3 return;
330             }
331              
332             # Set or change the boilerplate (template) text
333             sub text
334             {
335 10     10 1 3442 my $self = shift;
336              
337             # No arguments? Return the text.
338 10 100       32 return $boilerplate_for{$self}
339             unless @_;
340              
341 5         4 my $text = shift;
342 5 100       27 Text::Printf::X::ParameterError->throw('Too many parameters to text()')
343             if @_;
344 4 100       10 Text::Printf::X::ParameterError->throw('Text may not be set to an undefined value')
345             if !defined $text;
346              
347 3         52 $boilerplate_for{$self} = $text;
348 3         9 return;
349             }
350              
351             # Do the replacements.
352             sub fill
353             {
354 41     41 1 13322 my $self = shift;
355 41         80 my @fill_hashes = @_;
356              
357             # Validate the parameters
358 41         76 foreach my $arg (@fill_hashes)
359             {
360 55 100       203 Text::Printf::X::ParameterError->throw('Argument to fill() is not a hashref')
361             if ref $arg ne 'HASH';
362             }
363              
364 39         60 my @hashes;
365 39 100       111 push @hashes, @{ $value_hashes_for{$self}} if exists $value_hashes_for{$self};
  4         12  
366 39         64 push @hashes, @fill_hashes;
367 39 100       156 push @hashes, @{ $defaults_for {$self}} if exists $defaults_for {$self};
  6         15  
368              
369             # Fetch other attributes
370 39         88 my $str = $boilerplate_for{$self};
371 39 100       99 defined $str or Text::Printf::X::NoText->throw('Template text was never set');
372 38         68 my $rex = $regex_for{$self};
373              
374             # Do the subsitution
375 38         86 $bad_keys_of{$self} = [];
376 38         327 $str =~ s/$rex/$self->_substitution_of(\@hashes, $1, $2, $3, $4)/ge;
  121         307  
377              
378             # Any unfulfilled substitutions?
379 38         104 my $bk = $bad_keys_of{$self}; # shortcut for the next few lines
380 38 100       102 if (@$bk > 0)
381             {
382 5 100       19 my $s = @$bk == 1? q{} : 's';
383 5         13 my $bad_str = join ', ', @$bk;
384 5         14 $bad_keys_of{$self} = []; # reset in case exception is caught.
385 5         76 Text::Printf::X::KeyNotFound->throw(
386             message => "Could not resolve the following symbol$s: $bad_str",
387             symbols => $bk);
388             }
389              
390 33         150 return $str;
391             }
392              
393             # Helper function for regular expression in fill(), above.
394             sub _substitution_of
395             {
396 121     121   219 my $self = shift;
397 121         341 my ($values_aref, $whole_expr, $keyword, $format, $extend) = @_;
398              
399 121         180 Value_Hash: foreach my $hashref (@$values_aref)
400             {
401 173 100       390 next unless exists $hashref->{$keyword};
402              
403 114         177 my $value = $hashref->{$keyword};
404              
405             # Special DONTSET value: leave the whole expression intact
406 114 100 100     309 return $whole_expr
407             if ref($value) eq 'ARRAY' && $value eq $DONTSET;
408              
409 111 100       206 $value = q{} if !defined $value;
410 111 100       522 return $value if !defined $format;
411              
412 25         40 $format =~ tr/<>/-/d;
413 25         155 $value = sprintf "%$format", $value;
414              
415             # Special extended formatting
416 25 100       53 if (defined $extend)
417             {
418             # Currently, ',' and '$' are defined
419 8         10 my $v_len = length $value;
420 8 100       58 $value = commify $value if index ($extend, ',') >= 0;
421 8 100       46 $value =~ s/([^ ])/\$$1/ if index ($extend, '$') >= 0;
422 8         13 my $length_diff = length($value) - $v_len;
423 8         91 $value =~ s/^ {0,$length_diff}//;
424 8         16 $length_diff = length($value) - $v_len;
425 8         111 $value =~ s/ {0,$length_diff}$//;
426             }
427              
428 25         146 return $value;
429             }
430              
431             # Never found a match? Pity.
432             # Store the bad keyword, and leave it intact in the string.
433 7         12 push @{ $bad_keys_of{$self} }, $keyword;
  7         23  
434 7         32 return $whole_expr;
435             }
436              
437             # Debugging routine -- dumps a string representation of the object
438             sub _dump
439             {
440 0     0   0 my $self = shift;
441 0         0 my $out = q{};
442              
443 0         0 $out .= qq{Boilerplate: "$boilerplate_for{$self}"\n};
444 0         0 $out .= qq{Delimiters: [ "$delimiters_for{$self}[0]", "$delimiters_for{$self}[1]" ]\n};
445 0         0 $out .= qq{Regex: $regex_for{$self}\n};
446 0         0 $out .= qq{Value hashes: [\n};
447 0         0 my $i = 0;
448 0   0     0 my $vals = $value_hashes_for{$self} || [];
449 0         0 for my $h (@$vals)
450             {
451 0         0 $out .= " $i {\n";
452 0         0 foreach my $k (sort keys %$h)
453             {
454 0         0 $out .= " qq{$k} => qq{$h->{$k}}\n";
455             }
456 0         0 $out .= " },\n";
457 0         0 ++$i;
458             }
459 0         0 $out .= "]\n";
460              
461 0   0     0 my $bad_keys = $bad_keys_of{$self} || [];
462 0         0 $out .= qq{Bad keys: [} . join(", ", @$bad_keys) . "]\n";;
463 0         0 return $out;
464             }
465              
466             } # end encapsulation enclosure
467              
468              
469              
470             # printf-like convenience functions
471              
472             sub tprintf
473             {
474             # First arg a filehandle?
475 4     4 1 1358 my $fh;
476 4 100 100     38 if (ref $_[0] eq 'GLOB' || UNIVERSAL::can($_[0], 'print'))
477             {
478 3         5 $fh = shift;
479 3 100       13 Text::Printf::X::ParameterError->throw
480             ("tprintf() requires at least one non-handle argument")
481             if @_ < 1;
482             }
483              
484 3         10 my $string = t_printf_guts('tprintf', @_);
485              
486 2 50       12 if ($fh)
487             {
488 2 100       17 if (UNIVERSAL::can($fh, 'print'))
489             {
490 1         5 $fh->print($string);
491             }
492             else
493             {
494 1         2 print {$fh} $string;
  1         16  
495             }
496             }
497             else
498             {
499 0         0 print $string;
500             }
501             }
502              
503             sub tsprintf
504             {
505 10     10 1 9630 return t_printf_guts('tsprintf', @_);
506             }
507              
508             sub tfprintf
509             {
510 0 0   0 0 0 Text::Printf::X::ParameterError->throw
511             ("tfprintf() requires at least two arguments")
512             if @_ < 2;
513              
514 0         0 my $fh = shift;
515 0         0 print {$fh} t_printf_guts('tfprintf', @_);
  0         0  
516             }
517              
518             sub t_printf_guts
519             {
520 13     13 0 18 my $which = shift;
521 13 100       55 Text::Printf::X::ParameterError->throw
522             ("$which() requires at least one argument")
523             if @_ == 0;
524              
525 11         15 my $format = shift;
526 11         21 my @value_hashes = @_;
527              
528             # Validate the parameters
529 11         18 foreach my $arg (@value_hashes)
530             {
531 16 100       61 Text::Printf::X::ParameterError->throw
532             ("Argument to $which() is not a hashref")
533             if ref $arg ne 'HASH';
534             }
535              
536 9         48 my $template = Text::Printf->new ($format);
537 9         25 return $template->fill(@value_hashes);
538             }
539              
540             sub _decode_ref
541             {
542 2     2   5 my $ref = ref $_[0];
543 2 100       9 return $ref eq '' ? 'scalar'
544             : $ref . ' ref';
545             }
546              
547             1;
548             __END__