File Coverage

blib/lib/Text/MessageLibrary.pm
Criterion Covered Total %
statement 56 58 96.5
branch 15 24 62.5
condition 2 6 33.3
subroutine 12 12 100.0
pod 5 5 100.0
total 90 105 85.7


line stmt bran cond sub pod time code
1             package Text::MessageLibrary;
2             $VERSION = "0.15";
3            
4             =head1 NAME
5            
6             Text::MessageLibrary - centrally manage lists of static and dynamic status,
7             error, or other messages, encapsulated in an object
8            
9             =head1 SYNOPSIS
10            
11             # create a list of messages
12             $error_messages = Text::MessageLibrary->new({
13             bad_file_format => 'File format not recognized!',
14             file_open_failed => sub{"Unable to open file $_[0]: $!"},
15             _default => sub{"Unknown message " . shift() .
16             " with params " . (join ",",@_)},
17             });
18            
19             # generate messages
20             print $error_messages->bad_file_format;
21             print $error_messages->file_open_failed('myfile');
22             print $error_messages->no_such_message; # falls back to _default
23            
24             # override default prefixes and suffixes
25             $error_messages->set_prefix("myprogram: ");
26             $error_messages->set_suffix("\n");
27            
28             =head1 DESCRIPTION
29            
30             =head2 Overview
31            
32             With the Text::MessageLibrary class, you can create objects that dynamically
33             construct status, error, or other messages on behalf of your programs.
34             Text::MessageLibrary is intended to be useful in larger projects, where it
35             can be used to create centralized collections of messages that are easier to
36             maintain and document than string literals scattered throughout the code.
37            
38             To create a Text::MessageLibrary object, you'll need to create a hash containing
39             a set of keywords and a message associated with each keyword, then pass that
40             hash to the C constructor. The keywords you choose are then exposed as
41             methods of an individual Text::MessageLibrary object, so you can generate messages
42             with this syntax:
43            
44             $messages->message_keyword(...with params too, if you want...)
45            
46             The messages themselves may be either literal strings or anonymous subroutines
47             that can perform arbitrarily complex operations. For instance, if you create
48             an C<$error_messages> object like this:
49            
50             $error_messages = Text::MessageLibrary->new({
51             file_open_failed => sub{"Unable to open file $_[0]: $!\n"}
52             });
53            
54             You can then write this:
55            
56             open INPUT, "/no/such/file"
57             or die $error_messages->file_open_failed('myfile');
58            
59             And get this result:
60            
61             Unable to open file myfile: No such file or directory
62            
63             Notice that parameters to the method call are accessible to your subroutine
64             via C<@_>, and that the global C<$!> variable containing the error message
65             from the last file operation is available too.
66            
67             When you're using static error messages -- i.e., where interpolation at the
68             moment of message generation is not required -- you can skip the anonymous
69             subroutine and simply provide a string literal:
70            
71             $status_messages = Text::MessageLibrary->new(
72             new_record => 'loading new record',
73             all_done => 'processing complete',
74             );
75             ...
76             print $status_messages->new_record;
77             ...
78             print $status_messages->all_done;
79            
80             =head2 Prefixes and Suffixes
81            
82             Whether you're using static or dynamic messages, there's actually one more
83             thing that Text::MessageLibrary objects do when constructing messages: They
84             add a prefix and a suffix. By default, the prefix contains the name of the
85             current executable (stripped of path information if you're running on a
86             Windows or Unix variant), and the suffix is simply a newline. So in practice
87             you'll normally get messages that look more like this:
88            
89             YourProgramName: Unable to open file myfile: No such file or directory\n
90            
91             You can change this behavior by calling the C and C
92             methods:
93            
94             $error_messages->set_prefix("Error: ");
95             $error_messages->set_suffix(".");
96            
97             which would result instead in:
98            
99             Error: Unable to open file myfile: No such file or directory.
100            
101             The prefix and suffix that you set apply to all messages emitted by an
102             individual Text::MessageLibrary object. Note that the prefix and suffix are
103             expected to be fixed strings, not subroutines.
104            
105             (Incidentally, you can retrieve the current prefix and suffix by using the
106             C and C methods, but I can't think of a particularly
107             compelling reason to actually do that.)
108            
109             =head2 Defining Fallback Messages
110            
111             What happens if you try to call a method for which no message was defined?
112             Text::MessageLibrary provides default behavior, so that:
113            
114             print $status_messages->no_such_message('nice try', 'dude');
115            
116             results in:
117            
118             YourProgramName: message no_such_message(nice try,dude)\n
119            
120             You can override this behavior by specifying a C<_default> key (and
121             associated message) in your constructor:
122            
123             $error_messages = Text::MessageLibrary->new({
124             bad_file_format => 'File format not recognized!',
125             _default => sub{"Unknown message '$_[0]' received"},
126             });
127            
128             With this C<_default> definition, the output would instead be:
129            
130             YourProgramName: Unknown message 'no_such_message' received\n
131            
132             =head2 Practical Uses
133            
134             If you have a fairly large, multi-module program, you may want to centralize
135             many of your messages in a single module somewhere. For example:
136            
137             package MyMessages;
138             @ISA = qw(Exporter);
139             @EXPORT = qw($error_messages $status_messages);
140             use vars qw($error_messages $status_messages);
141             use Text::MessageLibrary;
142             use strict;
143            
144             {
145             my $verbose = 1;
146            
147             $error_messages = Text::MessageLibrary->new(
148             file_open => sub {return qq{file open failed on $_[0]: $!}},
149             _default => sub {return "unknown error $_[0] reported"},
150             );
151            
152             $status_messages = Text::MessageLibrary->new(
153             starting_parser => ($verbose ? "Starting parser\n" : ""),
154             starting_generator => ($verbose ? sub {"Starting generator $_[0]\n"} : ""),
155             );
156             $status_messages->set_prefix();
157             $status_messages->set_suffix();
158            
159             1;
160             }
161            
162             Then your other modules can simply C and do things like:
163            
164             print $status_messages->starting_parser;
165             print $status_messages->starting_generator('alpha');
166             print $status_messages->starting_generator('omega');
167             print $error_messages->unexpected_end_of_file;
168            
169             Since all your messages are located in one module, it's a simple task to
170             change their wording, control their level of verbosity with a single
171             statement, and so on. You could also easily change the language of your
172             messages, though this package is not really intended as a substitute for
173             a dedicated module such as C.
174            
175             Note that the methods generated are unique to each Text::MessageLibrary object,
176             so that given the definitions above, this statement:
177            
178             print $status_messages->file_open('my_file');
179            
180             would end up calling the C<_default> message generator for the
181             C<$status_messages> object. (C was defined only in the constructor
182             for C<$error_messages>, so no C method exists for
183             C<$status_messages>.) In effect, the method-call syntax is merely syntactic
184             sugar for a hypothetical method call like this:
185            
186             # there's not really a 'generate_message' method...
187             print $status_messages->generate_message('file_open','my_file');
188            
189             On a separate note, if you wish to subclass Text::MessageLibrary, you can override
190             the default (empty) C<_init> function that the constructor calls and perform
191             further initialization tasks there.
192            
193             =head2 Performance Considerations
194            
195             Not surprisingly, encapsulating your message generation within an object --
196             and, sometimes, an anonymous subroutine -- exacts a performance penalty. I've
197             found in small-scale experiments that the method call and anonymous-subroutine
198             execution is roughly an order of magnitude slower than using literal strings
199             and Perl's native interpolation. But it's still I fast in most cases,
200             and the reduced speed may be an acceptable tradeoff for improved
201             maintainability, particularly when it comes to things like error messages that
202             are (we hope!) generated only infrequently.
203            
204             =head2 Potential Enhancements
205            
206             There's currently no way to modify or add messages once you've constructed the
207             object, nor a clone/copy method, but I haven't yet found a reason to
208             implement either capability. And Simple Is Beautiful.
209            
210             =cut
211            
212            
213             ############################## CODE STARTS HERE ##############################
214            
215 1     1   7486 use vars qw($AUTOLOAD);
  1         2  
  1         41  
216 1     1   6 use strict;
  1         1  
  1         32  
217 1     1   4 use warnings;
  1         5  
  1         32  
218 1     1   4 use Carp;
  1         1  
  1         770  
219            
220            
221             =head1 PUBLIC METHODS
222            
223             =over 4
224            
225             =item Text::MessageLibrary->new(\%keyword_message_hash);
226            
227             Construct a new Text::MessageLibrary object. The C<(key,value)> pairs in
228             C<%keyword_message_hash> are used to define the methods that the object will
229             expose and the messages that will be generated when those methods are called.
230             The keys should be names that would pass muster as Perl subroutine names,
231             because you'll likely be calling them using the OO arrow syntax:
232            
233             $message_library->method_name;
234            
235             The values (messages) may be either literal strings or blocks of code to be
236             interpreted each time the method is invoked. Parameters passed to the method
237             are accessible to the code block in C<@_> as if it were a normal subroutine.
238             For example:
239            
240             $status_message = Text::MessageLibrary->new(
241             {general => sub{"You said: $_[0], $_[1], $_[2]."}};
242             );
243             print $status_message->general('zero', 'one', 'two');
244            
245             results in:
246            
247             You said: zero, one, two.
248            
249             The key C<_default> has a special significance: It defines a message that is
250             used if an unknown method is called. In this case, C<$_[0]> contains the name
251             of the unknown method, and the rest of C<@_> contains the parameters. The
252             object will provide baseline (one might say 'default') C<_default> behavior
253             if no such key is explicitly provided in the constructor.
254            
255             =cut
256            
257             sub new {
258 2     2 1 98 my ($class, @args) = @_;
259 2         3 my $self = {};
260 2         5 bless $self, $class;
261 2         11 $self->_init(@args); # do the real work
262 2         4 return $self;
263             }
264            
265            
266             =item $messages->set_prefix($new_prefix)
267            
268             Set the prefix that is prepended onto any message returned by this object. By
269             default, the prefix contains the name of the current executable (with path
270             stripped out if you're running under Windows and *nix OSs).
271            
272             Omitting C<$new_prefix> is equivalent to specifying a null string.
273            
274             =cut
275            
276             sub set_prefix {
277 3 50   3 1 10 croak "set_prefix expects a single optional param" unless @_ <= 2;
278 3         4 my ($self, $prefix) = @_;
279 3 100       8 $prefix = '' unless defined($prefix);
280 3         6 $self->{prefix} = $prefix;
281 3         4 return 1;
282             }
283            
284            
285             =item $messages->set_suffix($new_suffix)
286            
287             Set the suffix that is appended onto any message returned by this object. By
288             default, the suffix is a single newline.
289            
290             Omitting C<$new_suffix> is equivalent to specifying a null string.
291            
292             =cut
293            
294             sub set_suffix {
295 3 50   3 1 9 croak "set_suffix expects a single optional param" unless @_ <= 2;
296 3         4 my ($self, $suffix) = @_;
297 3 50       6 $suffix = '' unless defined($suffix);
298 3         4 $self->{suffix} = $suffix;
299 3         8 return 1;
300             }
301            
302            
303             =item $messages->get_prefix
304            
305             Return the currently defined prefix.
306            
307             =cut
308            
309             sub get_prefix {
310 6 50   6 1 11 croak "get_prefix expects no params" unless @_ == 1;
311 6         13 return $_[0]->{prefix};
312             }
313            
314            
315             =item $messages->get_suffix
316            
317             Return the currently defined suffix.
318            
319             =cut
320            
321             sub get_suffix {
322 6 50   6 1 12 croak "get_suffix expects no params" unless @_ == 1;
323 6         10 return $_[0]->{suffix};
324             }
325            
326             =back
327            
328             =cut
329            
330             ##### PRIVATE METHODS (AND VARIABLES)
331            
332             ##### AUTOLOAD
333             # The AUTOLOAD method is called whenever a Text::MessageLibrary object receives a
334             # method call to generate a message. It does not cache methods in the symbol
335             # table for future access, because methods are unique to I
336             # Text::MessageLibrary objects. (Remember that we're using method calls merely as
337             # syntactic sugar to make the calling code more readable.)
338            
339             sub AUTOLOAD {
340            
341             # figure out how we were called
342            
343 6     6   165 my $self = shift;
344 6         21 $AUTOLOAD =~ /.*::(\w+)/;
345 6         10 my $message_name = $1;
346 6 50       10 return if $message_name eq 'DESTROY';
347            
348             # look up the message for this method, or use the default message
349            
350 6         14 my $message_generator = $self->{messages}->{$message_name};
351 6 100       11 if (!defined($message_generator)) {
352 2         3 $message_generator = $self->{messages}->{_default};
353 2         9 @_ = ($message_name, @_);
354             }
355            
356 6         14 my $prefix = $self->get_prefix();
357 6         11 my $suffix = $self->get_suffix();
358            
359             # construct a dynamic message if needed, or simply return a static one
360            
361 6 100       14 if (ref $message_generator eq 'CODE') {
362 4         7 return $prefix . (&$message_generator) . $suffix;
363             } else {
364 2         19 return $prefix . $message_generator . $suffix;
365             }
366             }
367            
368            
369             ##### $messages->_init(@_)
370             # does the actual initialization.
371            
372             sub _init {
373 2     2   3 my ($self, @params) = @_;
374            
375             # dereference the input hash, or provide an empty hash if none was sent
376             # and set default values
377            
378 2 50       7 my %message_hash = defined $_[1] ? %{$_[1]} : ();
  2         10  
379             my %messages = (
380             _default => sub {
381 1     1   9 return "message " . $_[0] . "(" . (join ",", @_[1..$#_]) . ")"
382             },
383 2         15 %message_hash
384             );
385 2         12 $self->{messages} = \%messages;
386            
387             # rule of thumb to figure out name of executable (sans path): eliminate
388             # everything after the last slash (*nix) or backslash (Win)
389            
390 2         5 my $prefix = $0;
391 2 50 33     30 if ($^O eq 'MSWin32') {
    50 33        
392 0         0 $0 =~ m{(\\|\A)([^\\]*)$};
393 0         0 $prefix = $2;
394             } elsif ($^O ne 'Mac' && $^O ne 'VMS' && $^O ne 'OS2') {
395 2         11 $0 =~ m{(/|\A)([^/]*)$};
396 2         6 $prefix = $2;
397             }
398            
399 2         8 $self->set_prefix("$prefix: ");
400 2         5 $self->set_suffix("\n");
401             }
402            
403             ##### Internal Data Structure
404            
405             # A Text::MessageLibrary is a blessed hash containing the following keys:
406            
407             # A reference to the hash containing message keywords and message text/code
408             # that was passed into the constructor.
409             # prefix
410             # The current prefix, set with C.
411             # suffix
412             # The current suffix, set with C.
413            
414             1;
415            
416             =head1 REVISION HISTORY
417            
418             0.15 (2002-10-30)
419             Minor documentation tweaks.
420            
421             0.14 (2002-10-29)
422             Packaged for distribution on CPAN.
423            
424             0.13 (2002-10-16)
425             Minor (mostly cosmetic) updates to documentation, code, and test suite.
426             Converted to Artistic License.
427            
428             0.12 (2002-01-06)
429             First public beta. Changed constructor to expect hash to be passed by
430             reference. Split C<_init> out from C.
431            
432             0.11 (2002-01-05)
433             Removed method caching (which caused conflicts when instantiating
434             multiple objects), rationalized code, completed POD.
435            
436             0.10 (2001-12-17)
437             Initial implementation (not released).
438            
439             =head1 AUTHOR
440            
441             John Clyman (module-support@clyman.com)
442            
443             =head1 COPYRIGHT AND LICENSE
444            
445             Copyright (C) 2002 John Clyman. All Rights Reserved.
446            
447             This module is released under the Artistic License (see
448             http://www.perl.com/language/misc/Artistic.htmlZ<>).
449            
450             THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
451             IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
452             FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
453             AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
454             LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
455             OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
456             SOFTWARE.
457            
458             =cut