Parent Directory
|
Revision Log
Revision 1.11 - (view) (download) (as text)
1 : | olson | 1.1 | package Tracer; |
2 : | |||
3 : | require Exporter; | ||
4 : | @ISA = ('Exporter'); | ||
5 : | parrello | 1.11 | @EXPORT = qw(Trace T TSetup QTrace Confess Cluck Min Max Assert Open OpenDir); |
6 : | parrello | 1.9 | @EXPORT_OK = qw(GetFile GetOptions Merge MergeOptions ParseCommand ParseRecord UnEscape Escape); |
7 : | olson | 1.1 | use strict; |
8 : | use Carp qw(longmess croak); | ||
9 : | use CGI; | ||
10 : | parrello | 1.7 | use FIG_Config; |
11 : | parrello | 1.9 | use PageBuilder; |
12 : | olson | 1.1 | |
13 : | =head1 Tracing and Debugging Helpers | ||
14 : | |||
15 : | =head2 Introduction | ||
16 : | |||
17 : | This package provides simple tracing for debugging and reporting purposes. To use it simply call the | ||
18 : | L</TSetup> method to set the options and call L</Trace> to write out trace messages. Each trace | ||
19 : | parrello | 1.2 | message has a I<trace level> and I<category> associated with it. In addition, the tracing package itself |
20 : | has a list of categories and a single trace level set by the B<TSetup> method. Only messages whose trace | ||
21 : | olson | 1.1 | level is less than or equal to this package's trace level and whose category is activated will |
22 : | parrello | 1.2 | be written. Thus, a higher trace level on a message indicates that the message |
23 : | parrello | 1.10 | is less likely to be seen. A higher trace level passed to B<TSetup> means more trace messages will |
24 : | olson | 1.1 | appear. To generate a trace message, use the following syntax. |
25 : | |||
26 : | C<< Trace($message) if T(errors => 4); >> | ||
27 : | |||
28 : | parrello | 1.2 | This statement will produce a trace message if the trace level is 4 or more and the C<errors> |
29 : | parrello | 1.3 | category is active. Note that the special category C<main> is always active, so |
30 : | olson | 1.1 | |
31 : | parrello | 1.3 | C<< Trace($message) if T(main => 4); >> |
32 : | olson | 1.1 | |
33 : | will trace if the trace level is 4 or more. | ||
34 : | |||
35 : | If the category name is the same as the package name, all you need is the number. So, if the | ||
36 : | following call is made in the B<Sprout> package, it will appear if the C<Sprout> category is | ||
37 : | active and the trace level is 2 or more. | ||
38 : | |||
39 : | C<< Trace($message) if T(2); >> | ||
40 : | |||
41 : | parrello | 1.10 | To set up tracing, you call the L</TSetup> method. The method takes as input a trace level, a list |
42 : | olson | 1.1 | of category names, and a set of options. The trace level and list of category names are |
43 : | specified as a space-delimited string. Thus | ||
44 : | |||
45 : | C<< TSetup('3 errors Sprout ERDB', 'HTML'); >> | ||
46 : | |||
47 : | parrello | 1.7 | sets the trace level to 3, activates the C<errors>, C<Sprout>, and C<ERDB> categories, and |
48 : | specifies that messages should be output as HTML paragraphs. The parameters are formatted | ||
49 : | parrello | 1.10 | a little clumsily, but it makes them easier to input on a web form or in a query URL. |
50 : | olson | 1.1 | |
51 : | In addition to HTML and file output for trace messages, you can specify that the trace messages | ||
52 : | be queued. The messages can then be retrieved by calling the L</QTrace> method. This approach | ||
53 : | is useful if you are building a web page. Instead of having the trace messages interspersed with | ||
54 : | the page output, they can be gathered together and displayed at the end of the page. This makes | ||
55 : | it easier to debug page formatting problems. | ||
56 : | |||
57 : | parrello | 1.4 | Finally, you can specify that all trace messages be emitted as warnings. |
58 : | |||
59 : | olson | 1.1 | The flexibility of tracing makes it superior to simple use of directives like C<die> and C<warn>. |
60 : | Tracer calls can be left in the code with minimal overhead and then turned on only when needed. | ||
61 : | Thus, debugging information is available and easily retrieved even when the application is | ||
62 : | being used out in the field. | ||
63 : | |||
64 : | parrello | 1.10 | There is no hard and fast rule on how to use trace levels. The following is therefore only |
65 : | a suggestion. | ||
66 : | |||
67 : | =over 4 | ||
68 : | |||
69 : | =item 0 Error | ||
70 : | |||
71 : | Message indicates an error that may lead to incorrect results or that has stopped the | ||
72 : | application entirely. | ||
73 : | |||
74 : | =item 1 Warning | ||
75 : | |||
76 : | Message indicates something that is unexpected but that probably did not interfere | ||
77 : | with program execution. | ||
78 : | |||
79 : | =item 2 Notice | ||
80 : | |||
81 : | Message indicates the beginning or end of a major task. | ||
82 : | |||
83 : | =item 3 Information | ||
84 : | |||
85 : | Message indicates a subtask. In the FIG system, a subtask generally relates to a single | ||
86 : | genome. This would be a big loop that is not expected to execute more than 500 times or so. | ||
87 : | |||
88 : | =item 4 Detail | ||
89 : | |||
90 : | Message indicates a low-level loop iteration. | ||
91 : | |||
92 : | =back | ||
93 : | |||
94 : | olson | 1.1 | =cut |
95 : | parrello | 1.2 | |
96 : | olson | 1.1 | # Declare the configuration variables. |
97 : | |||
98 : | my $Destination = "NONE"; # Description of where to send the trace output. | ||
99 : | parrello | 1.10 | my $TeeFlag = 0; # TRUE if output is going to a file and to the |
100 : | # standard output | ||
101 : | parrello | 1.3 | my %Categories = ( main => 1 ); |
102 : | olson | 1.1 | # hash of active category names |
103 : | my $TraceLevel = 0; # trace level; a higher trace level produces more | ||
104 : | # messages | ||
105 : | my @Queue = (); # queued list of trace messages. | ||
106 : | parrello | 1.7 | my $LastCategory = "main"; # name of the last category interrogated |
107 : | parrello | 1.11 | my $SetupCount = 0; # number of times TSetup called |
108 : | olson | 1.1 | |
109 : | =head2 Public Methods | ||
110 : | |||
111 : | =head3 TSetup | ||
112 : | |||
113 : | C<< TSetup($categoryList, $target); >> | ||
114 : | |||
115 : | This method is used to specify the trace options. The options are stored as package data | ||
116 : | and interrogated by the L</Trace> and L</T> methods. | ||
117 : | |||
118 : | =over 4 | ||
119 : | |||
120 : | =item categoryList | ||
121 : | |||
122 : | A string specifying the trace level and the categories to be traced, separated by spaces. | ||
123 : | The trace level must come first. | ||
124 : | |||
125 : | =item target | ||
126 : | |||
127 : | The destination for the trace output. To send the trace output to a file, specify the file | ||
128 : | name preceded by a ">" symbol. If a double symbol is used (">>"), then the data is appended | ||
129 : | parrello | 1.10 | to the file. Otherwise the file is cleared before tracing begins. Precede the first ">" |
130 : | symbol with a C<+> to echo output to a file AND to the standard output. In addition to | ||
131 : | sending the trace messages to a file, you can specify a special destination. C<HTML> will | ||
132 : | cause tracing to the standard output with each line formatted as an HTML paragraph. C<TEXT> | ||
133 : | parrello | 1.5 | will cause tracing to the standard output as ordinary text. C<ERROR> will cause trace |
134 : | parrello | 1.9 | messages to be sent to the standard error output as ordinary text. C<QUEUE> will cause trace |
135 : | parrello | 1.6 | messages to be stored in a queue for later retrieval by the L</QTrace> method. C<WARN> will |
136 : | parrello | 1.9 | cause trace messages to be emitted as warnings using the B<warn> directive. C<NONE> will |
137 : | parrello | 1.6 | cause tracing to be suppressed. |
138 : | olson | 1.1 | |
139 : | =back | ||
140 : | |||
141 : | =cut | ||
142 : | |||
143 : | sub TSetup { | ||
144 : | # Get the parameters. | ||
145 : | my ($categoryList, $target) = @_; | ||
146 : | # Parse the category list. | ||
147 : | my @categoryData = split /\s+/, $categoryList; | ||
148 : | # Extract the trace level. | ||
149 : | $TraceLevel = shift @categoryData; | ||
150 : | # Build the category hash. | ||
151 : | for my $category (@categoryData) { | ||
152 : | $Categories{$category} = 1; | ||
153 : | } | ||
154 : | # Now we need to process the destination information. The most important special | ||
155 : | parrello | 1.10 | # cases are the single ">", which requires we clear the file first, and the |
156 : | # "+" prefix which indicates a double echo. | ||
157 : | if ($target =~ m/^\+?>>?/) { | ||
158 : | if ($target =~ m/^\+/) { | ||
159 : | $TeeFlag = 1; | ||
160 : | $target = substr($target, 1); | ||
161 : | } | ||
162 : | if ($target =~ m/^>[^>]/) { | ||
163 : | open TRACEFILE, $target; | ||
164 : | print TRACEFILE Now() . " Tracing initialized.\n"; | ||
165 : | close TRACEFILE; | ||
166 : | $Destination = ">$target"; | ||
167 : | } else { | ||
168 : | $Destination = $target; | ||
169 : | } | ||
170 : | olson | 1.1 | } else { |
171 : | $Destination = uc($target); | ||
172 : | } | ||
173 : | parrello | 1.11 | # Increment the setup counter. |
174 : | $SetupCount++; | ||
175 : | } | ||
176 : | |||
177 : | =head3 Setups | ||
178 : | |||
179 : | C<< my $count = Tracer::Setups(); >> | ||
180 : | |||
181 : | Return the number of times L</TSetup> has been called. | ||
182 : | |||
183 : | This method allows for the creation of conditional tracing setups where, for example, we | ||
184 : | may want to set up tracing if nobody else has done it before us. | ||
185 : | |||
186 : | =cut | ||
187 : | |||
188 : | sub Setups { | ||
189 : | return $SetupCount; | ||
190 : | olson | 1.1 | } |
191 : | |||
192 : | parrello | 1.10 | =head3 Open |
193 : | |||
194 : | C<< my $handle = Open($fileHandle, $fileSpec, $message); >> | ||
195 : | |||
196 : | parrello | 1.11 | Open a file. |
197 : | parrello | 1.10 | |
198 : | The I<$fileSpec> is essentially the second argument of the PERL C<open> | ||
199 : | function. The mode is specified using Unix-like shell information. So, for | ||
200 : | example, | ||
201 : | |||
202 : | Open(\*LOGFILE, '>>/usr/spool/news/twitlog', "Could not open twit log."); | ||
203 : | |||
204 : | would open for output appended to the specified file, and | ||
205 : | |||
206 : | Open(\*DATASTREAM, "| sort -u >$outputFile", "Could not open $outputFile."); | ||
207 : | |||
208 : | would open a pipe that sorts the records written and removes duplicates. Note | ||
209 : | parrello | 1.11 | the use of file handle syntax in the Open call. To use anonymous file handles, |
210 : | code as follows. | ||
211 : | parrello | 1.10 | |
212 : | my $logFile = Open(undef, '>>/usr/spool/news/twitlog', "Could not open twit log."); | ||
213 : | |||
214 : | parrello | 1.11 | The I<$message> parameter is used if the open fails. If it is set to C<0>, then |
215 : | the open returns TRUE if successful and FALSE if an error occurred. Otherwise, a | ||
216 : | failed open will throw an exception and the third parameter will be used to construct | ||
217 : | an error message. If the parameter is omitted, a standard message is constructed | ||
218 : | using the file spec. | ||
219 : | parrello | 1.10 | |
220 : | Could not open "/usr/spool/news/twitlog" | ||
221 : | |||
222 : | Note that the mode characters are automatically cleaned from the file name. | ||
223 : | The actual error message from the file system will be captured and appended to the | ||
224 : | message in any case. | ||
225 : | |||
226 : | Could not open "/usr/spool/news/twitlog": file not found. | ||
227 : | |||
228 : | In some versions of PERL the only error message we get is a number, which | ||
229 : | corresponds to the C++ C<errno> value. | ||
230 : | |||
231 : | Could not open "/usr/spool/news/twitlog": 6. | ||
232 : | |||
233 : | =over 4 | ||
234 : | |||
235 : | =item fileHandle | ||
236 : | |||
237 : | File handle. If this parameter is C<undef>, a file handle will be generated | ||
238 : | and returned as the value of this method. | ||
239 : | |||
240 : | =item fileSpec | ||
241 : | |||
242 : | File name and mode, as per the PERL C<open> function. | ||
243 : | |||
244 : | =item message (optional) | ||
245 : | |||
246 : | Error message to use if the open fails. If omitted, a standard error message | ||
247 : | will be generated. In either case, the error information from the file system | ||
248 : | parrello | 1.11 | is appended to the message. To specify a conditional open that does not throw |
249 : | an error if it fails, use C<0>. | ||
250 : | parrello | 1.10 | |
251 : | =item RETURN | ||
252 : | |||
253 : | parrello | 1.11 | Returns the name of the file handle assigned to the file, or C<undef> if the |
254 : | open failed. | ||
255 : | parrello | 1.10 | |
256 : | =back | ||
257 : | |||
258 : | =cut | ||
259 : | |||
260 : | sub Open { | ||
261 : | # Get the parameters. | ||
262 : | my ($fileHandle, $fileSpec, $message) = @_; | ||
263 : | # Attempt to open the file. | ||
264 : | my $rv = open $fileHandle, $fileSpec; | ||
265 : | # If the open failed, generate an error message. | ||
266 : | if (! $rv) { | ||
267 : | # Save the system error message. | ||
268 : | my $sysMessage = $!; | ||
269 : | parrello | 1.11 | # See if we need a default message. |
270 : | parrello | 1.10 | if (!$message) { |
271 : | parrello | 1.11 | # Clean any obvious mode characters and leading spaces from the |
272 : | # filename. | ||
273 : | my ($fileName) = FindNamePart($fileSpec); | ||
274 : | $message = "Could not open \"$fileName\""; | ||
275 : | parrello | 1.10 | } |
276 : | # Terminate with an error using the supplied message and the | ||
277 : | # error message from the file system. | ||
278 : | Confess("$message: $!"); | ||
279 : | } | ||
280 : | # Return the file handle. | ||
281 : | return $fileHandle; | ||
282 : | } | ||
283 : | |||
284 : | parrello | 1.11 | =head3 FindNamePart |
285 : | |||
286 : | C<< my ($fileName, $start, $len) = Tracer::FindNamePart($fileSpec); >> | ||
287 : | |||
288 : | Extract the portion of a file specification that contains the file name. | ||
289 : | |||
290 : | A file specification is the string passed to an C<open> call. It specifies the file | ||
291 : | mode and name. In a truly complex situation, it can specify a pipe sequence. This | ||
292 : | method assumes that the file name is whatever follows the first angle bracket | ||
293 : | sequence. So, for example, in the following strings the file name is | ||
294 : | C</usr/fig/myfile.txt>. | ||
295 : | |||
296 : | >>/usr/fig/myfile.txt | ||
297 : | </usr/fig/myfile.txt | ||
298 : | | sort -u > /usr/fig/myfile.txt | ||
299 : | |||
300 : | If the method cannot find a file name using its normal methods, it will return the | ||
301 : | whole incoming string. | ||
302 : | |||
303 : | =over 4 | ||
304 : | |||
305 : | =item fileSpec | ||
306 : | |||
307 : | File specification string from which the file name is to be extracted. | ||
308 : | |||
309 : | =item RETURN | ||
310 : | |||
311 : | Returns a three-element list. The first element contains the file name portion of | ||
312 : | the specified string, or the whole string if a file name cannot be found via normal | ||
313 : | methods. The second element contains the start position of the file name portion and | ||
314 : | the third element contains the length. | ||
315 : | |||
316 : | =back | ||
317 : | |||
318 : | =cut | ||
319 : | #: Return Type $; | ||
320 : | sub FindNamePart { | ||
321 : | # Get the parameters. | ||
322 : | my ($fileSpec) = @_; | ||
323 : | # Default to the whole input string. | ||
324 : | my ($retVal, $pos, $len) = ($fileSpec, 0, length $fileSpec); | ||
325 : | # Parse out the file name if we can. | ||
326 : | if ($fileSpec =~ m/(<|>>?)(.+?)(\s*)$/) { | ||
327 : | $retVal = $2; | ||
328 : | $len = length $retVal; | ||
329 : | $pos = (length $fileSpec) - (length $3) - $len; | ||
330 : | } | ||
331 : | # Return the result. | ||
332 : | return ($retVal, $pos, $len); | ||
333 : | } | ||
334 : | |||
335 : | =head3 OpenDir | ||
336 : | |||
337 : | C<< my @files = OpenDir($dirName, $filtered); >> | ||
338 : | |||
339 : | Open a directory and return all the file names. This function essentially performs | ||
340 : | the functions of an C<opendir> and C<readdir>. If the I<$filtered> parameter is | ||
341 : | set to TRUE, all filenames beginning with a period (C<.>) will be filtered out of | ||
342 : | the return list. If the directory does not open, an exception is thrown. So, | ||
343 : | for example, | ||
344 : | |||
345 : | my @files = OpenDir("/Volumes/fig/contigs", 1); | ||
346 : | |||
347 : | is effectively the same as | ||
348 : | |||
349 : | opendir(TMP, "/Volumes/fig/contigs") || Confess("Could not open /Volumes/fig/contigs."); | ||
350 : | my @files = grep { $_ !~ /^\./ } readdir(TMP); | ||
351 : | |||
352 : | Similarly, the following code | ||
353 : | |||
354 : | my @files = grep { $_ =~ /^\d/ } OpenDir("/Volumes/fig/orgs"); | ||
355 : | |||
356 : | Returns the names of all files in C</Volumes/fig/orgs> that begin with digits and | ||
357 : | automatically throws an error if the directory fails to open. | ||
358 : | |||
359 : | =over 4 | ||
360 : | |||
361 : | =item dirName | ||
362 : | |||
363 : | Name of the directory to open. | ||
364 : | |||
365 : | =item filtered | ||
366 : | |||
367 : | TRUE if files whose names begin with a period (C<.>) should be automatically removed | ||
368 : | from the list, else FALSE. | ||
369 : | |||
370 : | =back | ||
371 : | |||
372 : | =cut | ||
373 : | #: Return Type @; | ||
374 : | sub OpenDir { | ||
375 : | # Get the parameters. | ||
376 : | my ($dirName, $filtered) = @_; | ||
377 : | # Declare the return variable. | ||
378 : | my @retVal; | ||
379 : | # Open the directory. | ||
380 : | if (opendir(my $dirHandle, $dirName)) { | ||
381 : | # The directory opened successfully. Get the appropriate list according to the | ||
382 : | # strictures of the filter parameter. | ||
383 : | if ($filtered) { | ||
384 : | @retVal = grep { $_ !~ /^\./ } readdir $dirHandle; | ||
385 : | } else { | ||
386 : | @retVal = readdir $dirHandle; | ||
387 : | } | ||
388 : | } else { | ||
389 : | # Here the directory would not open. | ||
390 : | Confess("Could not open directory $dirName."); | ||
391 : | } | ||
392 : | # Return the result. | ||
393 : | return @retVal; | ||
394 : | } | ||
395 : | |||
396 : | parrello | 1.6 | =head3 SetLevel |
397 : | |||
398 : | C<< Tracer::SetLevel($newLevel); >> | ||
399 : | |||
400 : | Modify the trace level. A higher trace level will cause more messages to appear. | ||
401 : | |||
402 : | =over 4 | ||
403 : | |||
404 : | =item newLevel | ||
405 : | |||
406 : | Proposed new trace level. | ||
407 : | |||
408 : | =back | ||
409 : | |||
410 : | =cut | ||
411 : | |||
412 : | sub SetLevel { | ||
413 : | $TraceLevel = $_[0]; | ||
414 : | } | ||
415 : | |||
416 : | olson | 1.1 | =head3 Now |
417 : | |||
418 : | C<< my $string = Tracer::Now(); >> | ||
419 : | |||
420 : | Return a displayable time stamp containing the local time. | ||
421 : | |||
422 : | =cut | ||
423 : | |||
424 : | sub Now { | ||
425 : | my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); | ||
426 : | my $retVal = _p2($mon+1) . "/" . _p2($mday) . "/" . ($year + 1900) . " " . | ||
427 : | _p2($hour) . ":" . _p2($min) . ":" . _p2($sec); | ||
428 : | parrello | 1.2 | return $retVal; |
429 : | olson | 1.1 | } |
430 : | |||
431 : | # Pad a number to 2 digits. | ||
432 : | sub _p2 { | ||
433 : | my ($value) = @_; | ||
434 : | $value = "0$value" if ($value < 10); | ||
435 : | return $value; | ||
436 : | } | ||
437 : | |||
438 : | =head3 LogErrors | ||
439 : | |||
440 : | C<< Tracer::LogErrors($fileName); >> | ||
441 : | |||
442 : | Route the standard error output to a log file. | ||
443 : | |||
444 : | =over 4 | ||
445 : | |||
446 : | =item fileName | ||
447 : | |||
448 : | Name of the file to receive the error output. | ||
449 : | |||
450 : | =back | ||
451 : | |||
452 : | =cut | ||
453 : | |||
454 : | sub LogErrors { | ||
455 : | # Get the file name. | ||
456 : | my ($fileName) = @_; | ||
457 : | # Open the file as the standard error output. | ||
458 : | open STDERR, '>', $fileName; | ||
459 : | } | ||
460 : | |||
461 : | parrello | 1.5 | =head3 ReadOptions |
462 : | |||
463 : | C<< my %options = Tracer::ReadOptions($fileName); >> | ||
464 : | |||
465 : | Read a set of options from a file. Each option is encoded in a line of text that has the | ||
466 : | format | ||
467 : | |||
468 : | I<optionName>C<=>I<optionValue>C<; >I<comment> | ||
469 : | |||
470 : | The option name must consist entirely of letters, digits, and the punctuation characters | ||
471 : | parrello | 1.9 | C<.> and C<_>, and is case sensitive. Blank lines and lines in which the first nonblank |
472 : | character is a semi-colon will be ignored. The return hash will map each option name to | ||
473 : | parrello | 1.5 | the corresponding option value. |
474 : | |||
475 : | =over 4 | ||
476 : | |||
477 : | =item fileName | ||
478 : | |||
479 : | Name of the file containing the option data. | ||
480 : | |||
481 : | =item RETURN | ||
482 : | |||
483 : | Returns a hash mapping the option names specified in the file to their corresponding option | ||
484 : | value. | ||
485 : | |||
486 : | =back | ||
487 : | |||
488 : | =cut | ||
489 : | |||
490 : | sub ReadOptions { | ||
491 : | # Get the parameters. | ||
492 : | my ($fileName) = @_; | ||
493 : | # Open the file. | ||
494 : | (open CONFIGFILE, "<$fileName") || Confess("Could not open option file $fileName."); | ||
495 : | # Count the number of records read. | ||
496 : | my ($records, $comments) = 0; | ||
497 : | # Create the return hash. | ||
498 : | my %retVal = (); | ||
499 : | # Loop through the file, accumulating key-value pairs. | ||
500 : | while (my $line = <CONFIGFILE>) { | ||
501 : | # Denote we've read a line. | ||
502 : | $records++; | ||
503 : | # Determine the line type. | ||
504 : | if ($line =~ /^\s*[\n\r]/) { | ||
505 : | # A blank line is a comment. | ||
506 : | $comments++; | ||
507 : | } elsif ($line =~ /^\s*([A-Za-z0-9_\.]+)=([^;]*);/) { | ||
508 : | # Here we have an option assignment. | ||
509 : | retVal{$1} = $2; | ||
510 : | } elsif ($line =~ /^\s*;/) { | ||
511 : | # Here we have a text comment. | ||
512 : | $comments++; | ||
513 : | } else { | ||
514 : | # Here we have an invalid line. | ||
515 : | Trace("Invalid option statement in record $records.") if T(0); | ||
516 : | } | ||
517 : | } | ||
518 : | # Return the hash created. | ||
519 : | return %retVal; | ||
520 : | } | ||
521 : | |||
522 : | olson | 1.1 | =head3 GetOptions |
523 : | |||
524 : | C<< Tracer::GetOptions(\%defaults, \%options); >> | ||
525 : | |||
526 : | Merge a specified set of options into a table of defaults. This method takes two hash references | ||
527 : | as input and uses the data from the second to update the first. If the second does not exist, | ||
528 : | there will be no effect. An error will be thrown if one of the entries in the second hash does not | ||
529 : | exist in the first. | ||
530 : | |||
531 : | Consider the following example. | ||
532 : | |||
533 : | C<< my $optionTable = GetOptions({ dbType => 'mySQL', trace => 0 }, $options); >> | ||
534 : | |||
535 : | In this example, the variable B<$options> is expected to contain at most two options-- B<dbType> and | ||
536 : | B<trace>. The default database type is C<mySQL> and the default trace level is C<0>. If the value of | ||
537 : | B<$options> is C<< {dbType => 'Oracle'} >>, then the database type will be changed to C<Oracle> and | ||
538 : | the trace level will remain at 0. If B<$options> is undefined, then the database type and trace level | ||
539 : | will remain C<mySQL> and C<0>. If, on the other hand, B<$options> is defined as | ||
540 : | |||
541 : | C<< {databaseType => 'Oracle'} >> | ||
542 : | |||
543 : | an error will occur because the B<databaseType> option does not exist. | ||
544 : | |||
545 : | =over 4 | ||
546 : | |||
547 : | =item defaults | ||
548 : | |||
549 : | Table of default option values. | ||
550 : | |||
551 : | =item options | ||
552 : | |||
553 : | Table of overrides, if any. | ||
554 : | |||
555 : | =item RETURN | ||
556 : | |||
557 : | Returns a reference to the default table passed in as the first parameter. | ||
558 : | |||
559 : | =back | ||
560 : | |||
561 : | =cut | ||
562 : | |||
563 : | sub GetOptions { | ||
564 : | # Get the parameters. | ||
565 : | my ($defaults, $options) = @_; | ||
566 : | # Check for overrides. | ||
567 : | if ($options) { | ||
568 : | # Loop through the overrides. | ||
569 : | while (my ($option, $setting) = each %{$options}) { | ||
570 : | # Insure this override exists. | ||
571 : | if (!exists $defaults->{$option}) { | ||
572 : | croak "Unrecognized option $option encountered."; | ||
573 : | } else { | ||
574 : | # Apply the override. | ||
575 : | $defaults->{$option} = $setting; | ||
576 : | } | ||
577 : | } | ||
578 : | } | ||
579 : | # Return the merged table. | ||
580 : | return $defaults; | ||
581 : | } | ||
582 : | |||
583 : | =head3 MergeOptions | ||
584 : | |||
585 : | C<< Tracer::MergeOptions(\%table, \%defaults); >> | ||
586 : | |||
587 : | Merge default values into a hash table. This method looks at the key-value pairs in the | ||
588 : | second (default) hash, and if a matching key is not found in the first hash, the default | ||
589 : | pair is copied in. The process is similar to L</GetOptions>, but there is no error- | ||
590 : | checking and no return value. | ||
591 : | |||
592 : | =over 4 | ||
593 : | |||
594 : | =item table | ||
595 : | |||
596 : | Hash table to be updated with the default values. | ||
597 : | |||
598 : | =item defaults | ||
599 : | |||
600 : | Default values to be merged into the first hash table if they are not already present. | ||
601 : | |||
602 : | =back | ||
603 : | |||
604 : | =cut | ||
605 : | |||
606 : | sub MergeOptions { | ||
607 : | # Get the parameters. | ||
608 : | my ($table, $defaults) = @_; | ||
609 : | # Loop through the defaults. | ||
610 : | while (my ($key, $value) = each %{$defaults}) { | ||
611 : | if (!exists $table->{$key}) { | ||
612 : | $table->{$key} = $value; | ||
613 : | } | ||
614 : | } | ||
615 : | } | ||
616 : | |||
617 : | =head3 Trace | ||
618 : | |||
619 : | C<< Trace($message); >> | ||
620 : | |||
621 : | Write a trace message to the target location specified in L</TSetup>. If there has not been | ||
622 : | any prior call to B<TSetup>. | ||
623 : | |||
624 : | =over 4 | ||
625 : | |||
626 : | =item message | ||
627 : | |||
628 : | Message to write. | ||
629 : | |||
630 : | =back | ||
631 : | |||
632 : | =cut | ||
633 : | |||
634 : | sub Trace { | ||
635 : | # Get the parameters. | ||
636 : | my ($message) = @_; | ||
637 : | # Get the timestamp. | ||
638 : | my $timeStamp = Now(); | ||
639 : | parrello | 1.9 | # Format the message. Note we strip off any line terminators at the end. |
640 : | my $formatted = "$timeStamp <$LastCategory>: " . Strip($message); | ||
641 : | olson | 1.1 | # Process according to the destination. |
642 : | if ($Destination eq "TEXT") { | ||
643 : | # Write the message to the standard output. | ||
644 : | parrello | 1.5 | print "$formatted\n"; |
645 : | } elsif ($Destination eq "ERROR") { | ||
646 : | # Write the message to the error output. | ||
647 : | print STDERR "$formatted\n"; | ||
648 : | olson | 1.1 | } elsif ($Destination eq "QUEUE") { |
649 : | # Push the message into the queue. | ||
650 : | parrello | 1.5 | push @Queue, "$formatted"; |
651 : | olson | 1.1 | } elsif ($Destination eq "HTML") { |
652 : | # Convert the message to HTML and write it to the standard output. | ||
653 : | my $escapedMessage = CGI::escapeHTML($message); | ||
654 : | parrello | 1.5 | print "<p>$formatted</p>\n"; |
655 : | parrello | 1.4 | } elsif ($Destination eq "WARN") { |
656 : | # Emit the message as a warning. | ||
657 : | warn $message; | ||
658 : | olson | 1.1 | } elsif ($Destination =~ m/^>>/) { |
659 : | # Write the trace message to an output file. | ||
660 : | open TRACING, $Destination; | ||
661 : | parrello | 1.5 | print TRACING "$formatted\n"; |
662 : | olson | 1.1 | close TRACING; |
663 : | parrello | 1.10 | # If the Tee flag is on, echo it to the standard output. |
664 : | if ($TeeFlag) { | ||
665 : | print "$formatted\n"; | ||
666 : | } | ||
667 : | olson | 1.1 | } |
668 : | } | ||
669 : | |||
670 : | =head3 T | ||
671 : | |||
672 : | parrello | 1.2 | C<< my $switch = T($category, $traceLevel); >> |
673 : | olson | 1.1 | |
674 : | or | ||
675 : | parrello | 1.2 | |
676 : | olson | 1.1 | C<< my $switch = T($traceLevel); >> |
677 : | |||
678 : | Return TRUE if the trace level is at or above a specified value and the specified category | ||
679 : | is active, else FALSE. If no category is specified, the caller's package name is used. | ||
680 : | |||
681 : | =over 4 | ||
682 : | |||
683 : | =item category | ||
684 : | |||
685 : | Category to which the message belongs. If not specified, the caller's package name is | ||
686 : | used. | ||
687 : | |||
688 : | =item traceLevel | ||
689 : | |||
690 : | Relevant tracing level. | ||
691 : | |||
692 : | =item RETURN | ||
693 : | |||
694 : | TRUE if a message at the specified trace level would appear in the trace, else FALSE. | ||
695 : | |||
696 : | =back | ||
697 : | |||
698 : | =cut | ||
699 : | |||
700 : | sub T { | ||
701 : | # Declare the return variable. | ||
702 : | my $retVal = 0; | ||
703 : | # Only proceed if tracing is turned on. | ||
704 : | if ($Destination ne "NONE") { | ||
705 : | # Get the parameters. | ||
706 : | my ($category, $traceLevel) = @_; | ||
707 : | if (!defined $traceLevel) { | ||
708 : | # Here we have no category, so we need to get the calling package. | ||
709 : | $traceLevel = $category; | ||
710 : | my ($package, $fileName, $line) = caller; | ||
711 : | parrello | 1.3 | # If there is no calling package, we default to "main". |
712 : | olson | 1.1 | if (!$package) { |
713 : | parrello | 1.3 | $category = "main"; |
714 : | olson | 1.1 | } else { |
715 : | $category = $package; | ||
716 : | } | ||
717 : | } | ||
718 : | parrello | 1.7 | # Save the category name. |
719 : | $LastCategory = $category; | ||
720 : | # Use the category and tracelevel to compute the result. | ||
721 : | olson | 1.1 | $retVal = ($traceLevel <= $TraceLevel && exists $Categories{$category}); |
722 : | parrello | 1.3 | } |
723 : | olson | 1.1 | # Return the computed result. |
724 : | parrello | 1.3 | return $retVal; |
725 : | olson | 1.1 | } |
726 : | |||
727 : | =head3 ParseCommand | ||
728 : | |||
729 : | C<< my ($options, @arguments) = Tracer::ParseCommand(\%optionTable, @inputList); >> | ||
730 : | |||
731 : | Parse a command line consisting of a list of parameters. The initial parameters may be option | ||
732 : | parrello | 1.2 | specifiers of the form C<->I<option> or C<->I<option>C<=>I<value>. The options are stripped |
733 : | off and merged into a table of default options. The remainder of the command line is | ||
734 : | olson | 1.1 | returned as a list of positional arguments. For example, consider the following invocation. |
735 : | |||
736 : | C<< my ($options, @arguments) = ParseCommand({ errors => 0, logFile => 'trace.log'}, @words); >> | ||
737 : | |||
738 : | In this case, the list @words will be treated as a command line. There are two options available, | ||
739 : | B<errors> and B<logFile>. If @words has the following format | ||
740 : | |||
741 : | C<< -logFile=error.log apple orange rutabaga >> | ||
742 : | |||
743 : | then at the end of the invocation, C<$options> will be | ||
744 : | |||
745 : | C<< { errors => 0, logFile => 'error.log' } >> | ||
746 : | |||
747 : | and C<@arguments> will contain | ||
748 : | |||
749 : | C<< apple orange rutabaga >> | ||
750 : | |||
751 : | parrello | 1.2 | The parser allows for some escape sequences. See L</UnEscape> for a description. There is no |
752 : | olson | 1.1 | support for quote characters. |
753 : | |||
754 : | =over 4 | ||
755 : | |||
756 : | =item optionTable | ||
757 : | |||
758 : | Table of default options. | ||
759 : | |||
760 : | =item inputList | ||
761 : | |||
762 : | List of words on the command line. | ||
763 : | |||
764 : | =item RETURN | ||
765 : | |||
766 : | Returns a reference to the option table and a list of the positional arguments. | ||
767 : | |||
768 : | =back | ||
769 : | |||
770 : | =cut | ||
771 : | |||
772 : | sub ParseCommand { | ||
773 : | # Get the parameters. | ||
774 : | my ($optionTable, @inputList) = @_; | ||
775 : | # Process any options in the input list. | ||
776 : | my %overrides = (); | ||
777 : | while ((@inputList > 0) && ($inputList[0] =~ /^-/)) { | ||
778 : | # Get the current option. | ||
779 : | my $arg = shift @inputList; | ||
780 : | # Pull out the option name. | ||
781 : | $arg =~ /^-([^=]*)/g; | ||
782 : | my $name = $1; | ||
783 : | # Check for an option value. | ||
784 : | if ($arg =~ /\G=(.*)$/g) { | ||
785 : | # Here we have a value for the option. | ||
786 : | $overrides{$name} = UnEscape($1); | ||
787 : | } else { | ||
788 : | # Here there is no value, so we use 1. | ||
789 : | $overrides{$name} = 1; | ||
790 : | } | ||
791 : | } | ||
792 : | # Merge the options into the defaults. | ||
793 : | GetOptions($optionTable, \%overrides); | ||
794 : | # Translate the remaining parameters. | ||
795 : | my @retVal = (); | ||
796 : | for my $inputParm (@inputList) { | ||
797 : | push @retVal, UnEscape($inputParm); | ||
798 : | } | ||
799 : | # Return the results. | ||
800 : | return ($optionTable, @retVal); | ||
801 : | } | ||
802 : | |||
803 : | parrello | 1.9 | =head3 Escape |
804 : | |||
805 : | C<< my $codedString = Tracer::Escape($realString); >> | ||
806 : | |||
807 : | Escape a string for use in a command length. Spaces will be replaced by C<\b>, | ||
808 : | tabs replaced by C<\t>, new-lines replaced by C<\n>, and backslashes will be | ||
809 : | doubled. The effect is to exactly reverse the effect of L</UnEscape>. | ||
810 : | |||
811 : | =over 4 | ||
812 : | |||
813 : | =item realString | ||
814 : | |||
815 : | String to escape. | ||
816 : | |||
817 : | =item RETURN | ||
818 : | |||
819 : | Escaped equivalent of the real string. | ||
820 : | |||
821 : | =back | ||
822 : | |||
823 : | =cut | ||
824 : | |||
825 : | sub Escape { | ||
826 : | # Get the parameter. | ||
827 : | my ($realString) = @_; | ||
828 : | # Initialize the return variable. | ||
829 : | my $retVal = ""; | ||
830 : | # Loop through the parameter string, looking for sequences to escape. | ||
831 : | while (length $realString > 0) { | ||
832 : | # Look for the first sequence to escape. | ||
833 : | if ($realString =~ /^(.*?)([ \n\t\\])/) { | ||
834 : | # Here we found it. The text preceding the sequence is in $1. The sequence | ||
835 : | # itself is in $2. First, move the clear text to the return variable. | ||
836 : | $retVal .= $1; | ||
837 : | $realString = substr $realString, (length $2 + length $1); | ||
838 : | # Encode the escape sequence. | ||
839 : | my $char = $2; | ||
840 : | $char =~ tr/ \t\n/btn/; | ||
841 : | $retVal .= "\\" . $char; | ||
842 : | } else { | ||
843 : | # Here there are no more escape sequences. The rest of the string is | ||
844 : | # transferred unmodified. | ||
845 : | $retVal .= $realString; | ||
846 : | $realString = ""; | ||
847 : | } | ||
848 : | } | ||
849 : | # Return the result. | ||
850 : | return $retVal; | ||
851 : | } | ||
852 : | |||
853 : | olson | 1.1 | =head3 UnEscape |
854 : | |||
855 : | C<< my $realString = Tracer::UnEscape($codedString); >> | ||
856 : | |||
857 : | Replace escape sequences with their actual equivalents. C<\b> will be replaced by a space, | ||
858 : | C<\t> by a tab, C<\n> by a new-line character, and C<\\> by a back-slash. | ||
859 : | |||
860 : | =over 4 | ||
861 : | |||
862 : | =item codedString | ||
863 : | |||
864 : | String to un-escape. | ||
865 : | |||
866 : | =item RETURN | ||
867 : | |||
868 : | Returns a copy of the original string with the escape sequences converted to their actual | ||
869 : | values. | ||
870 : | |||
871 : | =back | ||
872 : | |||
873 : | =cut | ||
874 : | |||
875 : | sub UnEscape { | ||
876 : | # Get the parameter. | ||
877 : | my ($codedString) = @_; | ||
878 : | # Initialize the return variable. | ||
879 : | my $retVal = ""; | ||
880 : | parrello | 1.9 | # Only proceed if the incoming string is nonempty. |
881 : | if (defined $codedString) { | ||
882 : | # Loop through the parameter string, looking for escape sequences. We can't do | ||
883 : | # translating because it causes problems with the escaped slash. ("\\b" becomes | ||
884 : | # "\ " no matter what we do.) | ||
885 : | while (length $codedString > 0) { | ||
886 : | # Look for the first escape sequence. | ||
887 : | if ($codedString =~ /^(.*?)\\(\\|b|n|t)/) { | ||
888 : | # Here we found it. The text preceding the sequence is in $1. The sequence | ||
889 : | # itself is in $2. First, move the clear text to the return variable. | ||
890 : | $retVal .= $1; | ||
891 : | $codedString = substr $codedString, (2 + length $1); | ||
892 : | # Decode the escape sequence. | ||
893 : | my $char = $2; | ||
894 : | $char =~ tr/\\btn/\\ \t\n/; | ||
895 : | $retVal .= $char; | ||
896 : | } else { | ||
897 : | # Here there are no more escape sequences. The rest of the string is | ||
898 : | # transferred unmodified. | ||
899 : | $retVal .= $codedString; | ||
900 : | $codedString = ""; | ||
901 : | } | ||
902 : | olson | 1.1 | } |
903 : | } | ||
904 : | # Return the result. | ||
905 : | parrello | 1.2 | return $retVal; |
906 : | olson | 1.1 | } |
907 : | |||
908 : | =head3 ParseRecord | ||
909 : | |||
910 : | C<< my @fields = Tracer::ParseRecord($line); >> | ||
911 : | |||
912 : | Parse a tab-delimited data line. The data line is split into field values. Embedded tab | ||
913 : | and new-line characters in the data line must be represented as C<\t> and C<\n>, respectively. | ||
914 : | These will automatically be converted. | ||
915 : | |||
916 : | =over 4 | ||
917 : | |||
918 : | =item line | ||
919 : | |||
920 : | Line of data containing the tab-delimited fields. | ||
921 : | |||
922 : | =item RETURN | ||
923 : | |||
924 : | Returns a list of the fields found in the data line. | ||
925 : | |||
926 : | =back | ||
927 : | |||
928 : | =cut | ||
929 : | |||
930 : | sub ParseRecord { | ||
931 : | # Get the parameter. | ||
932 : | my ($line) = @_; | ||
933 : | # Remove the trailing new-line, if any. | ||
934 : | chomp $line; | ||
935 : | # Split the line read into pieces using the tab character. | ||
936 : | my @retVal = split /\t/, $line; | ||
937 : | # Trim and fix the escapes in each piece. | ||
938 : | for my $value (@retVal) { | ||
939 : | # Trim leading whitespace. | ||
940 : | $value =~ s/^\s+//; | ||
941 : | # Trim trailing whitespace. | ||
942 : | $value =~ s/\s+$//; | ||
943 : | # Delete the carriage returns. | ||
944 : | $value =~ s/\r//g; | ||
945 : | # Convert the escapes into their real values. | ||
946 : | $value =~ s/\\t/"\t"/ge; | ||
947 : | $value =~ s/\\n/"\n"/ge; | ||
948 : | } | ||
949 : | # Return the result. | ||
950 : | return @retVal; | ||
951 : | } | ||
952 : | |||
953 : | =head3 Merge | ||
954 : | |||
955 : | C<< my @mergedList = Tracer::Merge(@inputList); >> | ||
956 : | |||
957 : | Sort a list of strings and remove duplicates. | ||
958 : | |||
959 : | =over 4 | ||
960 : | |||
961 : | =item inputList | ||
962 : | |||
963 : | List of scalars to sort and merge. | ||
964 : | |||
965 : | =item RETURN | ||
966 : | |||
967 : | Returns a list containing the same elements sorted in ascending order with duplicates | ||
968 : | removed. | ||
969 : | |||
970 : | =back | ||
971 : | |||
972 : | =cut | ||
973 : | |||
974 : | sub Merge { | ||
975 : | # Get the input list in sort order. | ||
976 : | my @inputList = sort @_; | ||
977 : | # Only proceed if the list has at least two elements. | ||
978 : | if (@inputList > 1) { | ||
979 : | # Now we want to move through the list splicing out duplicates. | ||
980 : | my $i = 0; | ||
981 : | while ($i < @inputList) { | ||
982 : | # Get the current entry. | ||
983 : | my $thisEntry = $inputList[$i]; | ||
984 : | # Find out how many elements duplicate the current entry. | ||
985 : | my $j = $i + 1; | ||
986 : | my $dup1 = $i + 1; | ||
987 : | while ($j < @inputList && $inputList[$j] eq $thisEntry) { $j++; }; | ||
988 : | # If the number is nonzero, splice out the duplicates found. | ||
989 : | if ($j > $dup1) { | ||
990 : | splice @inputList, $dup1, $j - $dup1; | ||
991 : | } | ||
992 : | # Now the element at position $dup1 is different from the element before it | ||
993 : | # at position $i. We push $i forward one position and start again. | ||
994 : | $i++; | ||
995 : | } | ||
996 : | } | ||
997 : | # Return the merged list. | ||
998 : | return @inputList; | ||
999 : | } | ||
1000 : | |||
1001 : | =head3 GetFile | ||
1002 : | |||
1003 : | parrello | 1.6 | C<< my @fileContents = Tracer::GetFile($fileName); >> |
1004 : | olson | 1.1 | |
1005 : | Return the entire contents of a file. | ||
1006 : | |||
1007 : | =over 4 | ||
1008 : | |||
1009 : | =item fileName | ||
1010 : | |||
1011 : | Name of the file to read. | ||
1012 : | |||
1013 : | =item RETURN | ||
1014 : | |||
1015 : | parrello | 1.6 | In a list context, returns the entire file as a list with the line terminators removed. |
1016 : | In a scalar context, returns the entire file as a string. | ||
1017 : | olson | 1.1 | |
1018 : | =back | ||
1019 : | |||
1020 : | =cut | ||
1021 : | |||
1022 : | sub GetFile { | ||
1023 : | # Get the parameters. | ||
1024 : | my ($fileName) = @_; | ||
1025 : | # Declare the return variable. | ||
1026 : | parrello | 1.6 | my @retVal = (); |
1027 : | olson | 1.1 | # Open the file for input. |
1028 : | my $ok = open INPUTFILE, "<$fileName"; | ||
1029 : | if (!$ok) { | ||
1030 : | parrello | 1.6 | # If we had an error, trace it. We will automatically return a null value. |
1031 : | Trace("Could not open \"$fileName\" for input.") if T(0); | ||
1032 : | olson | 1.1 | } else { |
1033 : | parrello | 1.9 | # Read the whole file into the return variable, stripping off any terminator |
1034 : | parrello | 1.6 | # characters. |
1035 : | my $lineCount = 0; | ||
1036 : | while (my $line = <INPUTFILE>) { | ||
1037 : | $lineCount++; | ||
1038 : | parrello | 1.9 | $line = Strip($line); |
1039 : | parrello | 1.6 | push @retVal, $line; |
1040 : | olson | 1.1 | } |
1041 : | # Close it. | ||
1042 : | close INPUTFILE; | ||
1043 : | parrello | 1.6 | my $actualLines = @retVal; |
1044 : | olson | 1.1 | } |
1045 : | parrello | 1.6 | # Return the file's contents in the desired format. |
1046 : | parrello | 1.9 | if (wantarray) { |
1047 : | parrello | 1.6 | return @retVal; |
1048 : | } else { | ||
1049 : | return join "\n", @retVal; | ||
1050 : | } | ||
1051 : | olson | 1.1 | } |
1052 : | |||
1053 : | =head3 QTrace | ||
1054 : | |||
1055 : | C<< my $data = QTrace($format); >> | ||
1056 : | |||
1057 : | Return the queued trace data in the specified format. | ||
1058 : | |||
1059 : | =over 4 | ||
1060 : | |||
1061 : | =item format | ||
1062 : | |||
1063 : | C<html> to format the data as an HTML list, C<text> to format it as straight text. | ||
1064 : | |||
1065 : | =back | ||
1066 : | |||
1067 : | =cut | ||
1068 : | |||
1069 : | sub QTrace { | ||
1070 : | # Get the parameter. | ||
1071 : | my ($format) = @_; | ||
1072 : | # Create the return variable. | ||
1073 : | my $retVal = ""; | ||
1074 : | # Process according to the format. | ||
1075 : | if ($format =~ m/^HTML$/i) { | ||
1076 : | # Convert the queue into an HTML list. | ||
1077 : | $retVal = "<ul>\n"; | ||
1078 : | for my $line (@Queue) { | ||
1079 : | my $escapedLine = CGI::escapeHTML($line); | ||
1080 : | $retVal .= "<li>$escapedLine</li>\n"; | ||
1081 : | } | ||
1082 : | $retVal .= "</ul>\n"; | ||
1083 : | } elsif ($format =~ m/^TEXT$/i) { | ||
1084 : | # Convert the queue into a list of text lines. | ||
1085 : | $retVal = join("\n", @Queue) . "\n"; | ||
1086 : | } | ||
1087 : | # Clear the queue. | ||
1088 : | @Queue = (); | ||
1089 : | # Return the formatted list. | ||
1090 : | return $retVal; | ||
1091 : | } | ||
1092 : | |||
1093 : | =head3 Confess | ||
1094 : | |||
1095 : | C<< Confess($message); >> | ||
1096 : | |||
1097 : | Trace the call stack and abort the program with the specified message. The stack | ||
1098 : | trace will only appear if the trace level for this package is 1 or more. When used with | ||
1099 : | parrello | 1.9 | the OR operator and the L</Assert> method, B<Confess> can function as a debugging assert. |
1100 : | parrello | 1.6 | So, for example |
1101 : | olson | 1.1 | |
1102 : | parrello | 1.6 | C<< Assert($recNum >= 0) || Confess("Invalid record number $recNum."); >> |
1103 : | olson | 1.1 | |
1104 : | Will abort the program with a stack trace if the value of C<$recNum> is negative. | ||
1105 : | |||
1106 : | =over 4 | ||
1107 : | |||
1108 : | =item message | ||
1109 : | |||
1110 : | Message to include in the trace. | ||
1111 : | |||
1112 : | =back | ||
1113 : | |||
1114 : | =cut | ||
1115 : | |||
1116 : | sub Confess { | ||
1117 : | # Get the parameters. | ||
1118 : | my ($message) = @_; | ||
1119 : | # Trace the call stack. | ||
1120 : | Cluck($message) if T(1); | ||
1121 : | # Abort the program. | ||
1122 : | parrello | 1.5 | croak(">>> $message"); |
1123 : | olson | 1.1 | } |
1124 : | |||
1125 : | parrello | 1.6 | =head3 Assert |
1126 : | |||
1127 : | C<< Assert($condition1, $condition2, ... $conditionN); >> | ||
1128 : | |||
1129 : | Return TRUE if all the conditions are true. This method can be used in conjunction with | ||
1130 : | the OR operator and the L</Confess> method, B<Assert> can function as a debugging assert. | ||
1131 : | So, for example | ||
1132 : | |||
1133 : | C<< Assert($recNum >= 0) || Confess("Invalid record number $recNum."); >> | ||
1134 : | |||
1135 : | Will abort the program with a stack trace if the value of C<$recNum> is negative. | ||
1136 : | |||
1137 : | =cut | ||
1138 : | sub Assert { | ||
1139 : | my $retVal = 1; | ||
1140 : | LOOP: for my $condition (@_) { | ||
1141 : | if (! $condition) { | ||
1142 : | $retVal = 0; | ||
1143 : | last LOOP; | ||
1144 : | } | ||
1145 : | } | ||
1146 : | return $retVal; | ||
1147 : | } | ||
1148 : | |||
1149 : | olson | 1.1 | =head3 Cluck |
1150 : | |||
1151 : | C<< Cluck($message); >> | ||
1152 : | |||
1153 : | Trace the call stack. Note that for best results, you should qualify the call with a | ||
1154 : | trace condition. For example, | ||
1155 : | |||
1156 : | C<< Cluck("Starting record parse.") if T(3); >> | ||
1157 : | |||
1158 : | will only trace the stack if the trace level for the package is 3 or more. | ||
1159 : | |||
1160 : | =over 4 | ||
1161 : | |||
1162 : | =item message | ||
1163 : | |||
1164 : | Message to include in the trace. | ||
1165 : | |||
1166 : | =back | ||
1167 : | |||
1168 : | =cut | ||
1169 : | |||
1170 : | sub Cluck { | ||
1171 : | # Get the parameters. | ||
1172 : | my ($message) = @_; | ||
1173 : | parrello | 1.5 | # Trace what's happening. |
1174 : | Trace("Stack trace for event: $message"); | ||
1175 : | olson | 1.1 | my $confession = longmess($message); |
1176 : | parrello | 1.5 | # Convert the confession to a series of trace messages. Note we skip any |
1177 : | # messages relating to calls into Tracer. | ||
1178 : | olson | 1.1 | for my $line (split /\s*\n/, $confession) { |
1179 : | parrello | 1.5 | Trace($line) if ($line !~ /Tracer\.pm/); |
1180 : | olson | 1.1 | } |
1181 : | } | ||
1182 : | |||
1183 : | parrello | 1.5 | =head3 Min |
1184 : | |||
1185 : | C<< my $min = Min($value1, $value2, ... $valueN); >> | ||
1186 : | |||
1187 : | Return the minimum argument. The arguments are treated as numbers. | ||
1188 : | |||
1189 : | =over 4 | ||
1190 : | |||
1191 : | =item $value1, $value2, ... $valueN | ||
1192 : | |||
1193 : | List of numbers to compare. | ||
1194 : | |||
1195 : | =item RETURN | ||
1196 : | |||
1197 : | Returns the lowest number in the list. | ||
1198 : | |||
1199 : | =back | ||
1200 : | |||
1201 : | =cut | ||
1202 : | |||
1203 : | sub Min { | ||
1204 : | # Get the parameters. Note that we prime the return value with the first parameter. | ||
1205 : | my ($retVal, @values) = @_; | ||
1206 : | # Loop through the remaining parameters, looking for the lowest. | ||
1207 : | for my $value (@values) { | ||
1208 : | if ($value < $retVal) { | ||
1209 : | $retVal = $value; | ||
1210 : | } | ||
1211 : | } | ||
1212 : | # Return the minimum found. | ||
1213 : | return $retVal; | ||
1214 : | } | ||
1215 : | |||
1216 : | =head3 Max | ||
1217 : | |||
1218 : | C<< my $max = Max($value1, $value2, ... $valueN); >> | ||
1219 : | |||
1220 : | Return the maximum argument. The arguments are treated as numbers. | ||
1221 : | |||
1222 : | =over 4 | ||
1223 : | |||
1224 : | =item $value1, $value2, ... $valueN | ||
1225 : | |||
1226 : | List of numbers to compare. | ||
1227 : | |||
1228 : | =item RETURN | ||
1229 : | |||
1230 : | Returns the highest number in the list. | ||
1231 : | |||
1232 : | =back | ||
1233 : | |||
1234 : | =cut | ||
1235 : | |||
1236 : | sub Max { | ||
1237 : | # Get the parameters. Note that we prime the return value with the first parameter. | ||
1238 : | my ($retVal, @values) = @_; | ||
1239 : | # Loop through the remaining parameters, looking for the highest. | ||
1240 : | for my $value (@values) { | ||
1241 : | if ($value > $retVal) { | ||
1242 : | $retVal = $value; | ||
1243 : | } | ||
1244 : | } | ||
1245 : | # Return the maximum found. | ||
1246 : | return $retVal; | ||
1247 : | } | ||
1248 : | |||
1249 : | =head3 AddToListMap | ||
1250 : | |||
1251 : | C<< Tracer::AddToListMap(\%hash, $key, $value); >> | ||
1252 : | |||
1253 : | Add a key-value pair to a hash of lists. If no value exists for the key, a singleton list | ||
1254 : | is created for the key. Otherwise, the new value is pushed onto the list. | ||
1255 : | |||
1256 : | =over 4 | ||
1257 : | |||
1258 : | =item hash | ||
1259 : | |||
1260 : | Reference to the target hash. | ||
1261 : | |||
1262 : | =item key | ||
1263 : | |||
1264 : | Key for which the value is to be added. | ||
1265 : | |||
1266 : | =item value | ||
1267 : | |||
1268 : | Value to add to the key's value list. | ||
1269 : | |||
1270 : | =back | ||
1271 : | |||
1272 : | =cut | ||
1273 : | |||
1274 : | sub AddToListMap { | ||
1275 : | # Get the parameters. | ||
1276 : | my ($hash, $key, $value) = @_; | ||
1277 : | # Process according to whether or not the key already has a value. | ||
1278 : | if (! exists $hash->{$key}) { | ||
1279 : | $hash->{$key} = [$value]; | ||
1280 : | } else { | ||
1281 : | push @{$hash->{$key}}, $value; | ||
1282 : | } | ||
1283 : | } | ||
1284 : | olson | 1.1 | |
1285 : | parrello | 1.7 | =head3 DebugMode |
1286 : | |||
1287 : | C<< if (Tracer::DebugMode) { ...code... } >> | ||
1288 : | |||
1289 : | Return TRUE if debug mode has been turned on in FIG_Config, else output | ||
1290 : | an error page and return FALSE. | ||
1291 : | |||
1292 : | Certain CGI scripts are too dangerous to exist in the production | ||
1293 : | environment. This method provides a simple way to prevent them | ||
1294 : | from working unless they are explicitly turned on in the configuration | ||
1295 : | file by setting C<$FIG_Config::debug_mode> to 1. If debugging mode | ||
1296 : | is not turned on, an error web page will be output. | ||
1297 : | |||
1298 : | =cut | ||
1299 : | |||
1300 : | sub DebugMode { | ||
1301 : | # Declare the return variable. | ||
1302 : | my $retVal; | ||
1303 : | # Check the debug configuration. | ||
1304 : | if ($FIG_Config::debug_mode) { | ||
1305 : | $retVal = 1; | ||
1306 : | } else { | ||
1307 : | # Here debug mode is off, so we generate an error page. | ||
1308 : | parrello | 1.9 | my $pageString = PageBuilder::Build("<Html/ErrorPage.html", {}, "Html"); |
1309 : | print $pageString; | ||
1310 : | } | ||
1311 : | # Return the determination indicator. | ||
1312 : | return $retVal; | ||
1313 : | } | ||
1314 : | |||
1315 : | =head3 Strip | ||
1316 : | |||
1317 : | C<< my $string = Tracer::Strip($line); >> | ||
1318 : | |||
1319 : | Strip all line terminators off a string. This is necessary when dealing with files | ||
1320 : | that may have been transferred back and forth several times among different | ||
1321 : | operating environments. | ||
1322 : | |||
1323 : | =over 4 | ||
1324 : | |||
1325 : | =item line | ||
1326 : | |||
1327 : | Line of text to be stripped. | ||
1328 : | |||
1329 : | =item RETURN | ||
1330 : | |||
1331 : | The same line of text with all the line-ending characters chopped from the end. | ||
1332 : | |||
1333 : | =back | ||
1334 : | |||
1335 : | =cut | ||
1336 : | |||
1337 : | sub Strip { | ||
1338 : | # Get a copy of the parameter string. | ||
1339 : | my ($string) = @_; | ||
1340 : | my $retVal = $string; | ||
1341 : | # Strip the line terminator characters. | ||
1342 : | $retVal =~ s/(\r|\n)+$//g; | ||
1343 : | # Return the result. | ||
1344 : | return $retVal; | ||
1345 : | } | ||
1346 : | |||
1347 : | =head3 Pad | ||
1348 : | |||
1349 : | C<< my $paddedString = Tracer::Pad($string, $len, $left, $padChar); >> | ||
1350 : | |||
1351 : | Pad a string to a specified length. The pad character will be a | ||
1352 : | space, and the padding will be on the right side unless specified | ||
1353 : | in the third parameter. | ||
1354 : | |||
1355 : | =over 4 | ||
1356 : | |||
1357 : | =item string | ||
1358 : | |||
1359 : | String to be padded. | ||
1360 : | |||
1361 : | =item len | ||
1362 : | |||
1363 : | Desired length of the padded string. | ||
1364 : | |||
1365 : | =item left (optional) | ||
1366 : | |||
1367 : | TRUE if the string is to be left-padded; otherwise it will be padded on the right. | ||
1368 : | |||
1369 : | =item padChar (optional) | ||
1370 : | |||
1371 : | =item RETURN | ||
1372 : | |||
1373 : | Returns a copy of the original string with the spaces added to the specified end so | ||
1374 : | that it achieves the desired length. | ||
1375 : | |||
1376 : | =back | ||
1377 : | |||
1378 : | =cut | ||
1379 : | |||
1380 : | sub Pad { | ||
1381 : | # Get the parameters. | ||
1382 : | my ($string, $len, $left, $padChar) = @_; | ||
1383 : | # Compute the padding character. | ||
1384 : | if (! defined $padChar) { | ||
1385 : | $padChar = " "; | ||
1386 : | } | ||
1387 : | # Compute the number of spaces needed. | ||
1388 : | my $needed = $len - length $string; | ||
1389 : | # Copy the string into the return variable. | ||
1390 : | my $retVal = $string; | ||
1391 : | # Only proceed if padding is needed. | ||
1392 : | if ($needed > 0) { | ||
1393 : | # Create the pad string. | ||
1394 : | my $pad = $padChar x $needed; | ||
1395 : | # Affix it to the return value. | ||
1396 : | if ($left) { | ||
1397 : | $retVal = $pad . $retVal; | ||
1398 : | } else { | ||
1399 : | $retVal .= $pad; | ||
1400 : | redwards | 1.8 | } |
1401 : | parrello | 1.7 | } |
1402 : | parrello | 1.9 | # Return the result. |
1403 : | parrello | 1.7 | return $retVal; |
1404 : | } | ||
1405 : | |||
1406 : | redwards | 1.8 | 1; |
MCS Webmaster | ViewVC Help |
Powered by ViewVC 1.0.3 |