Parent Directory
|
Revision Log
Revision 1.129 - (view) (download) (as text)
1 : | gdpusch | 1.95 | # -*- perl -*- |
2 : | ######################################################################## | ||
3 : | olson | 1.30 | # Copyright (c) 2003-2006 University of Chicago and Fellowship |
4 : | # for Interpretations of Genomes. All Rights Reserved. | ||
5 : | # | ||
6 : | # This file is part of the SEED Toolkit. | ||
7 : | parrello | 1.61 | # |
8 : | olson | 1.30 | # The SEED Toolkit is free software. You can redistribute |
9 : | # it and/or modify it under the terms of the SEED Toolkit | ||
10 : | parrello | 1.61 | # Public License. |
11 : | olson | 1.30 | # |
12 : | # You should have received a copy of the SEED Toolkit Public License | ||
13 : | # along with this program; if not write to the University of Chicago | ||
14 : | # at info@ci.uchicago.edu or the Fellowship for Interpretation of | ||
15 : | # Genomes at veronika@thefig.info or download a copy from | ||
16 : | # http://www.theseed.org/LICENSE.TXT. | ||
17 : | gdpusch | 1.95 | ######################################################################## |
18 : | olson | 1.30 | |
19 : | olson | 1.1 | package Tracer; |
20 : | |||
21 : | parrello | 1.115 | use strict; |
22 : | use base qw(Exporter); | ||
23 : | use vars qw(@EXPORT @EXPORT_OK); | ||
24 : | parrello | 1.117 | @EXPORT = qw(Trace T TSetup QTrace Confess MemTrace Cluck Min Max Assert Open OpenDir TICK StandardSetup EmergencyKey ETracing Constrain Insure ChDir Emergency Warn TraceDump IDHASH); |
25 : | parrello | 1.97 | @EXPORT_OK = qw(GetFile GetOptions Merge MergeOptions ParseCommand ParseRecord UnEscape Escape PrintLine PutLine); |
26 : | gdpusch | 1.96 | use Carp qw(longmess croak carp); |
27 : | parrello | 1.12 | use CGI; |
28 : | parrello | 1.47 | use Cwd; |
29 : | parrello | 1.12 | use FIG_Config; |
30 : | parrello | 1.9 | use PageBuilder; |
31 : | parrello | 1.21 | use Digest::MD5; |
32 : | parrello | 1.36 | use File::Basename; |
33 : | parrello | 1.37 | use File::Path; |
34 : | parrello | 1.48 | use File::stat; |
35 : | parrello | 1.59 | use LWP::UserAgent; |
36 : | parrello | 1.64 | use Time::HiRes 'gettimeofday'; |
37 : | parrello | 1.65 | use URI::Escape; |
38 : | parrello | 1.74 | use Time::Local; |
39 : | parrello | 1.99 | use POSIX qw(strftime); |
40 : | use Time::Zone; | ||
41 : | parrello | 1.115 | use Fcntl qw(:DEFAULT :flock); |
42 : | use Data::Dumper; | ||
43 : | parrello | 1.99 | |
44 : | olson | 1.1 | |
45 : | =head1 Tracing and Debugging Helpers | ||
46 : | |||
47 : | parrello | 1.72 | =head2 Tracing |
48 : | olson | 1.1 | |
49 : | This package provides simple tracing for debugging and reporting purposes. To use it simply call the | ||
50 : | parrello | 1.72 | L</TSetup> or L</ETracing> method to set the options and call L</Trace> to write out trace messages. |
51 : | L</TSetup> and L</ETracing> both establish a I<trace level> and a list of I<categories>. Similarly, | ||
52 : | each trace message has a I<trace level> and I<category> associated with it. Only messages whose trace | ||
53 : | level is less than or equal to the setup trace level and whose category is activated will | ||
54 : | parrello | 1.2 | be written. Thus, a higher trace level on a message indicates that the message |
55 : | parrello | 1.72 | is less likely to be seen, while a higher trace level passed to B<TSetup> means more trace messages will |
56 : | appear. | ||
57 : | olson | 1.1 | |
58 : | parrello | 1.72 | =head3 Putting Trace Messages in Your Code |
59 : | |||
60 : | To generate a trace message, use the following syntax. | ||
61 : | |||
62 : | Trace($message) if T(errors => 4); | ||
63 : | olson | 1.1 | |
64 : | parrello | 1.2 | This statement will produce a trace message if the trace level is 4 or more and the C<errors> |
65 : | parrello | 1.72 | category is active. There is a special category C<main> that is always active, so |
66 : | olson | 1.1 | |
67 : | parrello | 1.72 | Trace($message) if T(main => 4); |
68 : | olson | 1.1 | |
69 : | will trace if the trace level is 4 or more. | ||
70 : | |||
71 : | If the category name is the same as the package name, all you need is the number. So, if the | ||
72 : | following call is made in the B<Sprout> package, it will appear if the C<Sprout> category is | ||
73 : | active and the trace level is 2 or more. | ||
74 : | |||
75 : | parrello | 1.72 | Trace($message) if T(2); |
76 : | |||
77 : | In scripts, where no package name is available, the category defaults to C<main>. | ||
78 : | |||
79 : | =head3 Custom Tracing | ||
80 : | |||
81 : | Many programs have customized tracing configured using the L</TSetup> method. This is no longer | ||
82 : | the preferred method, but a knowledge of how custom tracing works can make the more modern | ||
83 : | L</Emergency Tracing> easier to understand. | ||
84 : | olson | 1.1 | |
85 : | parrello | 1.72 | To set up custom tracing, you call the L</TSetup> method. The method takes as input a trace level, |
86 : | a list of category names, and a destination. The trace level and list of category names are | ||
87 : | olson | 1.1 | specified as a space-delimited string. Thus |
88 : | |||
89 : | parrello | 1.72 | TSetup('3 errors Sprout ERDB', 'TEXT'); |
90 : | olson | 1.1 | |
91 : | parrello | 1.7 | sets the trace level to 3, activates the C<errors>, C<Sprout>, and C<ERDB> categories, and |
92 : | parrello | 1.72 | specifies that messages should be sent to the standard output. |
93 : | parrello | 1.12 | |
94 : | To turn on tracing for ALL categories, use an asterisk. The call below sets every category to | ||
95 : | level 3 and writes the output to the standard error output. This sort of thing might be | ||
96 : | useful in a CGI environment. | ||
97 : | |||
98 : | parrello | 1.72 | TSetup('3 *', 'WARN'); |
99 : | olson | 1.1 | |
100 : | parrello | 1.72 | In addition standard error and file output for trace messages, you can specify that the trace messages |
101 : | olson | 1.1 | be queued. The messages can then be retrieved by calling the L</QTrace> method. This approach |
102 : | is useful if you are building a web page. Instead of having the trace messages interspersed with | ||
103 : | the page output, they can be gathered together and displayed at the end of the page. This makes | ||
104 : | it easier to debug page formatting problems. | ||
105 : | |||
106 : | parrello | 1.72 | Finally, you can specify that all trace messages be emitted to a file, or the standard output and |
107 : | a file at the same time. To trace to a file, specify the filename with an output character in front | ||
108 : | of it. | ||
109 : | |||
110 : | TSetup('4 SQL', ">$fileName"); | ||
111 : | |||
112 : | To trace to the standard output and a file at the same time, put a C<+> in front of the angle | ||
113 : | bracket. | ||
114 : | |||
115 : | TSetup('3 *', "+>$fileName"); | ||
116 : | parrello | 1.4 | |
117 : | olson | 1.1 | The flexibility of tracing makes it superior to simple use of directives like C<die> and C<warn>. |
118 : | Tracer calls can be left in the code with minimal overhead and then turned on only when needed. | ||
119 : | Thus, debugging information is available and easily retrieved even when the application is | ||
120 : | being used out in the field. | ||
121 : | |||
122 : | parrello | 1.72 | =head3 Trace Levels |
123 : | |||
124 : | parrello | 1.10 | There is no hard and fast rule on how to use trace levels. The following is therefore only |
125 : | a suggestion. | ||
126 : | |||
127 : | =over 4 | ||
128 : | |||
129 : | parrello | 1.32 | =item Error 0 |
130 : | parrello | 1.10 | |
131 : | Message indicates an error that may lead to incorrect results or that has stopped the | ||
132 : | application entirely. | ||
133 : | |||
134 : | parrello | 1.32 | =item Warning 1 |
135 : | parrello | 1.10 | |
136 : | Message indicates something that is unexpected but that probably did not interfere | ||
137 : | with program execution. | ||
138 : | |||
139 : | parrello | 1.32 | =item Notice 2 |
140 : | parrello | 1.10 | |
141 : | Message indicates the beginning or end of a major task. | ||
142 : | |||
143 : | parrello | 1.32 | =item Information 3 |
144 : | parrello | 1.10 | |
145 : | Message indicates a subtask. In the FIG system, a subtask generally relates to a single | ||
146 : | genome. This would be a big loop that is not expected to execute more than 500 times or so. | ||
147 : | |||
148 : | parrello | 1.32 | =item Detail 4 |
149 : | parrello | 1.10 | |
150 : | Message indicates a low-level loop iteration. | ||
151 : | |||
152 : | =back | ||
153 : | |||
154 : | parrello | 1.69 | The format of trace messages is important because some utilities analyze trace files. |
155 : | parrello | 1.72 | There are three fields-- the time stamp, the category name, and the text. |
156 : | The time stamp is between square brackets and the category name between angle brackets. | ||
157 : | After the category name there is a colon (C<:>) followed by the message text. | ||
158 : | If the square brackets or angle brackets are missing, then the trace management | ||
159 : | utilities assume that they are encountering a set of pre-formatted lines. | ||
160 : | |||
161 : | Note, however, that this formatting is done automatically by the tracing functions. You | ||
162 : | only need to know about it if you want to parse a trace file. | ||
163 : | |||
164 : | =head3 Emergency Tracing | ||
165 : | |||
166 : | Sometimes, you need a way for tracing to happen automatically without putting parameters | ||
167 : | in a form or on the command line. Emergency tracing does this. You invoke emergency tracing | ||
168 : | parrello | 1.97 | from the debug form, which is accessed from the [[DebugConsole]]. Emergency tracing requires |
169 : | that you specify a tracing key. For command-line tools, the key is | ||
170 : | parrello | 1.72 | taken from the C<TRACING> environment variable. For web services, the key is taken from |
171 : | a cookie. Either way, the key tells the tracing facility who you are, so that you control | ||
172 : | the tracing in your environment without stepping on other users. | ||
173 : | |||
174 : | The key can be anything you want. If you don't have a key, the C<SetPassword> page will | ||
175 : | generate one for you. | ||
176 : | |||
177 : | You can activate and de-activate emergency tracing from the debugging control panel, as | ||
178 : | well as display the trace file itself. | ||
179 : | |||
180 : | To enable emergency tracing in your code, call | ||
181 : | |||
182 : | ETracing($cgi) | ||
183 : | |||
184 : | from a web script and | ||
185 : | |||
186 : | ETracing() | ||
187 : | |||
188 : | from a command-line script. | ||
189 : | |||
190 : | The web script will look for the tracing key in the cookies, and the command-line | ||
191 : | script will look for it in the C<TRACING> environment variable. If you are | ||
192 : | parrello | 1.97 | using the L</StandardSetup> method or a [[WebApplication]], emergency tracing |
193 : | parrello | 1.72 | will be configured automatically. |
194 : | |||
195 : | olson | 1.1 | =cut |
196 : | parrello | 1.2 | |
197 : | olson | 1.1 | # Declare the configuration variables. |
198 : | |||
199 : | parrello | 1.94 | my $Destination = "WARN"; # Description of where to send the trace output. |
200 : | parrello | 1.12 | my $TeeFlag = 0; # TRUE if output is going to a file and to the |
201 : | # standard output | ||
202 : | parrello | 1.3 | my %Categories = ( main => 1 ); |
203 : | parrello | 1.12 | # hash of active category names |
204 : | parrello | 1.97 | my @LevelNames = qw(error warn notice info detail); |
205 : | parrello | 1.12 | my $TraceLevel = 0; # trace level; a higher trace level produces more |
206 : | # messages | ||
207 : | my @Queue = (); # queued list of trace messages. | ||
208 : | parrello | 1.7 | my $LastCategory = "main"; # name of the last category interrogated |
209 : | parrello | 1.97 | my $LastLevel = 0; # level of the last test call |
210 : | parrello | 1.11 | my $SetupCount = 0; # number of times TSetup called |
211 : | parrello | 1.12 | my $AllTrace = 0; # TRUE if we are tracing all categories. |
212 : | parrello | 1.99 | my $SavedCGI; # CGI object passed to ETracing |
213 : | parrello | 1.104 | my $CommandLine; # Command line passed to StandardSetup |
214 : | parrello | 1.119 | my $Confessions = 0; # confession count |
215 : | parrello | 1.99 | umask 2; # Fix the damn umask so everything is group-writable. |
216 : | olson | 1.1 | |
217 : | parrello | 1.93 | =head2 Tracing Methods |
218 : | |||
219 : | =head3 Setups | ||
220 : | |||
221 : | my $count = Tracer::Setups(); | ||
222 : | |||
223 : | Return the number of times L</TSetup> has been called. | ||
224 : | |||
225 : | This method allows for the creation of conditional tracing setups where, for example, we | ||
226 : | may want to set up tracing if nobody else has done it before us. | ||
227 : | |||
228 : | =cut | ||
229 : | |||
230 : | sub Setups { | ||
231 : | return $SetupCount; | ||
232 : | } | ||
233 : | olson | 1.1 | |
234 : | =head3 TSetup | ||
235 : | |||
236 : | parrello | 1.92 | TSetup($categoryList, $target); |
237 : | olson | 1.1 | |
238 : | This method is used to specify the trace options. The options are stored as package data | ||
239 : | and interrogated by the L</Trace> and L</T> methods. | ||
240 : | |||
241 : | =over 4 | ||
242 : | |||
243 : | =item categoryList | ||
244 : | |||
245 : | A string specifying the trace level and the categories to be traced, separated by spaces. | ||
246 : | The trace level must come first. | ||
247 : | |||
248 : | =item target | ||
249 : | |||
250 : | The destination for the trace output. To send the trace output to a file, specify the file | ||
251 : | name preceded by a ">" symbol. If a double symbol is used (">>"), then the data is appended | ||
252 : | parrello | 1.10 | to the file. Otherwise the file is cleared before tracing begins. Precede the first ">" |
253 : | symbol with a C<+> to echo output to a file AND to the standard output. In addition to | ||
254 : | sending the trace messages to a file, you can specify a special destination. C<HTML> will | ||
255 : | cause tracing to the standard output with each line formatted as an HTML paragraph. C<TEXT> | ||
256 : | parrello | 1.5 | will cause tracing to the standard output as ordinary text. C<ERROR> will cause trace |
257 : | parrello | 1.9 | messages to be sent to the standard error output as ordinary text. C<QUEUE> will cause trace |
258 : | parrello | 1.6 | messages to be stored in a queue for later retrieval by the L</QTrace> method. C<WARN> will |
259 : | parrello | 1.9 | cause trace messages to be emitted as warnings using the B<warn> directive. C<NONE> will |
260 : | parrello | 1.6 | cause tracing to be suppressed. |
261 : | olson | 1.1 | |
262 : | =back | ||
263 : | |||
264 : | =cut | ||
265 : | |||
266 : | sub TSetup { | ||
267 : | parrello | 1.12 | # Get the parameters. |
268 : | my ($categoryList, $target) = @_; | ||
269 : | # Parse the category list. | ||
270 : | my @categoryData = split /\s+/, $categoryList; | ||
271 : | # Extract the trace level. | ||
272 : | $TraceLevel = shift @categoryData; | ||
273 : | # Presume category-based tracing until we learn otherwise. | ||
274 : | $AllTrace = 0; | ||
275 : | # Build the category hash. Note that if we find a "*", we turn on non-category | ||
276 : | parrello | 1.33 | # tracing. We must also clear away any pre-existing data. |
277 : | parrello | 1.34 | %Categories = ( main => 1 ); |
278 : | parrello | 1.12 | for my $category (@categoryData) { |
279 : | if ($category eq '*') { | ||
280 : | $AllTrace = 1; | ||
281 : | } else { | ||
282 : | parrello | 1.13 | $Categories{lc $category} = 1; |
283 : | parrello | 1.12 | } |
284 : | } | ||
285 : | # Now we need to process the destination information. The most important special | ||
286 : | parrello | 1.98 | # case is when we're writing to a file. This is indicated by ">" (overwrite) and |
287 : | # ">>" (append). A leading "+" for either indicates that we are also writing to | ||
288 : | # the standard output (tee mode). | ||
289 : | parrello | 1.12 | if ($target =~ m/^\+?>>?/) { |
290 : | if ($target =~ m/^\+/) { | ||
291 : | $TeeFlag = 1; | ||
292 : | $target = substr($target, 1); | ||
293 : | } | ||
294 : | if ($target =~ m/^>[^>]/) { | ||
295 : | parrello | 1.98 | # We need to initialize the file (which clears it). |
296 : | parrello | 1.12 | open TRACEFILE, $target; |
297 : | parrello | 1.98 | print TRACEFILE "[" . Now() . "] [notice] [Tracer] Tracing initialized.\n"; |
298 : | parrello | 1.12 | close TRACEFILE; |
299 : | parrello | 1.98 | # Set to append mode now that the file has been cleared. |
300 : | parrello | 1.12 | $Destination = ">$target"; |
301 : | } else { | ||
302 : | $Destination = $target; | ||
303 : | } | ||
304 : | } else { | ||
305 : | $Destination = uc($target); | ||
306 : | } | ||
307 : | # Increment the setup counter. | ||
308 : | $SetupCount++; | ||
309 : | parrello | 1.11 | } |
310 : | |||
311 : | parrello | 1.93 | =head3 SetLevel |
312 : | parrello | 1.31 | |
313 : | parrello | 1.93 | Tracer::SetLevel($newLevel); |
314 : | parrello | 1.31 | |
315 : | parrello | 1.93 | Modify the trace level. A higher trace level will cause more messages to appear. |
316 : | parrello | 1.31 | |
317 : | parrello | 1.93 | =over 4 |
318 : | parrello | 1.31 | |
319 : | parrello | 1.93 | =item newLevel |
320 : | parrello | 1.31 | |
321 : | parrello | 1.93 | Proposed new trace level. |
322 : | parrello | 1.31 | |
323 : | parrello | 1.93 | =back |
324 : | parrello | 1.31 | |
325 : | parrello | 1.93 | =cut |
326 : | parrello | 1.31 | |
327 : | parrello | 1.93 | sub SetLevel { |
328 : | $TraceLevel = $_[0]; | ||
329 : | } | ||
330 : | parrello | 1.31 | |
331 : | parrello | 1.97 | =head3 ParseDate |
332 : | |||
333 : | my $time = Tracer::ParseDate($dateString); | ||
334 : | |||
335 : | Convert a date into a PERL time number. This method expects a date-like string | ||
336 : | and parses it into a number. The string must be vaguely date-like or it will | ||
337 : | return an undefined value. Our requirement is that a month and day be | ||
338 : | present and that three pieces of the date string (time of day, month and day, | ||
339 : | year) be separated by likely delimiters, such as spaces, commas, and such-like. | ||
340 : | |||
341 : | If a time of day is present, it must be in military time with two digits for | ||
342 : | everything but the hour. | ||
343 : | parrello | 1.31 | |
344 : | parrello | 1.97 | The year must be exactly four digits. |
345 : | parrello | 1.31 | |
346 : | parrello | 1.97 | Additional stuff can be in the string. We presume it's time zones or weekdays or something |
347 : | equally innocuous. This means, however, that a sufficiently long sentence with date-like | ||
348 : | parts in it may be interpreted as a date. Hopefully this will not be a problem. | ||
349 : | |||
350 : | It should be guaranteed that this method will parse the output of the L</Now> function. | ||
351 : | |||
352 : | The parameters are as follows. | ||
353 : | parrello | 1.31 | |
354 : | parrello | 1.93 | =over 4 |
355 : | parrello | 1.31 | |
356 : | parrello | 1.93 | =item dateString |
357 : | parrello | 1.31 | |
358 : | parrello | 1.97 | The date string to convert. |
359 : | parrello | 1.31 | |
360 : | parrello | 1.93 | =item RETURN |
361 : | parrello | 1.31 | |
362 : | parrello | 1.93 | Returns a PERL time, that is, a number of seconds since the epoch, or C<undef> if |
363 : | parrello | 1.97 | the date string is invalid. A valid date string must contain a month and day. |
364 : | parrello | 1.31 | |
365 : | parrello | 1.93 | =back |
366 : | parrello | 1.38 | |
367 : | parrello | 1.93 | =cut |
368 : | parrello | 1.38 | |
369 : | parrello | 1.97 | # Universal month conversion table. |
370 : | use constant MONTHS => { Jan => 0, January => 0, '01' => 0, '1' => 0, | ||
371 : | Feb => 1, February => 1, '02' => 1, '2' => 1, | ||
372 : | Mar => 2, March => 2, '03' => 2, '3' => 2, | ||
373 : | Apr => 3, April => 3, '04' => 3, '4' => 3, | ||
374 : | May => 4, May => 4, '05' => 4, '5' => 4, | ||
375 : | Jun => 5, June => 5, '06' => 5, '6' => 5, | ||
376 : | Jul => 6, July => 6, '07' => 6, '7' => 6, | ||
377 : | Aug => 7, August => 7, '08' => 7, '8' => 7, | ||
378 : | Sep => 8, September => 8, '09' => 8, '9' => 8, | ||
379 : | Oct => 9, October => 9, '10' => 9, | ||
380 : | Nov => 10, November => 10, '11' => 10, | ||
381 : | Dec => 11, December => 11, '12' => 11 | ||
382 : | }; | ||
383 : | |||
384 : | sub ParseDate { | ||
385 : | parrello | 1.93 | # Get the parameters. |
386 : | my ($dateString) = @_; | ||
387 : | # Declare the return variable. | ||
388 : | my $retVal; | ||
389 : | parrello | 1.97 | # Find the month and day of month. There are two ways that can happen. We check for the |
390 : | # numeric style first. That way, if the user's done something like "Sun 12/22", then we | ||
391 : | # won't be fooled into thinking the month is Sunday. | ||
392 : | if ($dateString =~ m#\b(\d{1,2})/(\d{1,2})\b# || $dateString =~ m#\b(\w+)\s(\d{1,2})\b#) { | ||
393 : | my ($mon, $mday) = (MONTHS->{$1}, $2); | ||
394 : | # Insist that the month and day are valid. | ||
395 : | if (defined($mon) && $2 >= 1 && $2 <= 31) { | ||
396 : | # Find the time. | ||
397 : | my ($hour, $min, $sec) = (0, 0, 0); | ||
398 : | if ($dateString =~ /\b(\d{1,2}):(\d{2}):(\d{2})\b/) { | ||
399 : | ($hour, $min, $sec) = ($1, $2, $3); | ||
400 : | } | ||
401 : | # Find the year. | ||
402 : | my $year; | ||
403 : | if ($dateString =~ /\b(\d{4})\b/) { | ||
404 : | $year = $1; | ||
405 : | } else { | ||
406 : | # Get the default year, which is this one. Note we must convert it to | ||
407 : | # the four-digit value expected by "timelocal". | ||
408 : | (undef, undef, undef, undef, undef, $year) = localtime(); | ||
409 : | $year += 1900; | ||
410 : | } | ||
411 : | $retVal = timelocal($sec, $min, $hour, $mday, $mon, $year); | ||
412 : | } | ||
413 : | parrello | 1.93 | } |
414 : | # Return the result. | ||
415 : | return $retVal; | ||
416 : | } | ||
417 : | parrello | 1.31 | |
418 : | parrello | 1.93 | =head3 LogErrors |
419 : | parrello | 1.42 | |
420 : | parrello | 1.93 | Tracer::LogErrors($fileName); |
421 : | parrello | 1.31 | |
422 : | parrello | 1.93 | Route the standard error output to a log file. |
423 : | parrello | 1.31 | |
424 : | parrello | 1.93 | =over 4 |
425 : | parrello | 1.31 | |
426 : | parrello | 1.93 | =item fileName |
427 : | parrello | 1.31 | |
428 : | parrello | 1.93 | Name of the file to receive the error output. |
429 : | parrello | 1.31 | |
430 : | parrello | 1.93 | =back |
431 : | parrello | 1.31 | |
432 : | parrello | 1.93 | =cut |
433 : | parrello | 1.84 | |
434 : | parrello | 1.93 | sub LogErrors { |
435 : | # Get the file name. | ||
436 : | my ($fileName) = @_; | ||
437 : | # Open the file as the standard error output. | ||
438 : | open STDERR, '>', $fileName; | ||
439 : | } | ||
440 : | parrello | 1.84 | |
441 : | parrello | 1.93 | =head3 Trace |
442 : | parrello | 1.31 | |
443 : | parrello | 1.93 | Trace($message); |
444 : | parrello | 1.31 | |
445 : | parrello | 1.93 | Write a trace message to the target location specified in L</TSetup>. If there has not been |
446 : | any prior call to B<TSetup>. | ||
447 : | parrello | 1.31 | |
448 : | parrello | 1.93 | =over 4 |
449 : | parrello | 1.42 | |
450 : | parrello | 1.93 | =item message |
451 : | parrello | 1.42 | |
452 : | parrello | 1.93 | Message to write. |
453 : | parrello | 1.42 | |
454 : | parrello | 1.93 | =back |
455 : | parrello | 1.72 | |
456 : | parrello | 1.93 | =cut |
457 : | parrello | 1.36 | |
458 : | parrello | 1.93 | sub Trace { |
459 : | # Get the parameters. | ||
460 : | my ($message) = @_; | ||
461 : | parrello | 1.97 | # Strip off any line terminators at the end of the message. We will add |
462 : | # new-line stuff ourselves. | ||
463 : | my $stripped = Strip($message); | ||
464 : | # Compute the caller information. | ||
465 : | my ($callPackage, $callFile, $callLine) = caller(); | ||
466 : | my $callFileTitle = basename($callFile); | ||
467 : | # Check the caller. | ||
468 : | parrello | 1.98 | my $callerInfo = ($callFileTitle ne "Tracer.pm" ? " [$callFileTitle $callLine]" : ""); |
469 : | parrello | 1.93 | # Get the timestamp. |
470 : | my $timeStamp = Now(); | ||
471 : | parrello | 1.97 | # Build the prefix. |
472 : | my $level = $LevelNames[$LastLevel] || "($LastLevel)"; | ||
473 : | parrello | 1.98 | my $prefix = "[$timeStamp] [$level] [$LastCategory]$callerInfo"; |
474 : | parrello | 1.97 | # Format the message. |
475 : | my $formatted = "$prefix $stripped"; | ||
476 : | parrello | 1.93 | # Process according to the destination. |
477 : | if ($Destination eq "TEXT") { | ||
478 : | # Write the message to the standard output. | ||
479 : | print "$formatted\n"; | ||
480 : | } elsif ($Destination eq "ERROR") { | ||
481 : | parrello | 1.97 | # Write the message to the error output. Here, we want our prefix fields. |
482 : | parrello | 1.93 | print STDERR "$formatted\n"; |
483 : | parrello | 1.97 | } elsif ($Destination eq "WARN") { |
484 : | # Emit the message to the standard error output. It is presumed that the | ||
485 : | parrello | 1.98 | # error logger will add its own prefix fields, the notable exception being |
486 : | # the caller info. | ||
487 : | print STDERR "$callerInfo$stripped\n"; | ||
488 : | parrello | 1.93 | } elsif ($Destination eq "QUEUE") { |
489 : | # Push the message into the queue. | ||
490 : | push @Queue, "$formatted"; | ||
491 : | } elsif ($Destination eq "HTML") { | ||
492 : | parrello | 1.116 | # Convert the message to HTML. |
493 : | parrello | 1.97 | my $escapedMessage = CGI::escapeHTML($stripped); |
494 : | parrello | 1.116 | # The stuff after the first line feed should be pre-formatted. |
495 : | my @lines = split /\s*\n/, $escapedMessage; | ||
496 : | # Get the normal portion. | ||
497 : | my $line1 = shift @lines; | ||
498 : | print "<p>$timeStamp $LastCategory $LastLevel: $line1</p>\n"; | ||
499 : | if (@lines) { | ||
500 : | parrello | 1.120 | print "<pre>" . join("\n", @lines, "</pre>"); |
501 : | parrello | 1.116 | } |
502 : | parrello | 1.93 | } elsif ($Destination =~ m/^>>/) { |
503 : | # Write the trace message to an output file. | ||
504 : | parrello | 1.97 | open(TRACING, $Destination) || die "Tracing open for \"$Destination\" failed: $!"; |
505 : | parrello | 1.111 | # Lock the file. |
506 : | flock TRACING, LOCK_EX; | ||
507 : | parrello | 1.93 | print TRACING "$formatted\n"; |
508 : | close TRACING; | ||
509 : | # If the Tee flag is on, echo it to the standard output. | ||
510 : | if ($TeeFlag) { | ||
511 : | print "$formatted\n"; | ||
512 : | } | ||
513 : | } | ||
514 : | } | ||
515 : | parrello | 1.36 | |
516 : | parrello | 1.117 | =head3 MemTrace |
517 : | |||
518 : | MemTrace($message); | ||
519 : | |||
520 : | Output a trace message that includes memory size information. | ||
521 : | |||
522 : | =over 4 | ||
523 : | |||
524 : | =item message | ||
525 : | |||
526 : | Message to display. The message will be followed by a sentence about the memory size. | ||
527 : | |||
528 : | =back | ||
529 : | |||
530 : | =cut | ||
531 : | |||
532 : | sub MemTrace { | ||
533 : | # Get the parameters. | ||
534 : | my ($message) = @_; | ||
535 : | my $memory = GetMemorySize(); | ||
536 : | Trace("$message $memory in use."); | ||
537 : | } | ||
538 : | |||
539 : | |||
540 : | parrello | 1.115 | =head3 TraceDump |
541 : | |||
542 : | TraceDump($title, $object); | ||
543 : | |||
544 : | Dump an object to the trace log. This method simply calls the C<Dumper> | ||
545 : | function, but routes the output to the trace log instead of returning it | ||
546 : | as a string. The output is arranged so that it comes out monospaced when | ||
547 : | it appears in an HTML trace dump. | ||
548 : | |||
549 : | =over 4 | ||
550 : | |||
551 : | =item title | ||
552 : | |||
553 : | Title to give to the object being dumped. | ||
554 : | |||
555 : | =item object | ||
556 : | |||
557 : | Reference to a list, hash, or object to dump. | ||
558 : | |||
559 : | =back | ||
560 : | |||
561 : | =cut | ||
562 : | |||
563 : | sub TraceDump { | ||
564 : | # Get the parameters. | ||
565 : | my ($title, $object) = @_; | ||
566 : | # Trace the object. | ||
567 : | Trace("Object dump for $title:\n" . Dumper($object)); | ||
568 : | } | ||
569 : | |||
570 : | parrello | 1.93 | =head3 T |
571 : | parrello | 1.36 | |
572 : | parrello | 1.93 | my $switch = T($category, $traceLevel); |
573 : | parrello | 1.36 | |
574 : | parrello | 1.93 | or |
575 : | parrello | 1.44 | |
576 : | parrello | 1.93 | my $switch = T($traceLevel); |
577 : | parrello | 1.44 | |
578 : | parrello | 1.93 | Return TRUE if the trace level is at or above a specified value and the specified category |
579 : | is active, else FALSE. If no category is specified, the caller's package name is used. | ||
580 : | parrello | 1.44 | |
581 : | parrello | 1.93 | =over 4 |
582 : | parrello | 1.44 | |
583 : | parrello | 1.93 | =item category |
584 : | parrello | 1.44 | |
585 : | parrello | 1.93 | Category to which the message belongs. If not specified, the caller's package name is |
586 : | used. | ||
587 : | parrello | 1.31 | |
588 : | parrello | 1.93 | =item traceLevel |
589 : | parrello | 1.31 | |
590 : | parrello | 1.93 | Relevant tracing level. |
591 : | parrello | 1.31 | |
592 : | parrello | 1.93 | =item RETURN |
593 : | parrello | 1.31 | |
594 : | parrello | 1.93 | TRUE if a message at the specified trace level would appear in the trace, else FALSE. |
595 : | parrello | 1.31 | |
596 : | parrello | 1.93 | =back |
597 : | parrello | 1.36 | |
598 : | parrello | 1.93 | =cut |
599 : | |||
600 : | sub T { | ||
601 : | # Declare the return variable. | ||
602 : | my $retVal = 0; | ||
603 : | # Only proceed if tracing is turned on. | ||
604 : | if ($Destination ne "NONE") { | ||
605 : | # Get the parameters. | ||
606 : | my ($category, $traceLevel) = @_; | ||
607 : | if (!defined $traceLevel) { | ||
608 : | # Here we have no category, so we need to get the calling package. | ||
609 : | # The calling package is normally the first parameter. If it is | ||
610 : | # omitted, the first parameter will be the tracelevel. So, the | ||
611 : | # first thing we do is shift the so-called category into the | ||
612 : | # $traceLevel variable where it belongs. | ||
613 : | $traceLevel = $category; | ||
614 : | my ($package, $fileName, $line) = caller; | ||
615 : | # If there is no calling package, we default to "main". | ||
616 : | if (!$package) { | ||
617 : | $category = "main"; | ||
618 : | } else { | ||
619 : | my @cats = split /::/, $package; | ||
620 : | $category = $cats[$#cats]; | ||
621 : | } | ||
622 : | } | ||
623 : | parrello | 1.97 | # Save the category name and level. |
624 : | parrello | 1.93 | $LastCategory = $category; |
625 : | parrello | 1.97 | $LastLevel = $traceLevel; |
626 : | parrello | 1.93 | # Convert it to lower case before we hash it. |
627 : | $category = lc $category; | ||
628 : | parrello | 1.100 | # Validate the trace level. |
629 : | parrello | 1.93 | if (ref $traceLevel) { |
630 : | Confess("Bad trace level."); | ||
631 : | } elsif (ref $TraceLevel) { | ||
632 : | Confess("Bad trace config."); | ||
633 : | } | ||
634 : | parrello | 1.100 | # Make the check. Note that level 0 shows even if the category is turned off. |
635 : | $retVal = ($traceLevel <= $TraceLevel && ($traceLevel == 0 || $AllTrace || exists $Categories{$category})); | ||
636 : | parrello | 1.93 | } |
637 : | # Return the computed result. | ||
638 : | return $retVal; | ||
639 : | } | ||
640 : | |||
641 : | =head3 QTrace | ||
642 : | |||
643 : | my $data = QTrace($format); | ||
644 : | |||
645 : | Return the queued trace data in the specified format. | ||
646 : | |||
647 : | =over 4 | ||
648 : | |||
649 : | =item format | ||
650 : | |||
651 : | C<html> to format the data as an HTML list, C<text> to format it as straight text. | ||
652 : | |||
653 : | =back | ||
654 : | |||
655 : | =cut | ||
656 : | |||
657 : | sub QTrace { | ||
658 : | # Get the parameter. | ||
659 : | my ($format) = @_; | ||
660 : | # Create the return variable. | ||
661 : | my $retVal = ""; | ||
662 : | # Only proceed if there is an actual queue. | ||
663 : | if (@Queue) { | ||
664 : | # Process according to the format. | ||
665 : | if ($format =~ m/^HTML$/i) { | ||
666 : | # Convert the queue into an HTML list. | ||
667 : | $retVal = "<ul>\n"; | ||
668 : | for my $line (@Queue) { | ||
669 : | my $escapedLine = CGI::escapeHTML($line); | ||
670 : | $retVal .= "<li>$escapedLine</li>\n"; | ||
671 : | } | ||
672 : | $retVal .= "</ul>\n"; | ||
673 : | } elsif ($format =~ m/^TEXT$/i) { | ||
674 : | # Convert the queue into a list of text lines. | ||
675 : | $retVal = join("\n", @Queue) . "\n"; | ||
676 : | } | ||
677 : | # Clear the queue. | ||
678 : | @Queue = (); | ||
679 : | } | ||
680 : | # Return the formatted list. | ||
681 : | return $retVal; | ||
682 : | } | ||
683 : | |||
684 : | =head3 Confess | ||
685 : | |||
686 : | Confess($message); | ||
687 : | |||
688 : | Trace the call stack and abort the program with the specified message. When used with | ||
689 : | the OR operator and the L</Assert> method, B<Confess> can function as a debugging assert. | ||
690 : | So, for example | ||
691 : | |||
692 : | Assert($recNum >= 0) || Confess("Invalid record number $recNum."); | ||
693 : | |||
694 : | Will abort the program with a stack trace if the value of C<$recNum> is negative. | ||
695 : | |||
696 : | =over 4 | ||
697 : | |||
698 : | =item message | ||
699 : | |||
700 : | Message to include in the trace. | ||
701 : | |||
702 : | =back | ||
703 : | |||
704 : | =cut | ||
705 : | |||
706 : | sub Confess { | ||
707 : | # Get the parameters. | ||
708 : | my ($message) = @_; | ||
709 : | parrello | 1.97 | # Set up the category and level. |
710 : | $LastCategory = "(confess)"; | ||
711 : | $LastLevel = 0; | ||
712 : | parrello | 1.93 | # Trace the call stack. |
713 : | Cluck($message); | ||
714 : | parrello | 1.119 | # Increment the confession count. |
715 : | $Confessions++; | ||
716 : | parrello | 1.93 | # Abort the program. |
717 : | croak(">>> $message"); | ||
718 : | } | ||
719 : | |||
720 : | parrello | 1.119 | =head3 Confessions |
721 : | |||
722 : | my $count = Tracer::Confessions(); | ||
723 : | |||
724 : | Return the number of calls to L</Confess> by the current task. | ||
725 : | |||
726 : | =cut | ||
727 : | |||
728 : | sub Confessions { | ||
729 : | return $Confessions; | ||
730 : | } | ||
731 : | |||
732 : | |||
733 : | parrello | 1.106 | =head3 SaveCGI |
734 : | |||
735 : | Tracer::SaveCGI($cgi); | ||
736 : | |||
737 : | This method saves the CGI object but does not activate emergency tracing. | ||
738 : | It is used to allow L</Warn> to work in situations where emergency | ||
739 : | tracing is contra-indicated (e.g. the wiki). | ||
740 : | |||
741 : | =over 4 | ||
742 : | |||
743 : | =item cgi | ||
744 : | |||
745 : | Active CGI query object. | ||
746 : | |||
747 : | =back | ||
748 : | |||
749 : | =cut | ||
750 : | |||
751 : | sub SaveCGI { | ||
752 : | $SavedCGI = $_[0]; | ||
753 : | } | ||
754 : | |||
755 : | parrello | 1.99 | =head3 Warn |
756 : | |||
757 : | parrello | 1.106 | Warn($message, @options); |
758 : | parrello | 1.99 | |
759 : | This method traces an important message. If an RSS feed is configured | ||
760 : | (via I<FIG_Config::error_feed>) and the tracing destination is C<WARN>, | ||
761 : | then the message will be echoed to the feed. In general, a tracing | ||
762 : | destination of C<WARN> indicates that the caller is running as a web | ||
763 : | service in a production environment; however, this is not a requirement. | ||
764 : | |||
765 : | parrello | 1.103 | To force warnings into the RSS feed even when the tracing destination |
766 : | is not C<WARN>, simply specify the C<Feed> tracing module. This can be | ||
767 : | configured automatically when L</StandardSetup> is used. | ||
768 : | |||
769 : | parrello | 1.99 | The L</Cluck> method calls this one for its final message. Since |
770 : | L</Confess> calls L</Cluck>, this means that any error which is caught | ||
771 : | and confessed will put something in the feed. This insures that someone | ||
772 : | will be alerted relatively quickly when a failure occurs. | ||
773 : | |||
774 : | =over 4 | ||
775 : | |||
776 : | =item message | ||
777 : | |||
778 : | Message to be traced. | ||
779 : | |||
780 : | parrello | 1.106 | =item options |
781 : | |||
782 : | A list containing zero or more options. | ||
783 : | |||
784 : | =back | ||
785 : | |||
786 : | The permissible options are as follows. | ||
787 : | |||
788 : | =over 4 | ||
789 : | |||
790 : | =item noStack | ||
791 : | |||
792 : | If specified, then the stack trace is not included in the output. | ||
793 : | |||
794 : | parrello | 1.99 | =back |
795 : | |||
796 : | =cut | ||
797 : | |||
798 : | sub Warn { | ||
799 : | # Get the parameters. | ||
800 : | parrello | 1.106 | my $message = shift @_; |
801 : | my %options = map { $_ => 1 } @_; | ||
802 : | parrello | 1.107 | # Save $@; |
803 : | my $savedError = $@; | ||
804 : | parrello | 1.99 | # Trace the message. |
805 : | Trace($message); | ||
806 : | parrello | 1.106 | # This will contain the lock handle. If it's defined, it means we need to unlock. |
807 : | my $lock; | ||
808 : | parrello | 1.103 | # Check for feed forcing. |
809 : | my $forceFeed = exists $Categories{feed}; | ||
810 : | parrello | 1.106 | # An error here would be disastrous. Note that if debug mode is specified, |
811 : | # we do this stuff even in a test environment. | ||
812 : | parrello | 1.99 | eval { |
813 : | # Do we need to put this in the RSS feed? | ||
814 : | parrello | 1.103 | if ($FIG_Config::error_feed && ($Destination eq 'WARN' || $forceFeed)) { |
815 : | parrello | 1.109 | # Probably. We need to check first, however, to see if it's from an |
816 : | # ignored IP. For non-CGI situations, we default the IP to the self-referent. | ||
817 : | my $key = "127.0.0.1"; | ||
818 : | if (defined $SavedCGI) { | ||
819 : | # Get the IP address. | ||
820 : | $key = $ENV{HTTP_X_FORWARDED_FOR} || $ENV{REMOTE_ADDR}; | ||
821 : | parrello | 1.101 | } |
822 : | parrello | 1.109 | # Is the IP address in the ignore list? |
823 : | my $found = scalar(grep { $_ eq $key } @FIG_Config::error_ignore_ips); | ||
824 : | if (! $found) { | ||
825 : | # No. We're good. We now need to compute the date, the link, and the title. | ||
826 : | # First, the date, in a very specific format. | ||
827 : | my $date = strftime("%a, %02e %b %H:%M:%S %Y ", localtime) . | ||
828 : | (tz_local_offset() / 30); | ||
829 : | # Environment data goes in here. We start with the date. | ||
830 : | my $environment = "$date. "; | ||
831 : | # If we need to recap the message (because it's too long to be a title), we'll | ||
832 : | # put it in here. | ||
833 : | my $recap; | ||
834 : | # Copy the message and remove excess space. | ||
835 : | my $title = $message; | ||
836 : | $title =~ s/\s+/ /gs; | ||
837 : | # If it's too long, we have to split it up. | ||
838 : | if (length $title > 60) { | ||
839 : | # Put the full message in the environment string. | ||
840 : | $recap = $title; | ||
841 : | # Excerpt it as the title. | ||
842 : | $title = substr($title, 0, 50) . "..."; | ||
843 : | } | ||
844 : | # If we have a CGI object, then this is a web error. Otherwise, it's | ||
845 : | # command-line. | ||
846 : | if (defined $SavedCGI) { | ||
847 : | # We're in a web service. The environment is the user's IP, and the link | ||
848 : | # is the URL that got us here. | ||
849 : | $environment .= "Event Reported at IP address $key process $$."; | ||
850 : | my $url = $SavedCGI->self_url(); | ||
851 : | # We need the user agent string and (if available) the referrer. | ||
852 : | # The referrer will be the link. | ||
853 : | $environment .= " User Agent $ENV{HTTP_USER_AGENT}"; | ||
854 : | if ($ENV{HTTP_REFERER}) { | ||
855 : | my $link = $ENV{HTTP_REFERER}; | ||
856 : | $environment .= " referred from <a href=\"$link\">$link</a>."; | ||
857 : | } else { | ||
858 : | $environment .= " referrer unknown."; | ||
859 : | } | ||
860 : | # Close off the sentence with the original link. | ||
861 : | $environment .= " URL of event is <a href=\"$url\">$url</a>."; | ||
862 : | } else { | ||
863 : | # No CGI object, so we're a command-line tool. Use the tracing | ||
864 : | # key and the PID as the user identifier, and add the command. | ||
865 : | my $key = EmergencyKey(); | ||
866 : | $environment .= "Event Reported by $key process $$."; | ||
867 : | if ($CommandLine) { | ||
868 : | # We're in a StandardSetup script, so we have the real command line. | ||
869 : | $environment .= "\n<pre>" . CGI::escapeHTML($CommandLine) . "</pre>\n"; | ||
870 : | } elsif ($ENV{_}) { | ||
871 : | # We're in a BASH script, so the command has been stored in the _ variable. | ||
872 : | $environment .= " Command = " . CGI::escapeHTML($ENV{_}) . "\n"; | ||
873 : | } | ||
874 : | } | ||
875 : | # Build a GUID. We use the current time, the title, and the process ID, | ||
876 : | # then digest the result. | ||
877 : | my $guid = Digest::MD5::md5_base64(gettimeofday(), $title, $$); | ||
878 : | # Finally, the description. This is a stack trace plus various environmental stuff. | ||
879 : | # The trace is optional. | ||
880 : | my $stackTrace; | ||
881 : | if ($options{noStack}) { | ||
882 : | $stackTrace = ""; | ||
883 : | } else { | ||
884 : | my @trace = LongMess(); | ||
885 : | # Only proceed if we got something back. | ||
886 : | if (scalar(@trace) > 0) { | ||
887 : | $trace[0] =~ s/Tracer::Warn.+?called/Event occurred/; | ||
888 : | $stackTrace = "Stack trace:<pre>" . join("\n", @trace, "</pre>"); | ||
889 : | } | ||
890 : | } | ||
891 : | # We got the stack trace. Now it's time to put it all together. | ||
892 : | # We have a goofy thing here in that we need to HTML-escape some sections of the description | ||
893 : | # twice. They will be escaped once here, and then once when written by XML::Simple. They are | ||
894 : | # unescaped once when processed by the RSS reader, and stuff in the description is treated as | ||
895 : | # HTML. So, anything escaped here is treated as a literal when viewed in the RSS reader, but | ||
896 : | # our <br>s and <pre>s are used to format the description. | ||
897 : | $recap = (defined $recap ? "<em>" . CGI::escapeHTML($recap) . "</em><br /><br />" : ""); | ||
898 : | my $description = "$recap$environment $stackTrace"; | ||
899 : | # Okay, we have all the pieces. Create a hash of the new event. | ||
900 : | my $newItem = { title => $title, | ||
901 : | description => $description, | ||
902 : | category => $LastCategory, | ||
903 : | pubDate => $date, | ||
904 : | guid => $guid, | ||
905 : | }; | ||
906 : | # We need XML capability for this. | ||
907 : | require XML::Simple; | ||
908 : | # The RSS document goes in here. | ||
909 : | my $rss; | ||
910 : | # Get the name of the RSS file. It's in the FIG temporary directory. | ||
911 : | my $fileName = "$FIG_Config::temp/$FIG_Config::error_feed"; | ||
912 : | # Open the config file and lock it. | ||
913 : | $lock = Open(undef, "<$FIG_Config::fig_disk/config/FIG_Config.pm"); | ||
914 : | flock $lock, LOCK_EX; | ||
915 : | # Does it exist? | ||
916 : | if (-s $fileName) { | ||
917 : | # Slurp it in. | ||
918 : | $rss = XML::Simple::XMLin($fileName, ForceArray => ['item']); | ||
919 : | parrello | 1.102 | } else { |
920 : | parrello | 1.109 | my $size = -s $fileName; |
921 : | # Create an empty channel. | ||
922 : | $rss = { | ||
923 : | channel => { | ||
924 : | title => 'NMPDR Warning Feed', | ||
925 : | link => "$FIG_Config::temp_url/$FIG_Config::error_feed", | ||
926 : | description => "Important messages regarding the status of the NMPDR.", | ||
927 : | generator => "NMPDR Trace Facility", | ||
928 : | docs => "http://blogs.law.harvard.edu/tech/rss", | ||
929 : | item => [] | ||
930 : | }, | ||
931 : | }; | ||
932 : | parrello | 1.100 | } |
933 : | parrello | 1.109 | # Get the channel object. |
934 : | my $channel = $rss->{channel}; | ||
935 : | # Update the last-build date. | ||
936 : | $channel->{lastBuildDate} = $date; | ||
937 : | # Get the item array. | ||
938 : | my $items = $channel->{item}; | ||
939 : | # Insure it has only 100 entries. | ||
940 : | while (scalar @{$items} > 100) { | ||
941 : | pop @{$items}; | ||
942 : | parrello | 1.104 | } |
943 : | parrello | 1.109 | # Add our new item at the front. |
944 : | unshift @{$items}, $newItem; | ||
945 : | # Create the XML. Note we do not include the root or the declaration. XML Simple can't handle | ||
946 : | # the requirements for those. | ||
947 : | my $xml = XML::Simple::XMLout($channel, NoAttr => 1, RootName => 'channel', XmlDecl => ''); | ||
948 : | # Here we put in the root and declaration. The problem is that the root has to have the version attribute | ||
949 : | # in it. So, we suppress the root and do it by hand, and that requires suppressing the declaration, too. | ||
950 : | $xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<rss version=\"2.0\">$xml\n</rss>"; | ||
951 : | # We don't use Open here because we can't afford an error. | ||
952 : | if (open XMLOUT, ">$fileName") { | ||
953 : | print XMLOUT $xml; | ||
954 : | close XMLOUT; | ||
955 : | parrello | 1.106 | } |
956 : | parrello | 1.101 | } |
957 : | parrello | 1.99 | } |
958 : | }; | ||
959 : | parrello | 1.103 | if ($@) { |
960 : | # If the feed failed, we need to know why. The error will be traced, but this method will not be involved | ||
961 : | # (which is a good thing). | ||
962 : | parrello | 1.102 | my $error = $@; |
963 : | parrello | 1.103 | Trace("Feed Error: $error") if T(Feed => 0); |
964 : | parrello | 1.102 | } |
965 : | parrello | 1.106 | # Be sure to unlock. |
966 : | if ($lock) { | ||
967 : | flock $lock, LOCK_UN; | ||
968 : | undef $lock; | ||
969 : | } | ||
970 : | parrello | 1.107 | # Restore the error message. |
971 : | $@ = $savedError; | ||
972 : | parrello | 1.99 | } |
973 : | |||
974 : | parrello | 1.106 | |
975 : | |||
976 : | |||
977 : | parrello | 1.93 | =head3 Assert |
978 : | |||
979 : | Assert($condition1, $condition2, ... $conditionN); | ||
980 : | |||
981 : | Return TRUE if all the conditions are true. This method can be used in conjunction with | ||
982 : | the OR operator and the L</Confess> method as a debugging assert. | ||
983 : | So, for example | ||
984 : | |||
985 : | Assert($recNum >= 0) || Confess("Invalid record number $recNum."); | ||
986 : | |||
987 : | Will abort the program with a stack trace if the value of C<$recNum> is negative. | ||
988 : | |||
989 : | =cut | ||
990 : | sub Assert { | ||
991 : | my $retVal = 1; | ||
992 : | LOOP: for my $condition (@_) { | ||
993 : | if (! $condition) { | ||
994 : | $retVal = 0; | ||
995 : | last LOOP; | ||
996 : | } | ||
997 : | } | ||
998 : | return $retVal; | ||
999 : | } | ||
1000 : | |||
1001 : | =head3 Cluck | ||
1002 : | |||
1003 : | Cluck($message); | ||
1004 : | |||
1005 : | Trace the call stack. Note that for best results, you should qualify the call with a | ||
1006 : | trace condition. For example, | ||
1007 : | |||
1008 : | Cluck("Starting record parse.") if T(3); | ||
1009 : | |||
1010 : | will only trace the stack if the trace level for the package is 3 or more. | ||
1011 : | |||
1012 : | =over 4 | ||
1013 : | |||
1014 : | =item message | ||
1015 : | |||
1016 : | Message to include in the trace. | ||
1017 : | |||
1018 : | =back | ||
1019 : | |||
1020 : | =cut | ||
1021 : | |||
1022 : | sub Cluck { | ||
1023 : | # Get the parameters. | ||
1024 : | my ($message) = @_; | ||
1025 : | # Trace what's happening. | ||
1026 : | Trace("Stack trace for event: $message"); | ||
1027 : | parrello | 1.99 | # Get the stack trace. |
1028 : | my @trace = LongMess(); | ||
1029 : | # Convert the trace to a series of messages. | ||
1030 : | for my $line (@trace) { | ||
1031 : | # Replace the tab at the beginning with spaces. | ||
1032 : | $line =~ s/^\t/ /; | ||
1033 : | # Trace the line. | ||
1034 : | Trace($line); | ||
1035 : | } | ||
1036 : | # Issue a warning. This displays the event message and inserts it into the RSS error feed. | ||
1037 : | Warn($message); | ||
1038 : | } | ||
1039 : | |||
1040 : | =head3 LongMess | ||
1041 : | |||
1042 : | my @lines = Tracer::LongMess(); | ||
1043 : | |||
1044 : | Return a stack trace with all tracing methods removed. The return will be in the form of a list | ||
1045 : | of message strings. | ||
1046 : | |||
1047 : | =cut | ||
1048 : | |||
1049 : | sub LongMess { | ||
1050 : | # Declare the return variable. | ||
1051 : | my @retVal = (); | ||
1052 : | my $confession = longmess(""); | ||
1053 : | parrello | 1.93 | for my $line (split /\s*\n/, $confession) { |
1054 : | parrello | 1.99 | unless ($line =~ /Tracer\.pm/) { |
1055 : | # Here we have a line worth keeping. Push it onto the result list. | ||
1056 : | push @retVal, $line; | ||
1057 : | parrello | 1.97 | } |
1058 : | parrello | 1.93 | } |
1059 : | parrello | 1.99 | # Return the result. |
1060 : | return @retVal; | ||
1061 : | parrello | 1.93 | } |
1062 : | |||
1063 : | =head3 ETracing | ||
1064 : | |||
1065 : | parrello | 1.125 | ETracing($parameter, %options); |
1066 : | parrello | 1.93 | |
1067 : | Set up emergency tracing. Emergency tracing is tracing that is turned | ||
1068 : | on automatically for any program that calls this method. The emergency | ||
1069 : | tracing parameters are stored in a a file identified by a tracing key. | ||
1070 : | If this method is called with a CGI object, then the tracing key is | ||
1071 : | taken from a cookie. If it is called with no parameters, then the tracing | ||
1072 : | key is taken from an environment variable. If it is called with a string, | ||
1073 : | the tracing key is that string. | ||
1074 : | |||
1075 : | =over 4 | ||
1076 : | |||
1077 : | =item parameter | ||
1078 : | |||
1079 : | A parameter from which the tracing key is computed. If it is a scalar, | ||
1080 : | that scalar is used as the tracing key. If it is a CGI object, the | ||
1081 : | tracing key is taken from the C<IP> cookie. If it is omitted, the | ||
1082 : | tracing key is taken from the C<TRACING> environment variable. If it | ||
1083 : | is a CGI object and emergency tracing is not on, the C<Trace> and | ||
1084 : | C<TF> parameters will be used to determine the type of tracing. | ||
1085 : | |||
1086 : | parrello | 1.125 | =item options |
1087 : | |||
1088 : | Hash of options. The permissible options are given below. | ||
1089 : | |||
1090 : | =over 8 | ||
1091 : | |||
1092 : | =item destType | ||
1093 : | |||
1094 : | Emergency tracing destination type to use if no tracing file is found. The | ||
1095 : | default is C<WARN>. | ||
1096 : | |||
1097 : | parrello | 1.124 | =item noParms |
1098 : | |||
1099 : | parrello | 1.125 | If TRUE, then display of the saved CGI parms is suppressed. The default is FALSE. |
1100 : | |||
1101 : | =item level | ||
1102 : | |||
1103 : | The trace level to use if no tracing file is found. The default is C<0>. | ||
1104 : | parrello | 1.124 | |
1105 : | parrello | 1.93 | =back |
1106 : | |||
1107 : | =cut | ||
1108 : | |||
1109 : | sub ETracing { | ||
1110 : | # Get the parameter. | ||
1111 : | parrello | 1.125 | my ($parameter, %options) = @_; |
1112 : | parrello | 1.93 | # Check for CGI mode. |
1113 : | parrello | 1.99 | if (defined $parameter && ref $parameter eq 'CGI') { |
1114 : | $SavedCGI = $parameter; | ||
1115 : | } else { | ||
1116 : | $SavedCGI = undef; | ||
1117 : | } | ||
1118 : | parrello | 1.125 | # Check for the noParms option. |
1119 : | my $noParms = $options{noParms} || 0; | ||
1120 : | # Get the default tracing information. | ||
1121 : | my $tracing = $options{level} || 0; | ||
1122 : | my $dest = $options{destType} || "WARN"; | ||
1123 : | parrello | 1.93 | # Check for emergency tracing. |
1124 : | my $tkey = EmergencyKey($parameter); | ||
1125 : | my $emergencyFile = EmergencyFileName($tkey); | ||
1126 : | parrello | 1.129 | if (-e $emergencyFile && (my $stat = stat($emergencyFile))) { |
1127 : | parrello | 1.93 | # We have the file. Read in the data. |
1128 : | my @tracing = GetFile($emergencyFile); | ||
1129 : | # Pull off the time limit. | ||
1130 : | my $expire = shift @tracing; | ||
1131 : | # Convert it to seconds. | ||
1132 : | $expire *= 3600; | ||
1133 : | # Check the file data. | ||
1134 : | my ($now) = gettimeofday; | ||
1135 : | parrello | 1.129 | if ($now - $stat->mtime <= $expire) { |
1136 : | parrello | 1.93 | # Emergency tracing is on. Pull off the destination and |
1137 : | # the trace level; | ||
1138 : | $dest = shift @tracing; | ||
1139 : | my $level = shift @tracing; | ||
1140 : | # Insure Tracer is specified. | ||
1141 : | my %moduleHash = map { $_ => 1 } @tracing; | ||
1142 : | $moduleHash{Tracer} = 1; | ||
1143 : | # Set the trace parameter. | ||
1144 : | $tracing = join(" ", $level, sort keys %moduleHash); | ||
1145 : | parrello | 1.44 | } |
1146 : | parrello | 1.36 | } |
1147 : | parrello | 1.125 | # Convert the destination to a real tracing destination. |
1148 : | $dest = EmergencyTracingDest($tkey, $dest); | ||
1149 : | parrello | 1.93 | # Setup the tracing we've determined from all the stuff above. |
1150 : | TSetup($tracing, $dest); | ||
1151 : | parrello | 1.94 | # Check to see if we're a web script. |
1152 : | parrello | 1.125 | if (defined $SavedCGI) { |
1153 : | # Yes we are. Trace the form and environment data if it's not suppressed. | ||
1154 : | if (! $noParms) { | ||
1155 : | TraceParms($SavedCGI); | ||
1156 : | } | ||
1157 : | parrello | 1.94 | # Check for RAW mode. In raw mode, we print a fake header so that we see everything |
1158 : | # emitted by the script in its raw form. | ||
1159 : | if (T(Raw => 3)) { | ||
1160 : | print CGI::header(-type => 'text/plain', -tracing => 'Raw'); | ||
1161 : | } | ||
1162 : | parrello | 1.83 | } |
1163 : | parrello | 1.31 | } |
1164 : | |||
1165 : | parrello | 1.93 | =head3 EmergencyFileName |
1166 : | |||
1167 : | my $fileName = Tracer::EmergencyFileName($tkey); | ||
1168 : | |||
1169 : | Return the emergency tracing file name. This is the file that specifies | ||
1170 : | the tracing information. | ||
1171 : | |||
1172 : | =over 4 | ||
1173 : | |||
1174 : | =item tkey | ||
1175 : | |||
1176 : | Tracing key for the current program. | ||
1177 : | parrello | 1.11 | |
1178 : | parrello | 1.93 | =item RETURN |
1179 : | parrello | 1.11 | |
1180 : | parrello | 1.93 | Returns the name of the file to contain the emergency tracing information. |
1181 : | parrello | 1.11 | |
1182 : | parrello | 1.93 | =back |
1183 : | parrello | 1.11 | |
1184 : | =cut | ||
1185 : | |||
1186 : | parrello | 1.93 | sub EmergencyFileName { |
1187 : | # Get the parameters. | ||
1188 : | my ($tkey) = @_; | ||
1189 : | # Compute the emergency tracing file name. | ||
1190 : | return "$FIG_Config::temp/Emergency$tkey.txt"; | ||
1191 : | olson | 1.1 | } |
1192 : | |||
1193 : | parrello | 1.93 | =head3 EmergencyFileTarget |
1194 : | parrello | 1.10 | |
1195 : | parrello | 1.93 | my $fileName = Tracer::EmergencyFileTarget($tkey); |
1196 : | parrello | 1.10 | |
1197 : | parrello | 1.93 | Return the emergency tracing target file name. This is the file that receives |
1198 : | the tracing output for file-based tracing. | ||
1199 : | parrello | 1.10 | |
1200 : | parrello | 1.93 | =over 4 |
1201 : | parrello | 1.10 | |
1202 : | parrello | 1.93 | =item tkey |
1203 : | parrello | 1.10 | |
1204 : | parrello | 1.93 | Tracing key for the current program. |
1205 : | parrello | 1.10 | |
1206 : | parrello | 1.93 | =item RETURN |
1207 : | parrello | 1.10 | |
1208 : | parrello | 1.93 | Returns the name of the file to contain the trace output. |
1209 : | parrello | 1.10 | |
1210 : | parrello | 1.93 | =back |
1211 : | parrello | 1.10 | |
1212 : | parrello | 1.93 | =cut |
1213 : | parrello | 1.10 | |
1214 : | parrello | 1.93 | sub EmergencyFileTarget { |
1215 : | # Get the parameters. | ||
1216 : | my ($tkey) = @_; | ||
1217 : | # Compute the emergency tracing file name. | ||
1218 : | return "$FIG_Config::temp/trace$tkey.log"; | ||
1219 : | } | ||
1220 : | parrello | 1.10 | |
1221 : | parrello | 1.93 | =head3 EmergencyTracingDest |
1222 : | parrello | 1.10 | |
1223 : | parrello | 1.93 | my $dest = Tracer::EmergencyTracingDest($tkey, $myDest); |
1224 : | parrello | 1.10 | |
1225 : | parrello | 1.93 | This method converts an emergency tracing destination to a real |
1226 : | tracing destination. The main difference is that if the | ||
1227 : | destination is C<FILE> or C<APPEND>, we convert it to file | ||
1228 : | output. If the destination is C<DUAL>, we convert it to file | ||
1229 : | and standard output. | ||
1230 : | parrello | 1.10 | |
1231 : | =over 4 | ||
1232 : | |||
1233 : | parrello | 1.93 | =item tkey |
1234 : | parrello | 1.10 | |
1235 : | parrello | 1.93 | Tracing key for this environment. |
1236 : | parrello | 1.10 | |
1237 : | parrello | 1.93 | =item myDest |
1238 : | parrello | 1.10 | |
1239 : | parrello | 1.93 | Destination from the emergency tracing file. |
1240 : | parrello | 1.10 | |
1241 : | =item RETURN | ||
1242 : | |||
1243 : | parrello | 1.93 | Returns a destination that can be passed into L</TSetup>. |
1244 : | parrello | 1.10 | |
1245 : | =back | ||
1246 : | |||
1247 : | =cut | ||
1248 : | |||
1249 : | parrello | 1.93 | sub EmergencyTracingDest { |
1250 : | parrello | 1.12 | # Get the parameters. |
1251 : | parrello | 1.93 | my ($tkey, $myDest) = @_; |
1252 : | # Declare the return variable. | ||
1253 : | my $retVal = $myDest; | ||
1254 : | # Process according to the destination value. | ||
1255 : | if ($myDest eq 'FILE') { | ||
1256 : | $retVal = ">" . EmergencyFileTarget($tkey); | ||
1257 : | } elsif ($myDest eq 'APPEND') { | ||
1258 : | $retVal = ">>" . EmergencyFileTarget($tkey); | ||
1259 : | } elsif ($myDest eq 'DUAL') { | ||
1260 : | $retVal = "+>" . EmergencyFileTarget($tkey); | ||
1261 : | parrello | 1.97 | } elsif ($myDest eq 'WARN') { |
1262 : | $retVal = "WARN"; | ||
1263 : | parrello | 1.12 | } |
1264 : | parrello | 1.93 | # Return the result. |
1265 : | return $retVal; | ||
1266 : | parrello | 1.10 | } |
1267 : | |||
1268 : | parrello | 1.93 | =head3 Emergency |
1269 : | |||
1270 : | Emergency($key, $hours, $dest, $level, @modules); | ||
1271 : | |||
1272 : | Turn on emergency tracing. This method is normally invoked over the web from | ||
1273 : | a debugging console, but it can also be called by the C<trace.pl> script. | ||
1274 : | The caller specifies the duration of the emergency in hours, the desired tracing | ||
1275 : | destination, the trace level, and a list of the trace modules to activate. | ||
1276 : | For the length of the duration, when a program in an environment with the | ||
1277 : | specified tracing key active invokes a Sprout CGI script, tracing will be | ||
1278 : | turned on automatically. See L</TSetup> for more about tracing setup and | ||
1279 : | L</ETracing> for more about emergency tracing. | ||
1280 : | |||
1281 : | =over 4 | ||
1282 : | parrello | 1.11 | |
1283 : | parrello | 1.93 | =item tkey |
1284 : | parrello | 1.11 | |
1285 : | parrello | 1.93 | The tracing key. This is used to identify the control file and the trace file. |
1286 : | parrello | 1.11 | |
1287 : | parrello | 1.93 | =item hours |
1288 : | parrello | 1.11 | |
1289 : | parrello | 1.93 | Number of hours to keep emergency tracing alive. |
1290 : | parrello | 1.11 | |
1291 : | parrello | 1.93 | =item dest |
1292 : | parrello | 1.11 | |
1293 : | parrello | 1.93 | Tracing destination. If no path information is specified for a file |
1294 : | destination, it is put in the FIG temporary directory. | ||
1295 : | parrello | 1.11 | |
1296 : | parrello | 1.93 | =item level |
1297 : | parrello | 1.11 | |
1298 : | parrello | 1.93 | Tracing level. A higher level means more trace messages. |
1299 : | parrello | 1.11 | |
1300 : | parrello | 1.93 | =item modules |
1301 : | parrello | 1.11 | |
1302 : | parrello | 1.93 | A list of the tracing modules to activate. |
1303 : | parrello | 1.11 | |
1304 : | =back | ||
1305 : | |||
1306 : | =cut | ||
1307 : | parrello | 1.93 | |
1308 : | sub Emergency { | ||
1309 : | parrello | 1.11 | # Get the parameters. |
1310 : | parrello | 1.93 | my ($tkey, $hours, $dest, $level, @modules) = @_; |
1311 : | # Create the emergency file. | ||
1312 : | my $specFile = EmergencyFileName($tkey); | ||
1313 : | my $outHandle = Open(undef, ">$specFile"); | ||
1314 : | print $outHandle join("\n", $hours, $dest, $level, @modules, ""); | ||
1315 : | parrello | 1.11 | } |
1316 : | |||
1317 : | parrello | 1.93 | =head3 EmergencyKey |
1318 : | |||
1319 : | my $tkey = EmergencyKey($parameter); | ||
1320 : | |||
1321 : | Return the Key to be used for emergency tracing. This could be an IP address, | ||
1322 : | a session ID, or a user name, depending on the environment. | ||
1323 : | parrello | 1.11 | |
1324 : | parrello | 1.93 | =over 4 |
1325 : | parrello | 1.11 | |
1326 : | parrello | 1.93 | =item parameter |
1327 : | parrello | 1.11 | |
1328 : | parrello | 1.93 | Parameter defining the method for finding the tracing key. If it is a scalar, |
1329 : | then it is presumed to be the tracing key itself. If it is a CGI object, then | ||
1330 : | the tracing key is taken from the C<IP> cookie. Otherwise, the tracing key is | ||
1331 : | taken from the C<TRACING> environment variable. | ||
1332 : | parrello | 1.29 | |
1333 : | parrello | 1.93 | =item RETURN |
1334 : | parrello | 1.11 | |
1335 : | parrello | 1.93 | Returns the key to be used for labels in emergency tracing. |
1336 : | parrello | 1.11 | |
1337 : | parrello | 1.93 | =back |
1338 : | parrello | 1.11 | |
1339 : | parrello | 1.93 | =cut |
1340 : | parrello | 1.29 | |
1341 : | parrello | 1.93 | sub EmergencyKey { |
1342 : | # Get the parameters. | ||
1343 : | my ($parameter) = @_; | ||
1344 : | # Declare the return variable. | ||
1345 : | my $retVal; | ||
1346 : | # Determine the parameter type. | ||
1347 : | parrello | 1.128 | if (! defined $parameter) { |
1348 : | parrello | 1.99 | # Here we're supposed to check the environment. If that fails, we |
1349 : | # get the effective login ID. | ||
1350 : | $retVal = $ENV{TRACING} || scalar getpwuid($<); | ||
1351 : | parrello | 1.93 | } else { |
1352 : | my $ptype = ref $parameter; | ||
1353 : | if ($ptype eq 'CGI') { | ||
1354 : | # Here we were invoked from a web page. Look for a cookie. | ||
1355 : | $retVal = $parameter->cookie('IP'); | ||
1356 : | } elsif (! $ptype) { | ||
1357 : | # Here the key was passed in. | ||
1358 : | $retVal = $parameter; | ||
1359 : | } | ||
1360 : | } | ||
1361 : | # If no luck finding a key, use the PID. | ||
1362 : | if (! defined $retVal) { | ||
1363 : | $retVal = $$; | ||
1364 : | } | ||
1365 : | # Return the result. | ||
1366 : | return $retVal; | ||
1367 : | } | ||
1368 : | parrello | 1.11 | |
1369 : | |||
1370 : | parrello | 1.93 | =head3 TraceParms |
1371 : | parrello | 1.11 | |
1372 : | parrello | 1.93 | Tracer::TraceParms($cgi); |
1373 : | parrello | 1.11 | |
1374 : | parrello | 1.93 | Trace the CGI parameters at trace level CGI => 3 and the environment variables |
1375 : | parrello | 1.94 | at level CGI => 4. A self-referencing URL is traced at level CGI => 2. |
1376 : | parrello | 1.11 | |
1377 : | parrello | 1.93 | =over 4 |
1378 : | parrello | 1.11 | |
1379 : | parrello | 1.93 | =item cgi |
1380 : | parrello | 1.31 | |
1381 : | parrello | 1.93 | CGI query object containing the parameters to trace. |
1382 : | parrello | 1.31 | |
1383 : | parrello | 1.11 | =back |
1384 : | |||
1385 : | =cut | ||
1386 : | parrello | 1.93 | |
1387 : | sub TraceParms { | ||
1388 : | parrello | 1.11 | # Get the parameters. |
1389 : | parrello | 1.93 | my ($cgi) = @_; |
1390 : | parrello | 1.94 | if (T(CGI => 2)) { |
1391 : | parrello | 1.115 | # Here we trace the GET-style URL for the script, but only if it's |
1392 : | # relatively small. | ||
1393 : | my $url = $cgi->url(-relative => 1, -query => 1); | ||
1394 : | my $len = length($url); | ||
1395 : | if ($len < 500) { | ||
1396 : | Trace("[URL] $url"); | ||
1397 : | } elsif ($len > 2048) { | ||
1398 : | Trace("[URL] URL is too long to use with GET ($len characters)."); | ||
1399 : | } else { | ||
1400 : | Trace("[URL] URL length is $len characters."); | ||
1401 : | } | ||
1402 : | parrello | 1.94 | } |
1403 : | parrello | 1.93 | if (T(CGI => 3)) { |
1404 : | # Here we want to trace the parameter data. | ||
1405 : | my @names = $cgi->param; | ||
1406 : | for my $parmName (sort @names) { | ||
1407 : | # Note we skip the Trace parameters, which are for our use only. | ||
1408 : | if ($parmName ne 'Trace' && $parmName ne 'TF') { | ||
1409 : | my @values = $cgi->param($parmName); | ||
1410 : | parrello | 1.97 | Trace("[CGI] $parmName = " . join(", ", @values)); |
1411 : | parrello | 1.93 | } |
1412 : | } | ||
1413 : | # Display the request method. | ||
1414 : | my $method = $cgi->request_method(); | ||
1415 : | Trace("Method: $method"); | ||
1416 : | } | ||
1417 : | if (T(CGI => 4)) { | ||
1418 : | # Here we want the environment data too. | ||
1419 : | for my $envName (sort keys %ENV) { | ||
1420 : | parrello | 1.97 | Trace("[ENV] $envName = $ENV{$envName}"); |
1421 : | parrello | 1.12 | } |
1422 : | } | ||
1423 : | parrello | 1.11 | } |
1424 : | |||
1425 : | parrello | 1.94 | =head3 TraceImages |
1426 : | |||
1427 : | Tracer::TraceImages($htmlString); | ||
1428 : | |||
1429 : | Trace information about all of an html document's images. The tracing | ||
1430 : | will be for type "IMG" at level 3. The image's source string | ||
1431 : | will be displayed. This is generally either the URL of the image or | ||
1432 : | raw data for the image itself. If the source is too long, only the first 300 | ||
1433 : | characters will be shown at trace level 3. The entire source will be shown, | ||
1434 : | however, at trace level 4. This method is not very smart, and might catch | ||
1435 : | Javascript code, but it is still useful when debugging the arcane | ||
1436 : | behavior of images in multiple browser environments. | ||
1437 : | |||
1438 : | =over 4 | ||
1439 : | |||
1440 : | =item htmlString | ||
1441 : | |||
1442 : | HTML text for an outgoing web page. | ||
1443 : | |||
1444 : | =back | ||
1445 : | |||
1446 : | =cut | ||
1447 : | |||
1448 : | sub TraceImages { | ||
1449 : | # Only proceed if we're at the proper trace level. | ||
1450 : | if (T(IMG => 3)) { | ||
1451 : | # For performance reasons we're manipulating $_[0] instead of retrieving the string | ||
1452 : | # into a variable called "$htmlString". This is because we expect html strings to be | ||
1453 : | # long, and don't want to copy them any more than we have to. | ||
1454 : | Trace(length($_[0]) . " characters in web page."); | ||
1455 : | # Loop through the HTML, culling image tags. | ||
1456 : | while ($_[0] =~ /<img\s+[^>]+?src="([^"]+)"/sgi) { | ||
1457 : | # Extract the source string and determine whether or not it's too long. | ||
1458 : | my $srcString = $1; | ||
1459 : | my $pos = pos($_[0]) - length($srcString); | ||
1460 : | my $excess = length($srcString) - 300; | ||
1461 : | # We'll put the display string in here. | ||
1462 : | my $srcDisplay = $srcString; | ||
1463 : | # If it's a data string, split it at the comma. | ||
1464 : | $srcDisplay =~ s/^(data[^,]+,)/$1\n/; | ||
1465 : | # If there's no excess or we're at trace level 4, we're done. At level 3 with | ||
1466 : | # a long string, however, we only show the first 300 characters. | ||
1467 : | if ($excess > 0 && ! T(IMG => 4)) { | ||
1468 : | $srcDisplay = substr($srcDisplay,0,300) . "\nplus $excess characters."; | ||
1469 : | } | ||
1470 : | # Output the trace message. | ||
1471 : | Trace("Image tag at position $pos:\n$srcDisplay"); | ||
1472 : | } | ||
1473 : | } | ||
1474 : | } | ||
1475 : | |||
1476 : | parrello | 1.93 | =head2 Command-Line Utility Methods |
1477 : | olson | 1.1 | |
1478 : | parrello | 1.93 | =head3 SendSMS |
1479 : | olson | 1.1 | |
1480 : | parrello | 1.93 | my $msgID = Tracer::SendSMS($phoneNumber, $msg); |
1481 : | olson | 1.1 | |
1482 : | parrello | 1.93 | Send a text message to a phone number using Clickatell. The FIG_Config file must contain the |
1483 : | user name, password, and API ID for the relevant account in the hash reference variable | ||
1484 : | I<$FIG_Config::phone>, using the keys C<user>, C<password>, and C<api_id>. For | ||
1485 : | example, if the user name is C<BruceTheHumanPet>, the password is C<silly>, and the API ID | ||
1486 : | is C<2561022>, then the FIG_Config file must contain | ||
1487 : | olson | 1.1 | |
1488 : | parrello | 1.93 | $phone = { user => 'BruceTheHumanPet', |
1489 : | password => 'silly', | ||
1490 : | api_id => '2561022' }; | ||
1491 : | olson | 1.1 | |
1492 : | parrello | 1.93 | The original purpose of this method was to insure Bruce would be notified immediately when the |
1493 : | Sprout Load terminates. Care should be taken if you do not wish Bruce to be notified immediately | ||
1494 : | when you call this method. | ||
1495 : | olson | 1.1 | |
1496 : | parrello | 1.93 | The message ID will be returned if successful, and C<undef> if an error occurs. |
1497 : | parrello | 1.74 | |
1498 : | parrello | 1.93 | =over 4 |
1499 : | parrello | 1.74 | |
1500 : | parrello | 1.93 | =item phoneNumber |
1501 : | parrello | 1.74 | |
1502 : | parrello | 1.93 | Phone number to receive the message, in international format. A United States phone number |
1503 : | would be prefixed by "1". A British phone number would be prefixed by "44". | ||
1504 : | parrello | 1.74 | |
1505 : | parrello | 1.93 | =item msg |
1506 : | parrello | 1.74 | |
1507 : | parrello | 1.93 | Message to send to the specified phone. |
1508 : | parrello | 1.74 | |
1509 : | =item RETURN | ||
1510 : | |||
1511 : | parrello | 1.93 | Returns the message ID if successful, and C<undef> if the message could not be sent. |
1512 : | parrello | 1.74 | |
1513 : | parrello | 1.78 | =back |
1514 : | |||
1515 : | parrello | 1.74 | =cut |
1516 : | |||
1517 : | parrello | 1.93 | sub SendSMS { |
1518 : | parrello | 1.74 | # Get the parameters. |
1519 : | parrello | 1.93 | my ($phoneNumber, $msg) = @_; |
1520 : | # Declare the return variable. If we do not change it, C<undef> will be returned. | ||
1521 : | parrello | 1.74 | my $retVal; |
1522 : | parrello | 1.93 | # Only proceed if we have phone support. |
1523 : | if (! defined $FIG_Config::phone) { | ||
1524 : | Trace("Phone support not present in FIG_Config.") if T(1); | ||
1525 : | } else { | ||
1526 : | # Get the phone data. | ||
1527 : | my $parms = $FIG_Config::phone; | ||
1528 : | # Get the Clickatell URL. | ||
1529 : | my $url = "http://api.clickatell.com/http/"; | ||
1530 : | # Create the user agent. | ||
1531 : | my $ua = LWP::UserAgent->new; | ||
1532 : | # Request a Clickatell session. | ||
1533 : | my $resp = $ua->post("$url/sendmsg", { user => $parms->{user}, | ||
1534 : | password => $parms->{password}, | ||
1535 : | api_id => $parms->{api_id}, | ||
1536 : | to => $phoneNumber, | ||
1537 : | text => $msg}); | ||
1538 : | # Check for an error. | ||
1539 : | if (! $resp->is_success) { | ||
1540 : | Trace("Alert failed.") if T(1); | ||
1541 : | } else { | ||
1542 : | # Get the message ID. | ||
1543 : | my $rstring = $resp->content; | ||
1544 : | if ($rstring =~ /^ID:\s+(.*)$/) { | ||
1545 : | $retVal = $1; | ||
1546 : | } else { | ||
1547 : | Trace("Phone attempt failed with $rstring") if T(1); | ||
1548 : | } | ||
1549 : | } | ||
1550 : | parrello | 1.74 | } |
1551 : | # Return the result. | ||
1552 : | return $retVal; | ||
1553 : | } | ||
1554 : | |||
1555 : | parrello | 1.93 | =head3 StandardSetup |
1556 : | olson | 1.1 | |
1557 : | parrello | 1.93 | my ($options, @parameters) = StandardSetup(\@categories, \%options, $parmHelp, @ARGV); |
1558 : | olson | 1.1 | |
1559 : | parrello | 1.93 | This method performs standard command-line parsing and tracing setup. The return |
1560 : | values are a hash of the command-line options and a list of the positional | ||
1561 : | parameters. Tracing is automatically set up and the command-line options are | ||
1562 : | validated. | ||
1563 : | olson | 1.1 | |
1564 : | parrello | 1.93 | This is a complex method that does a lot of grunt work. The parameters can |
1565 : | be more easily understood, however, once they are examined individually. | ||
1566 : | olson | 1.1 | |
1567 : | parrello | 1.93 | The I<categories> parameter is the most obtuse. It is a reference to a list of |
1568 : | special-purpose tracing categories. Most tracing categories are PERL package | ||
1569 : | names. So, for example, if you wanted to turn on tracing inside the B<Sprout>, | ||
1570 : | B<ERDB>, and B<SproutLoad> packages, you would specify the categories | ||
1571 : | olson | 1.1 | |
1572 : | parrello | 1.93 | ["Sprout", "SproutLoad", "ERDB"] |
1573 : | olson | 1.1 | |
1574 : | parrello | 1.93 | This would cause trace messages in the specified three packages to appear in |
1575 : | the output. There are two special tracing categories that are automatically | ||
1576 : | handled by this method. In other words, if you used L</TSetup> you would need | ||
1577 : | to include these categories manually, but if you use this method they are turned | ||
1578 : | on automatically. | ||
1579 : | parrello | 1.5 | |
1580 : | =over 4 | ||
1581 : | |||
1582 : | parrello | 1.93 | =item SQL |
1583 : | parrello | 1.5 | |
1584 : | parrello | 1.93 | Traces SQL commands and activity. |
1585 : | parrello | 1.5 | |
1586 : | parrello | 1.93 | =item Tracer |
1587 : | parrello | 1.5 | |
1588 : | parrello | 1.93 | Traces error messages and call stacks. |
1589 : | parrello | 1.5 | |
1590 : | =back | ||
1591 : | |||
1592 : | parrello | 1.93 | C<SQL> is only turned on if the C<-sql> option is specified in the command line. |
1593 : | The trace level is specified using the C<-trace> command-line option. For example, | ||
1594 : | the following command line for C<TransactFeatures> turns on SQL tracing and runs | ||
1595 : | all tracing at level 3. | ||
1596 : | parrello | 1.5 | |
1597 : | parrello | 1.93 | TransactFeatures -trace=3 -sql register ../xacts IDs.tbl |
1598 : | parrello | 1.5 | |
1599 : | parrello | 1.93 | Standard tracing is output to the standard output and echoed to the file |
1600 : | C<trace>I<$$>C<.log> in the FIG temporary directory, where I<$$> is the | ||
1601 : | process ID. You can also specify the C<user> parameter to put a user ID | ||
1602 : | instead of a process ID in the trace file name. So, for example | ||
1603 : | olson | 1.1 | |
1604 : | parrello | 1.93 | The default trace level is 2. To get all messages, specify a trace level of 4. |
1605 : | For a genome-by-genome update, use 3. | ||
1606 : | olson | 1.1 | |
1607 : | parrello | 1.93 | TransactFeatures -trace=3 -sql -user=Bruce register ../xacts IDs.tbl |
1608 : | olson | 1.1 | |
1609 : | parrello | 1.93 | would send the trace output to C<traceBruce.log> in the temporary directory. |
1610 : | olson | 1.1 | |
1611 : | parrello | 1.93 | The I<options> parameter is a reference to a hash containing the command-line |
1612 : | options, their default values, and an explanation of what they mean. Command-line | ||
1613 : | options may be in the form of switches or keywords. In the case of a switch, the | ||
1614 : | option value is 1 if it is specified and 0 if it is not specified. In the case | ||
1615 : | of a keyword, the value is separated from the option name by an equal sign. You | ||
1616 : | can see this last in the command-line example above. | ||
1617 : | olson | 1.1 | |
1618 : | parrello | 1.93 | You can specify a different default trace level by setting C<$options->{trace}> |
1619 : | prior to calling this method. | ||
1620 : | olson | 1.1 | |
1621 : | parrello | 1.93 | An example at this point would help. Consider, for example, the command-line utility |
1622 : | C<TransactFeatures>. It accepts a list of positional parameters plus the options | ||
1623 : | C<safe>, C<noAlias>, C<start>, and C<tblFiles>. To start up this command, we execute | ||
1624 : | the following code. | ||
1625 : | olson | 1.1 | |
1626 : | parrello | 1.93 | my ($options, @parameters) = Tracer::StandardSetup(["DocUtils"], |
1627 : | { safe => [0, "use database transactions"], | ||
1628 : | noAlias => [0, "do not expect aliases in CHANGE transactions"], | ||
1629 : | start => [' ', "start with this genome"], | ||
1630 : | tblFiles => [0, "output TBL files containing the corrected IDs"] }, | ||
1631 : | "<command> <transactionDirectory> <IDfile>", | ||
1632 : | @ARGV); | ||
1633 : | olson | 1.1 | |
1634 : | |||
1635 : | parrello | 1.93 | The call to C<ParseCommand> specifies the default values for the options and |
1636 : | stores the actual options in a hash that is returned as C<$options>. The | ||
1637 : | positional parameters are returned in C<@parameters>. | ||
1638 : | olson | 1.1 | |
1639 : | parrello | 1.93 | The following is a sample command line for C<TransactFeatures>. |
1640 : | olson | 1.1 | |
1641 : | parrello | 1.93 | TransactFeatures -trace=2 -noAlias register ../xacts IDs.tbl |
1642 : | olson | 1.1 | |
1643 : | parrello | 1.93 | Single and double hyphens are equivalent. So, you could also code the |
1644 : | above command as | ||
1645 : | olson | 1.1 | |
1646 : | parrello | 1.93 | TransactFeatures --trace=2 --noAlias register ../xacts IDs.tbl |
1647 : | olson | 1.1 | |
1648 : | parrello | 1.93 | In this case, C<register>, C<../xacts>, and C<IDs.tbl> are the positional |
1649 : | parameters, and would find themselves in I<@parameters> after executing the | ||
1650 : | above code fragment. The tracing would be set to level 2, and the categories | ||
1651 : | would be C<Tracer>, and <DocUtils>. C<Tracer> is standard, | ||
1652 : | and C<DocUtils> was included because it came in within the first parameter | ||
1653 : | to this method. The I<$options> hash would be | ||
1654 : | olson | 1.1 | |
1655 : | parrello | 1.93 | { trace => 2, sql => 0, safe => 0, |
1656 : | noAlias => 1, start => ' ', tblFiles => 0 } | ||
1657 : | olson | 1.1 | |
1658 : | parrello | 1.93 | Use of C<StandardSetup> in this way provides a simple way of performing |
1659 : | standard tracing setup and command-line parsing. Note that the caller is | ||
1660 : | not even aware of the command-line switches C<-trace> and C<-sql>, which | ||
1661 : | are used by this method to control the tracing. If additional tracing features | ||
1662 : | need to be added in the future, they can be processed by this method without | ||
1663 : | upsetting the command-line utilities. | ||
1664 : | olson | 1.1 | |
1665 : | parrello | 1.93 | If the C<background> option is specified on the command line, then the |
1666 : | standard and error outputs will be directed to files in the temporary | ||
1667 : | directory, using the same suffix as the trace file. So, if the command | ||
1668 : | line specified | ||
1669 : | olson | 1.1 | |
1670 : | parrello | 1.93 | -user=Bruce -background |
1671 : | olson | 1.1 | |
1672 : | parrello | 1.93 | then the trace output would go to C<traceBruce.log>, the standard output to |
1673 : | C<outBruce.log>, and the error output to C<errBruce.log>. This is designed to | ||
1674 : | simplify starting a command in the background. | ||
1675 : | olson | 1.1 | |
1676 : | parrello | 1.93 | The user name is also used as the tracing key for L</Emergency Tracing>. |
1677 : | Specifying a value of C<E> for the trace level causes emergency tracing to | ||
1678 : | be used instead of custom tracing. If the user name is not specified, | ||
1679 : | the tracing key is taken from the C<Tracing> environment variable. If there | ||
1680 : | parrello | 1.103 | is no value for that variable, the tracing key will be computed from the active |
1681 : | login ID. | ||
1682 : | |||
1683 : | Since the default situation in StandardSetup is to trace to the standard | ||
1684 : | output, errors that occur in command-line scripts will not generate | ||
1685 : | RSS events. To force the events, use the C<warn> option. | ||
1686 : | |||
1687 : | TransactFeatures -background -warn register ../xacts IDs.tbl | ||
1688 : | olson | 1.1 | |
1689 : | parrello | 1.93 | Finally, if the special option C<-help> is specified, the option |
1690 : | names will be traced at level 0 and the program will exit without processing. | ||
1691 : | This provides a limited help capability. For example, if the user enters | ||
1692 : | olson | 1.1 | |
1693 : | parrello | 1.93 | TransactFeatures -help |
1694 : | olson | 1.1 | |
1695 : | parrello | 1.93 | he would see the following output. |
1696 : | olson | 1.1 | |
1697 : | parrello | 1.93 | TransactFeatures [options] <command> <transactionDirectory> <IDfile> |
1698 : | -trace tracing level (default E) | ||
1699 : | -sql trace SQL commands | ||
1700 : | -safe use database transactions | ||
1701 : | -noAlias do not expect aliases in CHANGE transactions | ||
1702 : | -start start with this genome | ||
1703 : | -tblFiles output TBL files containing the corrected IDs | ||
1704 : | parrello | 1.111 | -forked do not erase the trace file before tracing |
1705 : | olson | 1.1 | |
1706 : | parrello | 1.93 | The caller has the option of modifying the tracing scheme by placing a value |
1707 : | for C<trace> in the incoming options hash. The default value can be overridden, | ||
1708 : | or the tracing to the standard output can be turned off by suffixing a minus | ||
1709 : | sign to the trace level. So, for example, | ||
1710 : | olson | 1.1 | |
1711 : | parrello | 1.93 | { trace => [0, "tracing level (default 0)"], |
1712 : | ... | ||
1713 : | olson | 1.1 | |
1714 : | parrello | 1.93 | would set the default trace level to 0 instead of E, while |
1715 : | olson | 1.1 | |
1716 : | parrello | 1.93 | { trace => ["2-", "tracing level (default 2)"], |
1717 : | ... | ||
1718 : | olson | 1.1 | |
1719 : | parrello | 1.93 | would set the default to 2, but trace only to the log file, not to the |
1720 : | standard output. | ||
1721 : | olson | 1.1 | |
1722 : | parrello | 1.93 | The parameters to this method are as follows. |
1723 : | olson | 1.1 | |
1724 : | parrello | 1.93 | =over 4 |
1725 : | olson | 1.1 | |
1726 : | parrello | 1.93 | =item categories |
1727 : | parrello | 1.2 | |
1728 : | parrello | 1.93 | Reference to a list of tracing category names. These should be names of |
1729 : | packages whose internal workings will need to be debugged to get the | ||
1730 : | command working. | ||
1731 : | olson | 1.1 | |
1732 : | parrello | 1.93 | =item options |
1733 : | olson | 1.1 | |
1734 : | parrello | 1.93 | Reference to a hash containing the legal options for the current command mapped |
1735 : | to their default values and descriptions. The user can override the defaults | ||
1736 : | by specifying the options as command-line switches prefixed by a hyphen. | ||
1737 : | Tracing-related options may be added to this hash. If the C<-h> option is | ||
1738 : | specified on the command line, the option descriptions will be used to | ||
1739 : | explain the options. To turn off tracing to the standard output, add a | ||
1740 : | minus sign to the value for C<trace> (see above). | ||
1741 : | olson | 1.1 | |
1742 : | parrello | 1.93 | =item parmHelp |
1743 : | olson | 1.1 | |
1744 : | parrello | 1.93 | A string that vaguely describes the positional parameters. This is used |
1745 : | if the user specifies the C<-h> option. | ||
1746 : | olson | 1.1 | |
1747 : | parrello | 1.93 | =item argv |
1748 : | olson | 1.1 | |
1749 : | parrello | 1.93 | List of command line parameters, including the option switches, which must |
1750 : | precede the positional parameters and be prefixed by a hyphen. | ||
1751 : | olson | 1.1 | |
1752 : | =item RETURN | ||
1753 : | |||
1754 : | parrello | 1.93 | Returns a list. The first element of the list is the reference to a hash that |
1755 : | maps the command-line option switches to their values. These will either be the | ||
1756 : | default values or overrides specified on the command line. The remaining | ||
1757 : | elements of the list are the position parameters, in order. | ||
1758 : | olson | 1.1 | |
1759 : | =back | ||
1760 : | |||
1761 : | =cut | ||
1762 : | |||
1763 : | parrello | 1.93 | sub StandardSetup { |
1764 : | # Get the parameters. | ||
1765 : | my ($categories, $options, $parmHelp, @argv) = @_; | ||
1766 : | # Get the default tracing key. | ||
1767 : | my $tkey = EmergencyKey(); | ||
1768 : | parrello | 1.104 | # Save the command line. |
1769 : | $CommandLine = join(" ", $0, map { $_ =~ /\s/ ? "\"$_\"" : $_ } @argv); | ||
1770 : | parrello | 1.93 | # Add the tracing options. |
1771 : | if (! exists $options->{trace}) { | ||
1772 : | $options->{trace} = ['2', "tracing level (E for emergency tracing)"]; | ||
1773 : | } | ||
1774 : | parrello | 1.112 | if (! exists $options->{forked}) { |
1775 : | $options->{forked} = [0, "keep old trace file"]; | ||
1776 : | } | ||
1777 : | parrello | 1.93 | $options->{sql} = [0, "turn on SQL tracing"]; |
1778 : | $options->{help} = [0, "display command-line options"]; | ||
1779 : | $options->{user} = [$tkey, "tracing key"]; | ||
1780 : | $options->{background} = [0, "spool standard and error output"]; | ||
1781 : | parrello | 1.103 | $options->{warn} = [0, "send errors to RSS feed"]; |
1782 : | parrello | 1.113 | $options->{moreTracing} = ["", "comma-delimited list of additional trace modules for debugging"]; |
1783 : | parrello | 1.93 | # Create a parsing hash from the options hash. The parsing hash |
1784 : | # contains the default values rather than the default value | ||
1785 : | # and the description. While we're at it, we'll memorize the | ||
1786 : | # length of the longest option name. | ||
1787 : | my $longestName = 0; | ||
1788 : | my %parseOptions = (); | ||
1789 : | for my $key (keys %{$options}) { | ||
1790 : | if (length $key > $longestName) { | ||
1791 : | $longestName = length $key; | ||
1792 : | } | ||
1793 : | $parseOptions{$key} = $options->{$key}->[0]; | ||
1794 : | } | ||
1795 : | # Parse the command line. | ||
1796 : | my ($retOptions, @retParameters) = ParseCommand(\%parseOptions, @argv); | ||
1797 : | # Get the logfile suffix. | ||
1798 : | my $suffix = $retOptions->{user}; | ||
1799 : | parrello | 1.113 | # We'll put the trace file name in here. We need it later if background |
1800 : | # mode is on. | ||
1801 : | my $traceFileName; | ||
1802 : | parrello | 1.93 | # Now we want to set up tracing. First, we need to know if the user |
1803 : | # wants emergency tracing. | ||
1804 : | if ($retOptions->{trace} eq 'E') { | ||
1805 : | ETracing($retOptions->{user}); | ||
1806 : | } else { | ||
1807 : | # Here the tracing is controlled from the command line. | ||
1808 : | my @cats = @{$categories}; | ||
1809 : | if ($retOptions->{sql}) { | ||
1810 : | push @cats, "SQL"; | ||
1811 : | } | ||
1812 : | parrello | 1.103 | if ($retOptions->{warn}) { |
1813 : | push @cats, "Feed"; | ||
1814 : | } | ||
1815 : | parrello | 1.93 | # Add the default categories. |
1816 : | push @cats, "Tracer"; | ||
1817 : | parrello | 1.113 | # Check for more tracing groups. |
1818 : | if ($retOptions->{moreTracing}) { | ||
1819 : | push @cats, split /,/, $retOptions->{moreTracing}; | ||
1820 : | } | ||
1821 : | parrello | 1.93 | # Next, we create the category string by joining the categories. |
1822 : | my $cats = join(" ", @cats); | ||
1823 : | # Check to determine whether or not the caller wants to turn off tracing | ||
1824 : | # to the standard output. | ||
1825 : | my $traceLevel = $retOptions->{trace}; | ||
1826 : | my $textOKFlag = 1; | ||
1827 : | if ($traceLevel =~ /^(.)-/) { | ||
1828 : | $traceLevel = $1; | ||
1829 : | $textOKFlag = 0; | ||
1830 : | } | ||
1831 : | # Now we set up the trace mode. | ||
1832 : | my $traceMode; | ||
1833 : | # Verify that we can open a file in the FIG temporary directory. | ||
1834 : | my $traceFileName = "$FIG_Config::temp/trace$suffix.log"; | ||
1835 : | parrello | 1.111 | my $traceFileSpec = ($retOptions->{forked} ? ">>$traceFileName" : ">$traceFileName"); |
1836 : | if (open TESTTRACE, "$traceFileSpec") { | ||
1837 : | parrello | 1.93 | # Here we can trace to a file. |
1838 : | parrello | 1.111 | $traceMode = ">>$traceFileName"; |
1839 : | parrello | 1.93 | if ($textOKFlag) { |
1840 : | # Echo to standard output if the text-OK flag is set. | ||
1841 : | $traceMode = "+$traceMode"; | ||
1842 : | } | ||
1843 : | # Close the test file. | ||
1844 : | close TESTTRACE; | ||
1845 : | } else { | ||
1846 : | parrello | 1.98 | # Here we can't trace to a file. Complain about this. |
1847 : | warn "Could not open trace file $traceFileName: $!\n"; | ||
1848 : | # We trace to the standard output if it's | ||
1849 : | # okay, and the error log otherwise. | ||
1850 : | parrello | 1.93 | if ($textOKFlag) { |
1851 : | $traceMode = "TEXT"; | ||
1852 : | parrello | 1.12 | } else { |
1853 : | parrello | 1.93 | $traceMode = "WARN"; |
1854 : | parrello | 1.12 | } |
1855 : | } | ||
1856 : | parrello | 1.93 | # Now set up the tracing. |
1857 : | TSetup("$traceLevel $cats", $traceMode); | ||
1858 : | } | ||
1859 : | parrello | 1.113 | # Check for background mode. |
1860 : | if ($retOptions->{background}) { | ||
1861 : | my $outFileName = "$FIG_Config::temp/out$suffix$$.log"; | ||
1862 : | my $errFileName = "$FIG_Config::temp/err$suffix$$.log"; | ||
1863 : | # Spool the output. | ||
1864 : | open STDOUT, ">$outFileName"; | ||
1865 : | # If we have a trace file, trace the errors to the log. Otherwise, | ||
1866 : | # spool the errors. | ||
1867 : | if (defined $traceFileName) { | ||
1868 : | open STDERR, "| Tracer $traceFileName"; | ||
1869 : | } else { | ||
1870 : | open STDERR, ">$errFileName"; | ||
1871 : | } | ||
1872 : | # Check for phone support. If we have phone support and a phone number, | ||
1873 : | # we want to turn it on. | ||
1874 : | if ($ENV{PHONE} && defined($FIG_Config::phone)) { | ||
1875 : | $retOptions->{phone} = $ENV{PHONE}; | ||
1876 : | } | ||
1877 : | } | ||
1878 : | parrello | 1.93 | # Check for the "help" option. If it is specified, dump the command-line |
1879 : | # options and exit the program. | ||
1880 : | if ($retOptions->{help}) { | ||
1881 : | $0 =~ m#[/\\](\w+)(\.pl)?$#i; | ||
1882 : | print "$1 [options] $parmHelp\n"; | ||
1883 : | for my $key (sort keys %{$options}) { | ||
1884 : | my $name = Pad($key, $longestName, 0, ' '); | ||
1885 : | my $desc = $options->{$key}->[1]; | ||
1886 : | if ($options->{$key}->[0]) { | ||
1887 : | $desc .= " (default " . $options->{$key}->[0] . ")"; | ||
1888 : | } | ||
1889 : | print " $name $desc\n"; | ||
1890 : | parrello | 1.36 | } |
1891 : | parrello | 1.93 | exit(0); |
1892 : | } | ||
1893 : | # Trace the options, if applicable. | ||
1894 : | if (T(3)) { | ||
1895 : | my @parms = grep { $retOptions->{$_} } keys %{$retOptions}; | ||
1896 : | Trace("Selected options: " . join(", ", sort @parms) . "."); | ||
1897 : | parrello | 1.3 | } |
1898 : | parrello | 1.93 | # Return the parsed parameters. |
1899 : | return ($retOptions, @retParameters); | ||
1900 : | olson | 1.1 | } |
1901 : | |||
1902 : | parrello | 1.93 | =head3 ReadOptions |
1903 : | olson | 1.1 | |
1904 : | parrello | 1.93 | my %options = Tracer::ReadOptions($fileName); |
1905 : | olson | 1.1 | |
1906 : | parrello | 1.93 | Read a set of options from a file. Each option is encoded in a line of text that has the |
1907 : | format | ||
1908 : | olson | 1.1 | |
1909 : | parrello | 1.93 | I<optionName>C<=>I<optionValue>C<; >I<comment> |
1910 : | olson | 1.1 | |
1911 : | parrello | 1.93 | The option name must consist entirely of letters, digits, and the punctuation characters |
1912 : | C<.> and C<_>, and is case sensitive. Blank lines and lines in which the first nonblank | ||
1913 : | character is a semi-colon will be ignored. The return hash will map each option name to | ||
1914 : | the corresponding option value. | ||
1915 : | olson | 1.1 | |
1916 : | =over 4 | ||
1917 : | |||
1918 : | parrello | 1.93 | =item fileName |
1919 : | olson | 1.1 | |
1920 : | parrello | 1.93 | Name of the file containing the option data. |
1921 : | olson | 1.1 | |
1922 : | =item RETURN | ||
1923 : | |||
1924 : | parrello | 1.93 | Returns a hash mapping the option names specified in the file to their corresponding option |
1925 : | value. | ||
1926 : | olson | 1.1 | |
1927 : | =back | ||
1928 : | |||
1929 : | =cut | ||
1930 : | |||
1931 : | parrello | 1.93 | sub ReadOptions { |
1932 : | parrello | 1.12 | # Get the parameters. |
1933 : | parrello | 1.93 | my ($fileName) = @_; |
1934 : | # Open the file. | ||
1935 : | (open CONFIGFILE, "<$fileName") || Confess("Could not open option file $fileName."); | ||
1936 : | # Count the number of records read. | ||
1937 : | my ($records, $comments) = 0; | ||
1938 : | # Create the return hash. | ||
1939 : | my %retVal = (); | ||
1940 : | # Loop through the file, accumulating key-value pairs. | ||
1941 : | while (my $line = <CONFIGFILE>) { | ||
1942 : | # Denote we've read a line. | ||
1943 : | $records++; | ||
1944 : | # Determine the line type. | ||
1945 : | if ($line =~ /^\s*[\n\r]/) { | ||
1946 : | # A blank line is a comment. | ||
1947 : | $comments++; | ||
1948 : | } elsif ($line =~ /^\s*([A-Za-z0-9_\.]+)=([^;]*);/) { | ||
1949 : | # Here we have an option assignment. | ||
1950 : | retVal{$1} = $2; | ||
1951 : | } elsif ($line =~ /^\s*;/) { | ||
1952 : | # Here we have a text comment. | ||
1953 : | $comments++; | ||
1954 : | parrello | 1.12 | } else { |
1955 : | parrello | 1.93 | # Here we have an invalid line. |
1956 : | Trace("Invalid option statement in record $records.") if T(0); | ||
1957 : | parrello | 1.12 | } |
1958 : | } | ||
1959 : | parrello | 1.93 | # Return the hash created. |
1960 : | return %retVal; | ||
1961 : | olson | 1.1 | } |
1962 : | |||
1963 : | parrello | 1.93 | =head3 GetOptions |
1964 : | parrello | 1.9 | |
1965 : | parrello | 1.93 | Tracer::GetOptions(\%defaults, \%options); |
1966 : | parrello | 1.9 | |
1967 : | parrello | 1.93 | Merge a specified set of options into a table of defaults. This method takes two hash references |
1968 : | as input and uses the data from the second to update the first. If the second does not exist, | ||
1969 : | there will be no effect. An error will be thrown if one of the entries in the second hash does not | ||
1970 : | exist in the first. | ||
1971 : | parrello | 1.9 | |
1972 : | parrello | 1.93 | Consider the following example. |
1973 : | parrello | 1.9 | |
1974 : | parrello | 1.93 | my $optionTable = GetOptions({ dbType => 'mySQL', trace => 0 }, $options); |
1975 : | parrello | 1.9 | |
1976 : | parrello | 1.93 | In this example, the variable B<$options> is expected to contain at most two options-- B<dbType> and |
1977 : | B<trace>. The default database type is C<mySQL> and the default trace level is C<0>. If the value of | ||
1978 : | B<$options> is C<< {dbType => 'Oracle'} >>, then the database type will be changed to C<Oracle> and | ||
1979 : | the trace level will remain at 0. If B<$options> is undefined, then the database type and trace level | ||
1980 : | will remain C<mySQL> and C<0>. If, on the other hand, B<$options> is defined as | ||
1981 : | parrello | 1.9 | |
1982 : | parrello | 1.93 | {databaseType => 'Oracle'} |
1983 : | parrello | 1.9 | |
1984 : | parrello | 1.93 | an error will occur because the B<databaseType> option does not exist. |
1985 : | parrello | 1.9 | |
1986 : | parrello | 1.93 | =over 4 |
1987 : | parrello | 1.9 | |
1988 : | parrello | 1.93 | =item defaults |
1989 : | parrello | 1.9 | |
1990 : | parrello | 1.93 | Table of default option values. |
1991 : | parrello | 1.9 | |
1992 : | parrello | 1.93 | =item options |
1993 : | olson | 1.1 | |
1994 : | parrello | 1.93 | Table of overrides, if any. |
1995 : | olson | 1.1 | |
1996 : | =item RETURN | ||
1997 : | |||
1998 : | parrello | 1.93 | Returns a reference to the default table passed in as the first parameter. |
1999 : | olson | 1.1 | |
2000 : | =back | ||
2001 : | |||
2002 : | =cut | ||
2003 : | |||
2004 : | parrello | 1.93 | sub GetOptions { |
2005 : | # Get the parameters. | ||
2006 : | my ($defaults, $options) = @_; | ||
2007 : | # Check for overrides. | ||
2008 : | if ($options) { | ||
2009 : | # Loop through the overrides. | ||
2010 : | while (my ($option, $setting) = each %{$options}) { | ||
2011 : | # Insure this override exists. | ||
2012 : | if (!exists $defaults->{$option}) { | ||
2013 : | croak "Unrecognized option $option encountered."; | ||
2014 : | parrello | 1.12 | } else { |
2015 : | parrello | 1.93 | # Apply the override. |
2016 : | $defaults->{$option} = $setting; | ||
2017 : | parrello | 1.12 | } |
2018 : | } | ||
2019 : | } | ||
2020 : | parrello | 1.93 | # Return the merged table. |
2021 : | return $defaults; | ||
2022 : | olson | 1.1 | } |
2023 : | |||
2024 : | parrello | 1.93 | =head3 MergeOptions |
2025 : | olson | 1.1 | |
2026 : | parrello | 1.93 | Tracer::MergeOptions(\%table, \%defaults); |
2027 : | olson | 1.1 | |
2028 : | parrello | 1.93 | Merge default values into a hash table. This method looks at the key-value pairs in the |
2029 : | second (default) hash, and if a matching key is not found in the first hash, the default | ||
2030 : | pair is copied in. The process is similar to L</GetOptions>, but there is no error- | ||
2031 : | checking and no return value. | ||
2032 : | olson | 1.1 | |
2033 : | =over 4 | ||
2034 : | |||
2035 : | parrello | 1.93 | =item table |
2036 : | olson | 1.1 | |
2037 : | parrello | 1.93 | Hash table to be updated with the default values. |
2038 : | olson | 1.1 | |
2039 : | parrello | 1.93 | =item defaults |
2040 : | olson | 1.1 | |
2041 : | parrello | 1.93 | Default values to be merged into the first hash table if they are not already present. |
2042 : | olson | 1.1 | |
2043 : | =back | ||
2044 : | |||
2045 : | =cut | ||
2046 : | |||
2047 : | parrello | 1.93 | sub MergeOptions { |
2048 : | # Get the parameters. | ||
2049 : | my ($table, $defaults) = @_; | ||
2050 : | # Loop through the defaults. | ||
2051 : | while (my ($key, $value) = each %{$defaults}) { | ||
2052 : | if (!exists $table->{$key}) { | ||
2053 : | $table->{$key} = $value; | ||
2054 : | } | ||
2055 : | parrello | 1.12 | } |
2056 : | olson | 1.1 | } |
2057 : | |||
2058 : | parrello | 1.111 | =head3 UnparseOptions |
2059 : | |||
2060 : | my $optionString = Tracer::UnparseOptions(\%options); | ||
2061 : | |||
2062 : | Convert an option hash into a command-line string. This will not | ||
2063 : | necessarily be the same text that came in, but it will nonetheless | ||
2064 : | produce the same ultimate result when parsed by L</StandardSetup>. | ||
2065 : | |||
2066 : | =over 4 | ||
2067 : | |||
2068 : | =item options | ||
2069 : | |||
2070 : | Reference to a hash of options to convert into an option string. | ||
2071 : | |||
2072 : | =item RETURN | ||
2073 : | |||
2074 : | Returns a string that will parse to the same set of options when | ||
2075 : | parsed by L</StandardSetup>. | ||
2076 : | |||
2077 : | =back | ||
2078 : | |||
2079 : | =cut | ||
2080 : | |||
2081 : | sub UnparseOptions { | ||
2082 : | # Get the parameters. | ||
2083 : | my ($options) = @_; | ||
2084 : | # The option segments will be put in here. | ||
2085 : | my @retVal = (); | ||
2086 : | # Loop through the options. | ||
2087 : | for my $key (keys %$options) { | ||
2088 : | # Get the option value. | ||
2089 : | my $value = $options->{$key}; | ||
2090 : | # Only use it if it's nonempty. | ||
2091 : | if (defined $value && $value ne "") { | ||
2092 : | my $segment = "--$key=$value"; | ||
2093 : | # Quote it if necessary. | ||
2094 : | if ($segment =~ /[ |<>*]/) { | ||
2095 : | $segment = '"' . $segment . '"'; | ||
2096 : | } | ||
2097 : | # Add it to the return list. | ||
2098 : | push @retVal, $segment; | ||
2099 : | } | ||
2100 : | } | ||
2101 : | # Return the result. | ||
2102 : | return join(" ", @retVal); | ||
2103 : | } | ||
2104 : | |||
2105 : | parrello | 1.93 | =head3 ParseCommand |
2106 : | olson | 1.1 | |
2107 : | parrello | 1.93 | my ($options, @arguments) = Tracer::ParseCommand(\%optionTable, @inputList); |
2108 : | olson | 1.1 | |
2109 : | parrello | 1.93 | Parse a command line consisting of a list of parameters. The initial parameters may be option |
2110 : | specifiers of the form C<->I<option> or C<->I<option>C<=>I<value>. The options are stripped | ||
2111 : | off and merged into a table of default options. The remainder of the command line is | ||
2112 : | returned as a list of positional arguments. For example, consider the following invocation. | ||
2113 : | olson | 1.1 | |
2114 : | parrello | 1.93 | my ($options, @arguments) = ParseCommand({ errors => 0, logFile => 'trace.log'}, @words); |
2115 : | olson | 1.1 | |
2116 : | parrello | 1.93 | In this case, the list @words will be treated as a command line and there are two options available, |
2117 : | B<errors> and B<logFile>. If @words has the following format | ||
2118 : | olson | 1.1 | |
2119 : | parrello | 1.93 | -logFile=error.log apple orange rutabaga |
2120 : | olson | 1.1 | |
2121 : | parrello | 1.93 | then at the end of the invocation, C<$options> will be |
2122 : | olson | 1.1 | |
2123 : | parrello | 1.93 | { errors => 0, logFile => 'error.log' } |
2124 : | olson | 1.1 | |
2125 : | parrello | 1.93 | and C<@arguments> will contain |
2126 : | olson | 1.1 | |
2127 : | parrello | 1.93 | apple orange rutabaga |
2128 : | olson | 1.1 | |
2129 : | parrello | 1.93 | The parser allows for some escape sequences. See L</UnEscape> for a description. There is no |
2130 : | support for quote characters. Options can be specified with single or double hyphens. | ||
2131 : | parrello | 1.54 | |
2132 : | =over 4 | ||
2133 : | |||
2134 : | parrello | 1.93 | =item optionTable |
2135 : | parrello | 1.54 | |
2136 : | parrello | 1.93 | Table of default options. |
2137 : | parrello | 1.54 | |
2138 : | parrello | 1.93 | =item inputList |
2139 : | parrello | 1.54 | |
2140 : | parrello | 1.93 | List of words on the command line. |
2141 : | parrello | 1.54 | |
2142 : | =item RETURN | ||
2143 : | |||
2144 : | parrello | 1.93 | Returns a reference to the option table and a list of the positional arguments. |
2145 : | parrello | 1.54 | |
2146 : | =back | ||
2147 : | |||
2148 : | =cut | ||
2149 : | |||
2150 : | parrello | 1.93 | sub ParseCommand { |
2151 : | parrello | 1.54 | # Get the parameters. |
2152 : | parrello | 1.93 | my ($optionTable, @inputList) = @_; |
2153 : | # Process any options in the input list. | ||
2154 : | my %overrides = (); | ||
2155 : | while ((@inputList > 0) && ($inputList[0] =~ /^--?/)) { | ||
2156 : | # Get the current option. | ||
2157 : | my $arg = shift @inputList; | ||
2158 : | # Pull out the option name. | ||
2159 : | $arg =~ /^--?([^=]*)/g; | ||
2160 : | my $name = $1; | ||
2161 : | # Check for an option value. | ||
2162 : | if ($arg =~ /\G=(.*)$/g) { | ||
2163 : | # Here we have a value for the option. | ||
2164 : | $overrides{$name} = UnEscape($1); | ||
2165 : | } else { | ||
2166 : | # Here there is no value, so we use 1. | ||
2167 : | $overrides{$name} = 1; | ||
2168 : | } | ||
2169 : | } | ||
2170 : | # Merge the options into the defaults. | ||
2171 : | GetOptions($optionTable, \%overrides); | ||
2172 : | # Translate the remaining parameters. | ||
2173 : | my @retVal = (); | ||
2174 : | for my $inputParm (@inputList) { | ||
2175 : | push @retVal, UnEscape($inputParm); | ||
2176 : | parrello | 1.54 | } |
2177 : | parrello | 1.93 | # Return the results. |
2178 : | return ($optionTable, @retVal); | ||
2179 : | parrello | 1.54 | } |
2180 : | |||
2181 : | parrello | 1.93 | |
2182 : | =head2 File Utility Methods | ||
2183 : | |||
2184 : | olson | 1.1 | =head3 GetFile |
2185 : | |||
2186 : | parrello | 1.92 | my @fileContents = Tracer::GetFile($fileName); |
2187 : | olson | 1.1 | |
2188 : | parrello | 1.35 | or |
2189 : | |||
2190 : | parrello | 1.92 | my $fileContents = Tracer::GetFile($fileName); |
2191 : | parrello | 1.35 | |
2192 : | Return the entire contents of a file. In list context, line-ends are removed and | ||
2193 : | each line is a list element. In scalar context, line-ends are replaced by C<\n>. | ||
2194 : | olson | 1.1 | |
2195 : | =over 4 | ||
2196 : | |||
2197 : | =item fileName | ||
2198 : | |||
2199 : | Name of the file to read. | ||
2200 : | |||
2201 : | =item RETURN | ||
2202 : | |||
2203 : | parrello | 1.6 | In a list context, returns the entire file as a list with the line terminators removed. |
2204 : | parrello | 1.39 | In a scalar context, returns the entire file as a string. If an error occurs opening |
2205 : | the file, an empty list will be returned. | ||
2206 : | olson | 1.1 | |
2207 : | =back | ||
2208 : | |||
2209 : | =cut | ||
2210 : | |||
2211 : | sub GetFile { | ||
2212 : | parrello | 1.12 | # Get the parameters. |
2213 : | my ($fileName) = @_; | ||
2214 : | # Declare the return variable. | ||
2215 : | my @retVal = (); | ||
2216 : | # Open the file for input. | ||
2217 : | parrello | 1.60 | my $handle = Open(undef, "<$fileName"); |
2218 : | # Read the whole file into the return variable, stripping off any terminator | ||
2219 : | # characters. | ||
2220 : | my $lineCount = 0; | ||
2221 : | while (my $line = <$handle>) { | ||
2222 : | $lineCount++; | ||
2223 : | $line = Strip($line); | ||
2224 : | push @retVal, $line; | ||
2225 : | } | ||
2226 : | # Close it. | ||
2227 : | close $handle; | ||
2228 : | my $actualLines = @retVal; | ||
2229 : | parrello | 1.77 | Trace("$actualLines lines read from file $fileName.") if T(File => 2); |
2230 : | parrello | 1.12 | # Return the file's contents in the desired format. |
2231 : | parrello | 1.9 | if (wantarray) { |
2232 : | parrello | 1.12 | return @retVal; |
2233 : | parrello | 1.6 | } else { |
2234 : | return join "\n", @retVal; | ||
2235 : | } | ||
2236 : | olson | 1.1 | } |
2237 : | |||
2238 : | parrello | 1.60 | =head3 PutFile |
2239 : | |||
2240 : | parrello | 1.92 | Tracer::PutFile($fileName, \@lines); |
2241 : | parrello | 1.60 | |
2242 : | Write out a file from a list of lines of text. | ||
2243 : | |||
2244 : | =over 4 | ||
2245 : | |||
2246 : | =item fileName | ||
2247 : | |||
2248 : | Name of the output file. | ||
2249 : | |||
2250 : | =item lines | ||
2251 : | |||
2252 : | Reference to a list of text lines. The lines will be written to the file in order, with trailing | ||
2253 : | parrello | 1.66 | new-line characters. Alternatively, may be a string, in which case the string will be written without |
2254 : | modification. | ||
2255 : | parrello | 1.60 | |
2256 : | =back | ||
2257 : | |||
2258 : | =cut | ||
2259 : | |||
2260 : | sub PutFile { | ||
2261 : | # Get the parameters. | ||
2262 : | my ($fileName, $lines) = @_; | ||
2263 : | # Open the output file. | ||
2264 : | my $handle = Open(undef, ">$fileName"); | ||
2265 : | parrello | 1.77 | # Count the lines written. |
2266 : | parrello | 1.66 | if (ref $lines ne 'ARRAY') { |
2267 : | # Here we have a scalar, so we write it raw. | ||
2268 : | print $handle $lines; | ||
2269 : | parrello | 1.77 | Trace("Scalar put to file $fileName.") if T(File => 3); |
2270 : | parrello | 1.66 | } else { |
2271 : | # Write the lines one at a time. | ||
2272 : | parrello | 1.77 | my $count = 0; |
2273 : | parrello | 1.66 | for my $line (@{$lines}) { |
2274 : | print $handle "$line\n"; | ||
2275 : | parrello | 1.77 | $count++; |
2276 : | parrello | 1.66 | } |
2277 : | parrello | 1.77 | Trace("$count lines put to file $fileName.") if T(File => 3); |
2278 : | parrello | 1.60 | } |
2279 : | # Close the output file. | ||
2280 : | close $handle; | ||
2281 : | } | ||
2282 : | |||
2283 : | parrello | 1.93 | =head3 ParseRecord |
2284 : | olson | 1.1 | |
2285 : | parrello | 1.93 | my @fields = Tracer::ParseRecord($line); |
2286 : | olson | 1.1 | |
2287 : | parrello | 1.93 | Parse a tab-delimited data line. The data line is split into field values. Embedded tab |
2288 : | and new-line characters in the data line must be represented as C<\t> and C<\n>, respectively. | ||
2289 : | These will automatically be converted. | ||
2290 : | olson | 1.1 | |
2291 : | =over 4 | ||
2292 : | |||
2293 : | parrello | 1.93 | =item line |
2294 : | |||
2295 : | Line of data containing the tab-delimited fields. | ||
2296 : | |||
2297 : | =item RETURN | ||
2298 : | olson | 1.1 | |
2299 : | parrello | 1.93 | Returns a list of the fields found in the data line. |
2300 : | olson | 1.1 | |
2301 : | =back | ||
2302 : | |||
2303 : | =cut | ||
2304 : | |||
2305 : | parrello | 1.93 | sub ParseRecord { |
2306 : | parrello | 1.12 | # Get the parameter. |
2307 : | parrello | 1.93 | my ($line) = @_; |
2308 : | # Remove the trailing new-line, if any. | ||
2309 : | chomp $line; | ||
2310 : | # Split the line read into pieces using the tab character. | ||
2311 : | my @retVal = split /\t/, $line; | ||
2312 : | # Trim and fix the escapes in each piece. | ||
2313 : | for my $value (@retVal) { | ||
2314 : | # Trim leading whitespace. | ||
2315 : | $value =~ s/^\s+//; | ||
2316 : | # Trim trailing whitespace. | ||
2317 : | $value =~ s/\s+$//; | ||
2318 : | # Delete the carriage returns. | ||
2319 : | $value =~ s/\r//g; | ||
2320 : | # Convert the escapes into their real values. | ||
2321 : | $value =~ s/\\t/"\t"/ge; | ||
2322 : | $value =~ s/\\n/"\n"/ge; | ||
2323 : | } | ||
2324 : | # Return the result. | ||
2325 : | return @retVal; | ||
2326 : | } | ||
2327 : | |||
2328 : | =head3 Merge | ||
2329 : | |||
2330 : | my @mergedList = Tracer::Merge(@inputList); | ||
2331 : | |||
2332 : | Sort a list of strings and remove duplicates. | ||
2333 : | |||
2334 : | =over 4 | ||
2335 : | |||
2336 : | =item inputList | ||
2337 : | |||
2338 : | List of scalars to sort and merge. | ||
2339 : | |||
2340 : | =item RETURN | ||
2341 : | |||
2342 : | Returns a list containing the same elements sorted in ascending order with duplicates | ||
2343 : | removed. | ||
2344 : | |||
2345 : | =back | ||
2346 : | |||
2347 : | =cut | ||
2348 : | |||
2349 : | sub Merge { | ||
2350 : | # Get the input list in sort order. | ||
2351 : | my @inputList = sort @_; | ||
2352 : | # Only proceed if the list has at least two elements. | ||
2353 : | if (@inputList > 1) { | ||
2354 : | # Now we want to move through the list splicing out duplicates. | ||
2355 : | my $i = 0; | ||
2356 : | while ($i < @inputList) { | ||
2357 : | # Get the current entry. | ||
2358 : | my $thisEntry = $inputList[$i]; | ||
2359 : | # Find out how many elements duplicate the current entry. | ||
2360 : | my $j = $i + 1; | ||
2361 : | my $dup1 = $i + 1; | ||
2362 : | while ($j < @inputList && $inputList[$j] eq $thisEntry) { $j++; }; | ||
2363 : | # If the number is nonzero, splice out the duplicates found. | ||
2364 : | if ($j > $dup1) { | ||
2365 : | splice @inputList, $dup1, $j - $dup1; | ||
2366 : | parrello | 1.14 | } |
2367 : | parrello | 1.93 | # Now the element at position $dup1 is different from the element before it |
2368 : | # at position $i. We push $i forward one position and start again. | ||
2369 : | $i++; | ||
2370 : | parrello | 1.14 | } |
2371 : | parrello | 1.12 | } |
2372 : | parrello | 1.93 | # Return the merged list. |
2373 : | return @inputList; | ||
2374 : | olson | 1.1 | } |
2375 : | |||
2376 : | parrello | 1.93 | =head3 Open |
2377 : | olson | 1.1 | |
2378 : | parrello | 1.93 | my $handle = Open($fileHandle, $fileSpec, $message); |
2379 : | olson | 1.1 | |
2380 : | parrello | 1.93 | Open a file. |
2381 : | olson | 1.1 | |
2382 : | parrello | 1.93 | The I<$fileSpec> is essentially the second argument of the PERL C<open> |
2383 : | function. The mode is specified using Unix-like shell information. So, for | ||
2384 : | example, | ||
2385 : | olson | 1.1 | |
2386 : | parrello | 1.93 | Open(\*LOGFILE, '>>/usr/spool/news/twitlog', "Could not open twit log."); |
2387 : | olson | 1.1 | |
2388 : | parrello | 1.93 | would open for output appended to the specified file, and |
2389 : | olson | 1.1 | |
2390 : | parrello | 1.93 | Open(\*DATASTREAM, "| sort -u >$outputFile", "Could not open $outputFile."); |
2391 : | olson | 1.1 | |
2392 : | parrello | 1.93 | would open a pipe that sorts the records written and removes duplicates. Note |
2393 : | the use of file handle syntax in the Open call. To use anonymous file handles, | ||
2394 : | code as follows. | ||
2395 : | olson | 1.1 | |
2396 : | parrello | 1.93 | my $logFile = Open(undef, '>>/usr/spool/news/twitlog', "Could not open twit log."); |
2397 : | olson | 1.1 | |
2398 : | parrello | 1.93 | The I<$message> parameter is used if the open fails. If it is set to C<0>, then |
2399 : | the open returns TRUE if successful and FALSE if an error occurred. Otherwise, a | ||
2400 : | failed open will throw an exception and the third parameter will be used to construct | ||
2401 : | an error message. If the parameter is omitted, a standard message is constructed | ||
2402 : | using the file spec. | ||
2403 : | olson | 1.1 | |
2404 : | parrello | 1.93 | Could not open "/usr/spool/news/twitlog" |
2405 : | olson | 1.1 | |
2406 : | parrello | 1.93 | Note that the mode characters are automatically cleaned from the file name. |
2407 : | The actual error message from the file system will be captured and appended to the | ||
2408 : | message in any case. | ||
2409 : | parrello | 1.6 | |
2410 : | parrello | 1.93 | Could not open "/usr/spool/news/twitlog": file not found. |
2411 : | parrello | 1.6 | |
2412 : | parrello | 1.93 | In some versions of PERL the only error message we get is a number, which |
2413 : | corresponds to the C++ C<errno> value. | ||
2414 : | parrello | 1.6 | |
2415 : | parrello | 1.93 | Could not open "/usr/spool/news/twitlog": 6. |
2416 : | parrello | 1.6 | |
2417 : | parrello | 1.93 | =over 4 |
2418 : | parrello | 1.6 | |
2419 : | parrello | 1.93 | =item fileHandle |
2420 : | parrello | 1.6 | |
2421 : | parrello | 1.93 | File handle. If this parameter is C<undef>, a file handle will be generated |
2422 : | and returned as the value of this method. | ||
2423 : | olson | 1.1 | |
2424 : | parrello | 1.93 | =item fileSpec |
2425 : | olson | 1.1 | |
2426 : | parrello | 1.93 | File name and mode, as per the PERL C<open> function. |
2427 : | olson | 1.1 | |
2428 : | parrello | 1.93 | =item message (optional) |
2429 : | olson | 1.1 | |
2430 : | parrello | 1.93 | Error message to use if the open fails. If omitted, a standard error message |
2431 : | will be generated. In either case, the error information from the file system | ||
2432 : | is appended to the message. To specify a conditional open that does not throw | ||
2433 : | an error if it fails, use C<0>. | ||
2434 : | olson | 1.1 | |
2435 : | parrello | 1.93 | =item RETURN |
2436 : | olson | 1.1 | |
2437 : | parrello | 1.93 | Returns the name of the file handle assigned to the file, or C<undef> if the |
2438 : | open failed. | ||
2439 : | olson | 1.1 | |
2440 : | =back | ||
2441 : | |||
2442 : | =cut | ||
2443 : | |||
2444 : | parrello | 1.93 | sub Open { |
2445 : | parrello | 1.12 | # Get the parameters. |
2446 : | parrello | 1.93 | my ($fileHandle, $fileSpec, $message) = @_; |
2447 : | # Attempt to open the file. | ||
2448 : | my $rv = open $fileHandle, $fileSpec; | ||
2449 : | # If the open failed, generate an error message. | ||
2450 : | if (! $rv) { | ||
2451 : | # Save the system error message. | ||
2452 : | my $sysMessage = $!; | ||
2453 : | # See if we need a default message. | ||
2454 : | if (!$message) { | ||
2455 : | # Clean any obvious mode characters and leading spaces from the | ||
2456 : | # filename. | ||
2457 : | my ($fileName) = FindNamePart($fileSpec); | ||
2458 : | $message = "Could not open \"$fileName\""; | ||
2459 : | } | ||
2460 : | # Terminate with an error using the supplied message and the | ||
2461 : | # error message from the file system. | ||
2462 : | Confess("$message: $!"); | ||
2463 : | parrello | 1.12 | } |
2464 : | parrello | 1.93 | # Return the file handle. |
2465 : | return $fileHandle; | ||
2466 : | olson | 1.1 | } |
2467 : | |||
2468 : | parrello | 1.93 | =head3 FindNamePart |
2469 : | |||
2470 : | my ($fileName, $start, $len) = Tracer::FindNamePart($fileSpec); | ||
2471 : | |||
2472 : | Extract the portion of a file specification that contains the file name. | ||
2473 : | |||
2474 : | A file specification is the string passed to an C<open> call. It specifies the file | ||
2475 : | mode and name. In a truly complex situation, it can specify a pipe sequence. This | ||
2476 : | method assumes that the file name is whatever follows the first angle bracket | ||
2477 : | sequence. So, for example, in the following strings the file name is | ||
2478 : | C</usr/fig/myfile.txt>. | ||
2479 : | parrello | 1.5 | |
2480 : | parrello | 1.93 | >>/usr/fig/myfile.txt |
2481 : | </usr/fig/myfile.txt | ||
2482 : | | sort -u > /usr/fig/myfile.txt | ||
2483 : | parrello | 1.5 | |
2484 : | parrello | 1.93 | If the method cannot find a file name using its normal methods, it will return the |
2485 : | whole incoming string. | ||
2486 : | parrello | 1.5 | |
2487 : | =over 4 | ||
2488 : | |||
2489 : | parrello | 1.93 | =item fileSpec |
2490 : | parrello | 1.5 | |
2491 : | parrello | 1.93 | File specification string from which the file name is to be extracted. |
2492 : | parrello | 1.5 | |
2493 : | =item RETURN | ||
2494 : | |||
2495 : | parrello | 1.93 | Returns a three-element list. The first element contains the file name portion of |
2496 : | the specified string, or the whole string if a file name cannot be found via normal | ||
2497 : | methods. The second element contains the start position of the file name portion and | ||
2498 : | the third element contains the length. | ||
2499 : | parrello | 1.5 | |
2500 : | =back | ||
2501 : | |||
2502 : | =cut | ||
2503 : | parrello | 1.93 | #: Return Type $; |
2504 : | sub FindNamePart { | ||
2505 : | # Get the parameters. | ||
2506 : | my ($fileSpec) = @_; | ||
2507 : | # Default to the whole input string. | ||
2508 : | my ($retVal, $pos, $len) = ($fileSpec, 0, length $fileSpec); | ||
2509 : | # Parse out the file name if we can. | ||
2510 : | if ($fileSpec =~ m/(<|>>?)(.+?)(\s*)$/) { | ||
2511 : | $retVal = $2; | ||
2512 : | $len = length $retVal; | ||
2513 : | $pos = (length $fileSpec) - (length $3) - $len; | ||
2514 : | parrello | 1.12 | } |
2515 : | parrello | 1.93 | # Return the result. |
2516 : | return ($retVal, $pos, $len); | ||
2517 : | parrello | 1.5 | } |
2518 : | |||
2519 : | parrello | 1.93 | =head3 OpenDir |
2520 : | parrello | 1.5 | |
2521 : | parrello | 1.93 | my @files = OpenDir($dirName, $filtered, $flag); |
2522 : | parrello | 1.5 | |
2523 : | parrello | 1.93 | Open a directory and return all the file names. This function essentially performs |
2524 : | the functions of an C<opendir> and C<readdir>. If the I<$filtered> parameter is | ||
2525 : | set to TRUE, all filenames beginning with a period (C<.>), dollar sign (C<$>), | ||
2526 : | or pound sign (C<#>) and all filenames ending with a tilde C<~>) will be | ||
2527 : | filtered out of the return list. If the directory does not open and I<$flag> is not | ||
2528 : | set, an exception is thrown. So, for example, | ||
2529 : | parrello | 1.5 | |
2530 : | parrello | 1.93 | my @files = OpenDir("/Volumes/fig/contigs", 1); |
2531 : | parrello | 1.5 | |
2532 : | parrello | 1.93 | is effectively the same as |
2533 : | parrello | 1.5 | |
2534 : | parrello | 1.93 | opendir(TMP, "/Volumes/fig/contigs") || Confess("Could not open /Volumes/fig/contigs."); |
2535 : | my @files = grep { $_ !~ /^[\.\$\#]/ && $_ !~ /~$/ } readdir(TMP); | ||
2536 : | parrello | 1.5 | |
2537 : | parrello | 1.93 | Similarly, the following code |
2538 : | parrello | 1.5 | |
2539 : | parrello | 1.93 | my @files = grep { $_ =~ /^\d/ } OpenDir("/Volumes/fig/orgs", 0, 1); |
2540 : | parrello | 1.5 | |
2541 : | parrello | 1.93 | Returns the names of all files in C</Volumes/fig/orgs> that begin with digits and |
2542 : | automatically returns an empty list if the directory fails to open. | ||
2543 : | parrello | 1.5 | |
2544 : | parrello | 1.93 | =over 4 |
2545 : | parrello | 1.5 | |
2546 : | parrello | 1.93 | =item dirName |
2547 : | parrello | 1.5 | |
2548 : | parrello | 1.93 | Name of the directory to open. |
2549 : | parrello | 1.5 | |
2550 : | parrello | 1.93 | =item filtered |
2551 : | parrello | 1.5 | |
2552 : | parrello | 1.93 | TRUE if files whose names begin with a period (C<.>) should be automatically removed |
2553 : | from the list, else FALSE. | ||
2554 : | parrello | 1.5 | |
2555 : | parrello | 1.93 | =item flag |
2556 : | parrello | 1.5 | |
2557 : | parrello | 1.93 | TRUE if a failure to open is okay, else FALSE |
2558 : | parrello | 1.5 | |
2559 : | parrello | 1.93 | =back |
2560 : | parrello | 1.5 | |
2561 : | parrello | 1.93 | =cut |
2562 : | #: Return Type @; | ||
2563 : | sub OpenDir { | ||
2564 : | # Get the parameters. | ||
2565 : | my ($dirName, $filtered, $flag) = @_; | ||
2566 : | # Declare the return variable. | ||
2567 : | my @retVal = (); | ||
2568 : | # Open the directory. | ||
2569 : | if (opendir(my $dirHandle, $dirName)) { | ||
2570 : | # The directory opened successfully. Get the appropriate list according to the | ||
2571 : | # strictures of the filter parameter. | ||
2572 : | if ($filtered) { | ||
2573 : | @retVal = grep { $_ !~ /^[\.\$\#]/ && $_ !~ /~$/ } readdir $dirHandle; | ||
2574 : | } else { | ||
2575 : | @retVal = readdir $dirHandle; | ||
2576 : | } | ||
2577 : | parrello | 1.117 | closedir $dirHandle; |
2578 : | parrello | 1.93 | } elsif (! $flag) { |
2579 : | # Here the directory would not open and it's considered an error. | ||
2580 : | Confess("Could not open directory $dirName."); | ||
2581 : | } | ||
2582 : | # Return the result. | ||
2583 : | return @retVal; | ||
2584 : | } | ||
2585 : | parrello | 1.5 | |
2586 : | |||
2587 : | parrello | 1.93 | =head3 Insure |
2588 : | parrello | 1.5 | |
2589 : | parrello | 1.93 | Insure($dirName, $chmod); |
2590 : | parrello | 1.5 | |
2591 : | parrello | 1.93 | Insure a directory is present. |
2592 : | parrello | 1.5 | |
2593 : | parrello | 1.93 | =over 4 |
2594 : | parrello | 1.5 | |
2595 : | parrello | 1.93 | =item dirName |
2596 : | olson | 1.1 | |
2597 : | parrello | 1.93 | Name of the directory to check. If it does not exist, it will be created. |
2598 : | parrello | 1.7 | |
2599 : | parrello | 1.93 | =item chmod (optional) |
2600 : | parrello | 1.7 | |
2601 : | parrello | 1.93 | Security privileges to be given to the directory if it is created. |
2602 : | parrello | 1.7 | |
2603 : | parrello | 1.93 | =back |
2604 : | parrello | 1.7 | |
2605 : | =cut | ||
2606 : | |||
2607 : | parrello | 1.93 | sub Insure { |
2608 : | my ($dirName, $chmod) = @_; | ||
2609 : | if (! -d $dirName) { | ||
2610 : | Trace("Creating $dirName directory.") if T(2); | ||
2611 : | eval { | ||
2612 : | mkpath $dirName; | ||
2613 : | # If we have permissions specified, set them here. | ||
2614 : | if (defined($chmod)) { | ||
2615 : | chmod $chmod, $dirName; | ||
2616 : | } | ||
2617 : | }; | ||
2618 : | if ($@) { | ||
2619 : | Confess("Error creating $dirName: $@"); | ||
2620 : | } | ||
2621 : | parrello | 1.12 | } |
2622 : | parrello | 1.9 | } |
2623 : | |||
2624 : | parrello | 1.93 | =head3 ChDir |
2625 : | parrello | 1.9 | |
2626 : | parrello | 1.93 | ChDir($dirName); |
2627 : | parrello | 1.9 | |
2628 : | parrello | 1.93 | Change to the specified directory. |
2629 : | parrello | 1.9 | |
2630 : | =over 4 | ||
2631 : | |||
2632 : | parrello | 1.93 | =item dirName |
2633 : | parrello | 1.9 | |
2634 : | parrello | 1.93 | Name of the directory to which we want to change. |
2635 : | parrello | 1.9 | |
2636 : | =back | ||
2637 : | |||
2638 : | =cut | ||
2639 : | |||
2640 : | parrello | 1.93 | sub ChDir { |
2641 : | my ($dirName) = @_; | ||
2642 : | if (! -d $dirName) { | ||
2643 : | Confess("Cannot change to directory $dirName: no such directory."); | ||
2644 : | } else { | ||
2645 : | Trace("Changing to directory $dirName.") if T(File => 4); | ||
2646 : | my $okFlag = chdir $dirName; | ||
2647 : | if (! $okFlag) { | ||
2648 : | Confess("Error switching to directory $dirName."); | ||
2649 : | } | ||
2650 : | } | ||
2651 : | parrello | 1.9 | } |
2652 : | |||
2653 : | parrello | 1.93 | =head3 SetPermissions |
2654 : | |||
2655 : | Tracer::SetPermissions($dirName, $group, $mask, %otherMasks); | ||
2656 : | parrello | 1.9 | |
2657 : | parrello | 1.93 | Set the permissions for a directory and all the files and folders inside it. |
2658 : | In addition, the group ownership will be changed to the specified value. | ||
2659 : | parrello | 1.9 | |
2660 : | parrello | 1.93 | This method is more vulnerable than most to permission and compatability |
2661 : | problems, so it does internal error recovery. | ||
2662 : | parrello | 1.9 | |
2663 : | =over 4 | ||
2664 : | |||
2665 : | parrello | 1.93 | =item dirName |
2666 : | |||
2667 : | Name of the directory to process. | ||
2668 : | |||
2669 : | =item group | ||
2670 : | parrello | 1.9 | |
2671 : | parrello | 1.93 | Name of the group to be assigned. |
2672 : | parrello | 1.9 | |
2673 : | parrello | 1.93 | =item mask |
2674 : | parrello | 1.9 | |
2675 : | parrello | 1.93 | Permission mask. Bits that are C<1> in this mask will be ORed into the |
2676 : | permission bits of any file or directory that does not already have them | ||
2677 : | set to 1. | ||
2678 : | parrello | 1.9 | |
2679 : | parrello | 1.93 | =item otherMasks |
2680 : | parrello | 1.9 | |
2681 : | parrello | 1.93 | Map of search patterns to permission masks. If a directory name matches |
2682 : | one of the patterns, that directory and all its members and subdirectories | ||
2683 : | will be assigned the new pattern. For example, the following would | ||
2684 : | parrello | 1.110 | assign 0664 to most files, but would use 0777 for directories named C<tmp>. |
2685 : | parrello | 1.9 | |
2686 : | parrello | 1.93 | Tracer::SetPermissions($dirName, 'fig', 01664, '^tmp$' => 01777); |
2687 : | parrello | 1.9 | |
2688 : | parrello | 1.93 | The list is ordered, so the following would use 0777 for C<tmp1> and |
2689 : | 0666 for C<tmp>, C<tmp2>, or C<tmp3>. | ||
2690 : | parrello | 1.22 | |
2691 : | parrello | 1.93 | Tracer::SetPermissions($dirName, 'fig', 01664, '^tmp1' => 0777, |
2692 : | '^tmp' => 0666); | ||
2693 : | parrello | 1.9 | |
2694 : | parrello | 1.93 | Note that the pattern matches are all case-insensitive, and only directory |
2695 : | names are matched, not file names. | ||
2696 : | parrello | 1.9 | |
2697 : | =back | ||
2698 : | |||
2699 : | =cut | ||
2700 : | |||
2701 : | parrello | 1.93 | sub SetPermissions { |
2702 : | parrello | 1.12 | # Get the parameters. |
2703 : | parrello | 1.93 | my ($dirName, $group, $mask, @otherMasks) = @_; |
2704 : | # Set up for error recovery. | ||
2705 : | eval { | ||
2706 : | # Switch to the specified directory. | ||
2707 : | ChDir($dirName); | ||
2708 : | # Get the group ID. | ||
2709 : | my $gid = getgrnam($group); | ||
2710 : | # Get the mask for tracing. | ||
2711 : | my $traceMask = sprintf("%04o", $mask) . "($mask)"; | ||
2712 : | Trace("Fixing permissions for directory $dirName using group $group($gid) and mask $traceMask.") if T(File => 2); | ||
2713 : | my $fixCount = 0; | ||
2714 : | my $lookCount = 0; | ||
2715 : | # @dirs will be a stack of directories to be processed. | ||
2716 : | my @dirs = (getcwd()); | ||
2717 : | while (scalar(@dirs) > 0) { | ||
2718 : | # Get the current directory. | ||
2719 : | my $dir = pop @dirs; | ||
2720 : | # Check for a match to one of the specified directory names. To do | ||
2721 : | # that, we need to pull the individual part of the name off of the | ||
2722 : | # whole path. | ||
2723 : | my $simpleName = $dir; | ||
2724 : | if ($dir =~ m!/([^/]+)$!) { | ||
2725 : | $simpleName = $1; | ||
2726 : | } | ||
2727 : | Trace("Simple directory name for $dir is $simpleName.") if T(File => 4); | ||
2728 : | # Search for a match. | ||
2729 : | my $match = 0; | ||
2730 : | my $i; | ||
2731 : | for ($i = 0; $i < $#otherMasks && ! $match; $i += 2) { | ||
2732 : | my $pattern = $otherMasks[$i]; | ||
2733 : | if ($simpleName =~ /$pattern/i) { | ||
2734 : | $match = 1; | ||
2735 : | } | ||
2736 : | } | ||
2737 : | parrello | 1.110 | # Find out if we have a match. Note we use $i-1 because the loop added 2 |
2738 : | parrello | 1.93 | # before terminating due to the match. |
2739 : | if ($match && $otherMasks[$i-1] != $mask) { | ||
2740 : | # This directory matches one of the incoming patterns, and it's | ||
2741 : | # a different mask, so we process it recursively with that mask. | ||
2742 : | SetPermissions($dir, $group, $otherMasks[$i-1], @otherMasks); | ||
2743 : | } else { | ||
2744 : | # Here we can process normally. Get all of the non-hidden members. | ||
2745 : | my @submems = OpenDir($dir, 1); | ||
2746 : | for my $submem (@submems) { | ||
2747 : | # Get the full name. | ||
2748 : | my $thisMem = "$dir/$submem"; | ||
2749 : | Trace("Checking member $thisMem.") if T(4); | ||
2750 : | $lookCount++; | ||
2751 : | if ($lookCount % 1000 == 0) { | ||
2752 : | Trace("$lookCount members examined. Current is $thisMem. Mask is $traceMask") if T(File => 3); | ||
2753 : | } | ||
2754 : | # Fix the group. | ||
2755 : | chown -1, $gid, $thisMem; | ||
2756 : | # Insure this member is not a symlink. | ||
2757 : | if (! -l $thisMem) { | ||
2758 : | # Get its info. | ||
2759 : | my $fileInfo = stat $thisMem; | ||
2760 : | # Only proceed if we got the info. Otherwise, it's a hard link | ||
2761 : | # and we want to skip it anyway. | ||
2762 : | if ($fileInfo) { | ||
2763 : | my $fileMode = $fileInfo->mode; | ||
2764 : | if (($fileMode & $mask) != $mask) { | ||
2765 : | # Fix this member. | ||
2766 : | $fileMode |= $mask; | ||
2767 : | chmod $fileMode, $thisMem; | ||
2768 : | $fixCount++; | ||
2769 : | } | ||
2770 : | # If it's a subdirectory, stack it. | ||
2771 : | if (-d $thisMem) { | ||
2772 : | push @dirs, $thisMem; | ||
2773 : | } | ||
2774 : | } | ||
2775 : | } | ||
2776 : | } | ||
2777 : | } | ||
2778 : | parrello | 1.12 | } |
2779 : | parrello | 1.93 | Trace("$lookCount files and directories processed, $fixCount fixed.") if T(File => 2); |
2780 : | }; | ||
2781 : | # Check for an error. | ||
2782 : | if ($@) { | ||
2783 : | Confess("SetPermissions error: $@"); | ||
2784 : | parrello | 1.12 | } |
2785 : | parrello | 1.7 | } |
2786 : | |||
2787 : | parrello | 1.93 | =head3 GetLine |
2788 : | parrello | 1.29 | |
2789 : | parrello | 1.93 | my @data = Tracer::GetLine($handle); |
2790 : | parrello | 1.15 | |
2791 : | parrello | 1.93 | Read a line of data from a tab-delimited file. |
2792 : | parrello | 1.15 | |
2793 : | =over 4 | ||
2794 : | |||
2795 : | parrello | 1.93 | =item handle |
2796 : | parrello | 1.15 | |
2797 : | parrello | 1.93 | Open file handle from which to read. |
2798 : | parrello | 1.15 | |
2799 : | =item RETURN | ||
2800 : | |||
2801 : | parrello | 1.93 | Returns a list of the fields in the record read. The fields are presumed to be |
2802 : | tab-delimited. If we are at the end of the file, then an empty list will be | ||
2803 : | returned. If an empty line is read, a single list item consisting of a null | ||
2804 : | string will be returned. | ||
2805 : | parrello | 1.15 | |
2806 : | =back | ||
2807 : | |||
2808 : | =cut | ||
2809 : | parrello | 1.93 | |
2810 : | sub GetLine { | ||
2811 : | parrello | 1.15 | # Get the parameters. |
2812 : | parrello | 1.93 | my ($handle) = @_; |
2813 : | # Declare the return variable. | ||
2814 : | my @retVal = (); | ||
2815 : | Trace("File position is " . tell($handle) . ". EOF flag is " . eof($handle) . ".") if T(File => 4); | ||
2816 : | # Read from the file. | ||
2817 : | my $line = <$handle>; | ||
2818 : | # Only proceed if we found something. | ||
2819 : | if (defined $line) { | ||
2820 : | # Remove the new-line. We are a bit over-cautious here because the file may be coming in via an | ||
2821 : | # upload control and have a nonstandard EOL combination. | ||
2822 : | $line =~ s/(\r|\n)+$//; | ||
2823 : | # Here we do some fancy tracing to help in debugging complicated EOL marks. | ||
2824 : | if (T(File => 4)) { | ||
2825 : | my $escapedLine = $line; | ||
2826 : | $escapedLine =~ s/\n/\\n/g; | ||
2827 : | $escapedLine =~ s/\r/\\r/g; | ||
2828 : | $escapedLine =~ s/\t/\\t/g; | ||
2829 : | Trace("Line read: -->$escapedLine<--"); | ||
2830 : | } | ||
2831 : | # If the line is empty, return a single empty string; otherwise, parse | ||
2832 : | # it into fields. | ||
2833 : | if ($line eq "") { | ||
2834 : | push @retVal, ""; | ||
2835 : | } else { | ||
2836 : | push @retVal, split /\t/,$line; | ||
2837 : | } | ||
2838 : | } else { | ||
2839 : | # Trace the reason the read failed. | ||
2840 : | Trace("End of file: $!") if T(File => 3); | ||
2841 : | parrello | 1.15 | } |
2842 : | parrello | 1.93 | # Return the result. |
2843 : | return @retVal; | ||
2844 : | parrello | 1.15 | } |
2845 : | |||
2846 : | parrello | 1.93 | =head3 PutLine |
2847 : | |||
2848 : | Tracer::PutLine($handle, \@fields, $eol); | ||
2849 : | parrello | 1.35 | |
2850 : | parrello | 1.93 | Write a line of data to a tab-delimited file. The specified field values will be |
2851 : | output in tab-separated form, with a trailing new-line. | ||
2852 : | parrello | 1.35 | |
2853 : | parrello | 1.93 | =over 4 |
2854 : | parrello | 1.35 | |
2855 : | parrello | 1.93 | =item handle |
2856 : | parrello | 1.69 | |
2857 : | parrello | 1.93 | Output file handle. |
2858 : | parrello | 1.69 | |
2859 : | parrello | 1.93 | =item fields |
2860 : | parrello | 1.69 | |
2861 : | parrello | 1.93 | List of field values. |
2862 : | parrello | 1.69 | |
2863 : | parrello | 1.93 | =item eol (optional) |
2864 : | parrello | 1.69 | |
2865 : | parrello | 1.93 | End-of-line character (default is "\n"). |
2866 : | parrello | 1.69 | |
2867 : | =back | ||
2868 : | parrello | 1.35 | |
2869 : | =cut | ||
2870 : | |||
2871 : | parrello | 1.93 | sub PutLine { |
2872 : | parrello | 1.69 | # Get the parameters. |
2873 : | parrello | 1.93 | my ($handle, $fields, $eol) = @_; |
2874 : | # Write the data. | ||
2875 : | print $handle join("\t", @{$fields}) . ($eol || "\n"); | ||
2876 : | parrello | 1.69 | } |
2877 : | |||
2878 : | |||
2879 : | parrello | 1.97 | =head3 PrintLine |
2880 : | |||
2881 : | Tracer::PrintLine($line); | ||
2882 : | |||
2883 : | Print a line of text with a trailing new-line. | ||
2884 : | |||
2885 : | =over 4 | ||
2886 : | |||
2887 : | =item line | ||
2888 : | |||
2889 : | Line of text to print. | ||
2890 : | |||
2891 : | =back | ||
2892 : | |||
2893 : | =cut | ||
2894 : | |||
2895 : | sub PrintLine { | ||
2896 : | # Get the parameters. | ||
2897 : | my ($line) = @_; | ||
2898 : | # Print the line. | ||
2899 : | print "$line\n"; | ||
2900 : | } | ||
2901 : | |||
2902 : | parrello | 1.69 | |
2903 : | parrello | 1.93 | =head2 Other Useful Methods |
2904 : | |||
2905 : | parrello | 1.115 | =head3 IDHASH |
2906 : | |||
2907 : | my $hash = SHTargetSearch::IDHASH(@keys); | ||
2908 : | |||
2909 : | This is a dinky little method that converts a list of values to a reference | ||
2910 : | to hash of values to labels. The values and labels are the same. | ||
2911 : | |||
2912 : | =cut | ||
2913 : | |||
2914 : | sub IDHASH { | ||
2915 : | my %retVal = map { $_ => $_ } @_; | ||
2916 : | return \%retVal; | ||
2917 : | } | ||
2918 : | |||
2919 : | =head3 Pluralize | ||
2920 : | |||
2921 : | my $plural = Tracer::Pluralize($word); | ||
2922 : | |||
2923 : | This is a very simple pluralization utility. It adds an C<s> at the end | ||
2924 : | of the input word unless it already ends in an C<s>, in which case it | ||
2925 : | adds C<es>. | ||
2926 : | |||
2927 : | =over 4 | ||
2928 : | |||
2929 : | =item word | ||
2930 : | |||
2931 : | Singular word to pluralize. | ||
2932 : | |||
2933 : | =item RETURN | ||
2934 : | |||
2935 : | Returns the probable plural form of the word. | ||
2936 : | |||
2937 : | =back | ||
2938 : | |||
2939 : | =cut | ||
2940 : | |||
2941 : | sub Pluralize { | ||
2942 : | # Get the parameters. | ||
2943 : | my ($word) = @_; | ||
2944 : | # Declare the return variable. | ||
2945 : | my $retVal; | ||
2946 : | if ($word =~ /s$/) { | ||
2947 : | $retVal = $word . 'es'; | ||
2948 : | } else { | ||
2949 : | $retVal = $word . 's'; | ||
2950 : | } | ||
2951 : | # Return the result. | ||
2952 : | return $retVal; | ||
2953 : | } | ||
2954 : | |||
2955 : | =head3 Numeric | ||
2956 : | |||
2957 : | my $okFlag = Tracer::Numeric($string); | ||
2958 : | |||
2959 : | Return the value of the specified string if it is numeric, or an undefined value | ||
2960 : | if it is not numeric. | ||
2961 : | |||
2962 : | =over 4 | ||
2963 : | |||
2964 : | =item string | ||
2965 : | |||
2966 : | String to check. | ||
2967 : | |||
2968 : | =item RETURN | ||
2969 : | |||
2970 : | Returns the numeric value of the string if successful, or C<undef> if the string | ||
2971 : | is not numeric. | ||
2972 : | |||
2973 : | =back | ||
2974 : | |||
2975 : | =cut | ||
2976 : | |||
2977 : | sub Numeric { | ||
2978 : | # Get the parameters. | ||
2979 : | my ($string) = @_; | ||
2980 : | # We'll put the value in here if we succeed. | ||
2981 : | my $retVal; | ||
2982 : | # Get a working copy of the string. | ||
2983 : | my $copy = $string; | ||
2984 : | # Trim leading and trailing spaces. | ||
2985 : | $copy =~ s/^\s+//; | ||
2986 : | $copy =~ s/\s+$//; | ||
2987 : | # Check the result. | ||
2988 : | if ($copy =~ /^[+-]?\d+$/) { | ||
2989 : | $retVal = $copy; | ||
2990 : | } elsif ($copy =~ /^([+-]\d+|\d*)[eE][+-]?\d+$/) { | ||
2991 : | $retVal = $copy; | ||
2992 : | } elsif ($copy =~ /^([+-]\d+|\d*)\.\d*([eE][+-]?\d+)?$/) { | ||
2993 : | $retVal = $copy; | ||
2994 : | } | ||
2995 : | # Return the result. | ||
2996 : | return $retVal; | ||
2997 : | } | ||
2998 : | |||
2999 : | |||
3000 : | parrello | 1.93 | =head3 ParseParm |
3001 : | |||
3002 : | my $listValue = Tracer::ParseParm($string); | ||
3003 : | |||
3004 : | Convert a parameter into a list reference. If the parameter is undefined, | ||
3005 : | an undefined value will be returned. Otherwise, it will be parsed as a | ||
3006 : | comma-separated list of values. | ||
3007 : | parrello | 1.69 | |
3008 : | =over 4 | ||
3009 : | |||
3010 : | parrello | 1.93 | =item string |
3011 : | |||
3012 : | Incoming string. | ||
3013 : | |||
3014 : | =item RETURN | ||
3015 : | parrello | 1.69 | |
3016 : | parrello | 1.93 | Returns a reference to a list of values, or C<undef> if the incoming value |
3017 : | was undefined. | ||
3018 : | parrello | 1.69 | |
3019 : | =back | ||
3020 : | |||
3021 : | =cut | ||
3022 : | |||
3023 : | parrello | 1.93 | sub ParseParm { |
3024 : | # Get the parameters. | ||
3025 : | my ($string) = @_; | ||
3026 : | # Declare the return variable. | ||
3027 : | my $retVal; | ||
3028 : | # Check for data. | ||
3029 : | if (defined $string) { | ||
3030 : | # We have some, so split it into a list. | ||
3031 : | $retVal = [ split /\s*,\s*/, $string]; | ||
3032 : | parrello | 1.72 | } |
3033 : | parrello | 1.93 | # Return the result. |
3034 : | return $retVal; | ||
3035 : | parrello | 1.69 | } |
3036 : | |||
3037 : | parrello | 1.97 | =head3 Now |
3038 : | |||
3039 : | my $string = Tracer::Now(); | ||
3040 : | |||
3041 : | Return a displayable time stamp containing the local time. Whatever format this | ||
3042 : | method produces must be parseable by L</ParseDate>. | ||
3043 : | |||
3044 : | =cut | ||
3045 : | |||
3046 : | sub Now { | ||
3047 : | return DisplayTime(time); | ||
3048 : | } | ||
3049 : | |||
3050 : | =head3 DisplayTime | ||
3051 : | |||
3052 : | my $string = Tracer::DisplayTime($time); | ||
3053 : | parrello | 1.69 | |
3054 : | parrello | 1.97 | Convert a time value to a displayable time stamp. Whatever format this |
3055 : | method produces must be parseable by L</ParseDate>. | ||
3056 : | |||
3057 : | =over 4 | ||
3058 : | parrello | 1.69 | |
3059 : | parrello | 1.97 | =item time |
3060 : | parrello | 1.69 | |
3061 : | parrello | 1.97 | Time to display, in seconds since the epoch, or C<undef> if the time is unknown. |
3062 : | |||
3063 : | =item RETURN | ||
3064 : | parrello | 1.69 | |
3065 : | parrello | 1.97 | Returns a displayable time, or C<(n/a)> if the incoming time is undefined. |
3066 : | parrello | 1.69 | |
3067 : | parrello | 1.97 | =back |
3068 : | parrello | 1.69 | |
3069 : | parrello | 1.93 | =cut |
3070 : | parrello | 1.69 | |
3071 : | parrello | 1.97 | sub DisplayTime { |
3072 : | my ($time) = @_; | ||
3073 : | my $retVal = "(n/a)"; | ||
3074 : | if (defined $time) { | ||
3075 : | my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($time); | ||
3076 : | $retVal = _p2($mon+1) . "/" . _p2($mday) . "/" . ($year + 1900) . " " . | ||
3077 : | _p2($hour) . ":" . _p2($min) . ":" . _p2($sec); | ||
3078 : | } | ||
3079 : | parrello | 1.93 | return $retVal; |
3080 : | } | ||
3081 : | parrello | 1.69 | |
3082 : | parrello | 1.93 | # Pad a number to 2 digits. |
3083 : | sub _p2 { | ||
3084 : | my ($value) = @_; | ||
3085 : | $value = "0$value" if ($value < 10); | ||
3086 : | return $value; | ||
3087 : | parrello | 1.69 | } |
3088 : | |||
3089 : | parrello | 1.93 | =head3 Escape |
3090 : | parrello | 1.69 | |
3091 : | parrello | 1.93 | my $codedString = Tracer::Escape($realString); |
3092 : | parrello | 1.69 | |
3093 : | parrello | 1.93 | Escape a string for use in a command. Tabs will be replaced by C<\t>, new-lines |
3094 : | replaced by C<\n>, carriage returns will be deleted, and backslashes will be doubled. The | ||
3095 : | result is to reverse the effect of L</UnEscape>. | ||
3096 : | parrello | 1.69 | |
3097 : | =over 4 | ||
3098 : | |||
3099 : | parrello | 1.93 | =item realString |
3100 : | parrello | 1.69 | |
3101 : | parrello | 1.93 | String to escape. |
3102 : | parrello | 1.69 | |
3103 : | =item RETURN | ||
3104 : | |||
3105 : | parrello | 1.93 | Escaped equivalent of the real string. |
3106 : | |||
3107 : | =back | ||
3108 : | |||
3109 : | =cut | ||
3110 : | |||
3111 : | sub Escape { | ||
3112 : | # Get the parameter. | ||
3113 : | my ($realString) = @_; | ||
3114 : | # Initialize the return variable. | ||
3115 : | my $retVal = ""; | ||
3116 : | # Loop through the parameter string, looking for sequences to escape. | ||
3117 : | while (length $realString > 0) { | ||
3118 : | # Look for the first sequence to escape. | ||
3119 : | if ($realString =~ /^(.*?)([\n\t\r\\])/) { | ||
3120 : | # Here we found it. The text preceding the sequence is in $1. The sequence | ||
3121 : | # itself is in $2. First, move the clear text to the return variable. | ||
3122 : | $retVal .= $1; | ||
3123 : | # Strip the processed section off the real string. | ||
3124 : | $realString = substr $realString, (length $2) + (length $1); | ||
3125 : | # Get the matched character. | ||
3126 : | my $char = $2; | ||
3127 : | # If we have a CR, we are done. | ||
3128 : | if ($char ne "\r") { | ||
3129 : | # It's not a CR, so encode the escape sequence. | ||
3130 : | $char =~ tr/\t\n/tn/; | ||
3131 : | $retVal .= "\\" . $char; | ||
3132 : | } | ||
3133 : | } else { | ||
3134 : | # Here there are no more escape sequences. The rest of the string is | ||
3135 : | # transferred unmodified. | ||
3136 : | $retVal .= $realString; | ||
3137 : | $realString = ""; | ||
3138 : | } | ||
3139 : | } | ||
3140 : | # Return the result. | ||
3141 : | return $retVal; | ||
3142 : | parrello | 1.69 | } |
3143 : | |||
3144 : | parrello | 1.93 | =head3 UnEscape |
3145 : | parrello | 1.69 | |
3146 : | parrello | 1.93 | my $realString = Tracer::UnEscape($codedString); |
3147 : | parrello | 1.69 | |
3148 : | parrello | 1.93 | Replace escape sequences with their actual equivalents. C<\t> will be replaced by |
3149 : | a tab, C<\n> by a new-line character, and C<\\> by a backslash. C<\r> codes will | ||
3150 : | be deleted. | ||
3151 : | parrello | 1.69 | |
3152 : | =over 4 | ||
3153 : | |||
3154 : | parrello | 1.93 | =item codedString |
3155 : | parrello | 1.69 | |
3156 : | parrello | 1.93 | String to un-escape. |
3157 : | parrello | 1.69 | |
3158 : | =item RETURN | ||
3159 : | |||
3160 : | parrello | 1.93 | Returns a copy of the original string with the escape sequences converted to their actual |
3161 : | values. | ||
3162 : | parrello | 1.69 | |
3163 : | =back | ||
3164 : | |||
3165 : | =cut | ||
3166 : | |||
3167 : | parrello | 1.93 | sub UnEscape { |
3168 : | # Get the parameter. | ||
3169 : | my ($codedString) = @_; | ||
3170 : | # Initialize the return variable. | ||
3171 : | my $retVal = ""; | ||
3172 : | # Only proceed if the incoming string is nonempty. | ||
3173 : | if (defined $codedString) { | ||
3174 : | # Loop through the parameter string, looking for escape sequences. We can't do | ||
3175 : | # translating because it causes problems with the escaped slash. ("\\t" becomes | ||
3176 : | # "\<tab>" no matter what we do.) | ||
3177 : | while (length $codedString > 0) { | ||
3178 : | # Look for the first escape sequence. | ||
3179 : | if ($codedString =~ /^(.*?)\\(\\|n|t|r)/) { | ||
3180 : | # Here we found it. The text preceding the sequence is in $1. The sequence | ||
3181 : | # itself is in $2. First, move the clear text to the return variable. | ||
3182 : | $retVal .= $1; | ||
3183 : | $codedString = substr $codedString, (2 + length $1); | ||
3184 : | # Get the escape value. | ||
3185 : | my $char = $2; | ||
3186 : | # If we have a "\r", we are done. | ||
3187 : | if ($char ne 'r') { | ||
3188 : | # Here it's not an 'r', so we convert it. | ||
3189 : | $char =~ tr/\\tn/\\\t\n/; | ||
3190 : | $retVal .= $char; | ||
3191 : | } | ||
3192 : | } else { | ||
3193 : | # Here there are no more escape sequences. The rest of the string is | ||
3194 : | # transferred unmodified. | ||
3195 : | $retVal .= $codedString; | ||
3196 : | $codedString = ""; | ||
3197 : | } | ||
3198 : | } | ||
3199 : | parrello | 1.35 | } |
3200 : | parrello | 1.69 | # Return the result. |
3201 : | return $retVal; | ||
3202 : | } | ||
3203 : | |||
3204 : | parrello | 1.93 | =head3 Percent |
3205 : | parrello | 1.69 | |
3206 : | parrello | 1.93 | my $percent = Tracer::Percent($number, $base); |
3207 : | parrello | 1.69 | |
3208 : | parrello | 1.93 | Returns the percent of the base represented by the given number. If the base |
3209 : | is zero, returns zero. | ||
3210 : | parrello | 1.69 | |
3211 : | =over 4 | ||
3212 : | |||
3213 : | parrello | 1.93 | =item number |
3214 : | parrello | 1.69 | |
3215 : | parrello | 1.93 | Percent numerator. |
3216 : | parrello | 1.69 | |
3217 : | parrello | 1.93 | =item base |
3218 : | parrello | 1.69 | |
3219 : | parrello | 1.93 | Percent base. |
3220 : | parrello | 1.69 | |
3221 : | parrello | 1.93 | =item RETURN |
3222 : | parrello | 1.69 | |
3223 : | parrello | 1.93 | Returns the percentage of the base represented by the numerator. |
3224 : | parrello | 1.69 | |
3225 : | =back | ||
3226 : | |||
3227 : | =cut | ||
3228 : | |||
3229 : | parrello | 1.93 | sub Percent { |
3230 : | parrello | 1.69 | # Get the parameters. |
3231 : | parrello | 1.93 | my ($number, $base) = @_; |
3232 : | # Declare the return variable. | ||
3233 : | my $retVal = 0; | ||
3234 : | # Compute the percent. | ||
3235 : | if ($base != 0) { | ||
3236 : | $retVal = $number * 100 / $base; | ||
3237 : | } | ||
3238 : | # Return the result. | ||
3239 : | return $retVal; | ||
3240 : | parrello | 1.69 | } |
3241 : | |||
3242 : | parrello | 1.115 | =head3 In |
3243 : | |||
3244 : | my $flag = Tracer::In($value, $min, $max); | ||
3245 : | |||
3246 : | Return TRUE if the value is between the minimum and the maximum, else FALSE. | ||
3247 : | |||
3248 : | =cut | ||
3249 : | |||
3250 : | sub In { | ||
3251 : | return ($_[0] <= $_[2] && $_[0] >= $_[1]); | ||
3252 : | } | ||
3253 : | |||
3254 : | |||
3255 : | parrello | 1.97 | =head3 Constrain |
3256 : | |||
3257 : | my $constrained = Constrain($value, $min, $max); | ||
3258 : | |||
3259 : | Modify a numeric value to bring it to a point in between a maximum and a minimum. | ||
3260 : | |||
3261 : | =over 4 | ||
3262 : | |||
3263 : | =item value | ||
3264 : | |||
3265 : | Value to constrain. | ||
3266 : | |||
3267 : | =item min (optional) | ||
3268 : | |||
3269 : | Minimum permissible value. If this parameter is undefined, no minimum constraint will be applied. | ||
3270 : | |||
3271 : | =item max (optional) | ||
3272 : | |||
3273 : | Maximum permissible value. If this parameter is undefined, no maximum constraint will be applied. | ||
3274 : | |||
3275 : | =item RETURN | ||
3276 : | |||
3277 : | Returns the incoming value, constrained according to the other parameters. | ||
3278 : | |||
3279 : | =back | ||
3280 : | |||
3281 : | =cut | ||
3282 : | |||
3283 : | sub Constrain { | ||
3284 : | # Get the parameters. | ||
3285 : | my ($value, $min, $max) = @_; | ||
3286 : | # Declare the return variable. | ||
3287 : | my $retVal = $value; | ||
3288 : | # Apply the minimum constraint. | ||
3289 : | if (defined $min && $retVal < $min) { | ||
3290 : | $retVal = $min; | ||
3291 : | } | ||
3292 : | # Apply the maximum constraint. | ||
3293 : | if (defined $max && $retVal > $max) { | ||
3294 : | $retVal = $max; | ||
3295 : | } | ||
3296 : | # Return the result. | ||
3297 : | return $retVal; | ||
3298 : | } | ||
3299 : | |||
3300 : | parrello | 1.93 | =head3 Min |
3301 : | parrello | 1.69 | |
3302 : | parrello | 1.93 | my $min = Min($value1, $value2, ... $valueN); |
3303 : | parrello | 1.69 | |
3304 : | parrello | 1.93 | Return the minimum argument. The arguments are treated as numbers. |
3305 : | parrello | 1.69 | |
3306 : | =over 4 | ||
3307 : | |||
3308 : | parrello | 1.93 | =item $value1, $value2, ... $valueN |
3309 : | parrello | 1.69 | |
3310 : | parrello | 1.93 | List of numbers to compare. |
3311 : | parrello | 1.69 | |
3312 : | =item RETURN | ||
3313 : | |||
3314 : | parrello | 1.93 | Returns the lowest number in the list. |
3315 : | parrello | 1.69 | |
3316 : | =back | ||
3317 : | |||
3318 : | =cut | ||
3319 : | |||
3320 : | parrello | 1.93 | sub Min { |
3321 : | # Get the parameters. Note that we prime the return value with the first parameter. | ||
3322 : | my ($retVal, @values) = @_; | ||
3323 : | # Loop through the remaining parameters, looking for the lowest. | ||
3324 : | for my $value (@values) { | ||
3325 : | if ($value < $retVal) { | ||
3326 : | $retVal = $value; | ||
3327 : | parrello | 1.72 | } |
3328 : | } | ||
3329 : | parrello | 1.93 | # Return the minimum found. |
3330 : | parrello | 1.70 | return $retVal; |
3331 : | parrello | 1.35 | } |
3332 : | |||
3333 : | parrello | 1.93 | =head3 Max |
3334 : | |||
3335 : | my $max = Max($value1, $value2, ... $valueN); | ||
3336 : | parrello | 1.69 | |
3337 : | parrello | 1.93 | Return the maximum argument. The arguments are treated as numbers. |
3338 : | parrello | 1.65 | |
3339 : | parrello | 1.93 | =over 4 |
3340 : | parrello | 1.65 | |
3341 : | parrello | 1.93 | =item $value1, $value2, ... $valueN |
3342 : | parrello | 1.65 | |
3343 : | parrello | 1.93 | List of numbers to compare. |
3344 : | parrello | 1.65 | |
3345 : | parrello | 1.93 | =item RETURN |
3346 : | parrello | 1.65 | |
3347 : | parrello | 1.93 | Returns the highest number in the list. |
3348 : | parrello | 1.65 | |
3349 : | =back | ||
3350 : | |||
3351 : | =cut | ||
3352 : | |||
3353 : | parrello | 1.93 | sub Max { |
3354 : | # Get the parameters. Note that we prime the return value with the first parameter. | ||
3355 : | my ($retVal, @values) = @_; | ||
3356 : | # Loop through the remaining parameters, looking for the highest. | ||
3357 : | for my $value (@values) { | ||
3358 : | if ($value > $retVal) { | ||
3359 : | $retVal = $value; | ||
3360 : | parrello | 1.65 | } |
3361 : | } | ||
3362 : | parrello | 1.93 | # Return the maximum found. |
3363 : | return $retVal; | ||
3364 : | parrello | 1.65 | } |
3365 : | |||
3366 : | parrello | 1.93 | =head3 Strip |
3367 : | parrello | 1.37 | |
3368 : | parrello | 1.93 | my $string = Tracer::Strip($line); |
3369 : | parrello | 1.37 | |
3370 : | parrello | 1.93 | Strip all line terminators off a string. This is necessary when dealing with files |
3371 : | that may have been transferred back and forth several times among different | ||
3372 : | operating environments. | ||
3373 : | parrello | 1.37 | |
3374 : | =over 4 | ||
3375 : | |||
3376 : | parrello | 1.93 | =item line |
3377 : | parrello | 1.37 | |
3378 : | parrello | 1.93 | Line of text to be stripped. |
3379 : | parrello | 1.37 | |
3380 : | parrello | 1.93 | =item RETURN |
3381 : | parrello | 1.91 | |
3382 : | parrello | 1.93 | The same line of text with all the line-ending characters chopped from the end. |
3383 : | parrello | 1.91 | |
3384 : | parrello | 1.37 | =back |
3385 : | |||
3386 : | =cut | ||
3387 : | |||
3388 : | parrello | 1.93 | sub Strip { |
3389 : | # Get a copy of the parameter string. | ||
3390 : | my ($string) = @_; | ||
3391 : | my $retVal = (defined $string ? $string : ""); | ||
3392 : | # Strip the line terminator characters. | ||
3393 : | $retVal =~ s/(\r|\n)+$//g; | ||
3394 : | # Return the result. | ||
3395 : | return $retVal; | ||
3396 : | parrello | 1.43 | } |
3397 : | |||
3398 : | parrello | 1.115 | =head3 Trim |
3399 : | |||
3400 : | my $string = Tracer::Trim($line); | ||
3401 : | |||
3402 : | Trim all spaces from the beginning and ending of a string. | ||
3403 : | |||
3404 : | =over 4 | ||
3405 : | |||
3406 : | =item line | ||
3407 : | |||
3408 : | Line of text to be trimmed. | ||
3409 : | |||
3410 : | =item RETURN | ||
3411 : | |||
3412 : | The same line of text with all whitespace chopped off either end. | ||
3413 : | |||
3414 : | =back | ||
3415 : | |||
3416 : | =cut | ||
3417 : | |||
3418 : | sub Trim { | ||
3419 : | # Get a copy of the parameter string. | ||
3420 : | my ($string) = @_; | ||
3421 : | my $retVal = (defined $string ? $string : ""); | ||
3422 : | # Strip the front spaces. | ||
3423 : | $retVal =~ s/^\s+//; | ||
3424 : | # Strip the back spaces. | ||
3425 : | $retVal =~ s/\s+$//; | ||
3426 : | # Return the result. | ||
3427 : | return $retVal; | ||
3428 : | } | ||
3429 : | |||
3430 : | parrello | 1.93 | =head3 Pad |
3431 : | parrello | 1.43 | |
3432 : | parrello | 1.93 | my $paddedString = Tracer::Pad($string, $len, $left, $padChar); |
3433 : | parrello | 1.43 | |
3434 : | parrello | 1.93 | Pad a string to a specified length. The pad character will be a |
3435 : | space, and the padding will be on the right side unless specified | ||
3436 : | in the third parameter. | ||
3437 : | parrello | 1.43 | |
3438 : | =over 4 | ||
3439 : | |||
3440 : | parrello | 1.93 | =item string |
3441 : | |||
3442 : | String to be padded. | ||
3443 : | |||
3444 : | =item len | ||
3445 : | |||
3446 : | Desired length of the padded string. | ||
3447 : | |||
3448 : | =item left (optional) | ||
3449 : | |||
3450 : | TRUE if the string is to be left-padded; otherwise it will be padded on the right. | ||
3451 : | |||
3452 : | =item padChar (optional) | ||
3453 : | |||
3454 : | Character to use for padding. The default is a space. | ||
3455 : | |||
3456 : | =item RETURN | ||
3457 : | parrello | 1.43 | |
3458 : | parrello | 1.93 | Returns a copy of the original string with the pad character added to the |
3459 : | specified end so that it achieves the desired length. | ||
3460 : | parrello | 1.43 | |
3461 : | =back | ||
3462 : | |||
3463 : | =cut | ||
3464 : | |||
3465 : | parrello | 1.93 | sub Pad { |
3466 : | # Get the parameters. | ||
3467 : | my ($string, $len, $left, $padChar) = @_; | ||
3468 : | # Compute the padding character. | ||
3469 : | if (! defined $padChar) { | ||
3470 : | $padChar = " "; | ||
3471 : | } | ||
3472 : | # Compute the number of spaces needed. | ||
3473 : | my $needed = $len - length $string; | ||
3474 : | # Copy the string into the return variable. | ||
3475 : | my $retVal = $string; | ||
3476 : | # Only proceed if padding is needed. | ||
3477 : | if ($needed > 0) { | ||
3478 : | # Create the pad string. | ||
3479 : | my $pad = $padChar x $needed; | ||
3480 : | # Affix it to the return value. | ||
3481 : | if ($left) { | ||
3482 : | $retVal = $pad . $retVal; | ||
3483 : | } else { | ||
3484 : | $retVal .= $pad; | ||
3485 : | parrello | 1.43 | } |
3486 : | parrello | 1.37 | } |
3487 : | parrello | 1.93 | # Return the result. |
3488 : | return $retVal; | ||
3489 : | parrello | 1.37 | } |
3490 : | |||
3491 : | parrello | 1.118 | =head3 Quoted |
3492 : | |||
3493 : | my $string = Tracer::Quoted($var); | ||
3494 : | |||
3495 : | Convert the specified value to a string and enclose it in single quotes. | ||
3496 : | If it's undefined, the string C<undef> in angle brackets will be used | ||
3497 : | instead. | ||
3498 : | |||
3499 : | =over 4 | ||
3500 : | |||
3501 : | =item var | ||
3502 : | |||
3503 : | Value to quote. | ||
3504 : | |||
3505 : | =item RETURN | ||
3506 : | |||
3507 : | Returns a string enclosed in quotes, or an indication the value is undefined. | ||
3508 : | |||
3509 : | =back | ||
3510 : | |||
3511 : | =cut | ||
3512 : | |||
3513 : | sub Quoted { | ||
3514 : | # Get the parameters. | ||
3515 : | my ($var) = @_; | ||
3516 : | # Declare the return variable. | ||
3517 : | my $retVal; | ||
3518 : | # Are we undefined? | ||
3519 : | if (! defined $var) { | ||
3520 : | $retVal = "<undef>"; | ||
3521 : | } else { | ||
3522 : | # No, so convert to a string and enclose in quotes. | ||
3523 : | $retVal = $var; | ||
3524 : | $retVal =~ s/'/\\'/; | ||
3525 : | $retVal = "'$retVal'"; | ||
3526 : | } | ||
3527 : | # Return the result. | ||
3528 : | return $retVal; | ||
3529 : | } | ||
3530 : | |||
3531 : | parrello | 1.93 | =head3 EOF |
3532 : | |||
3533 : | This is a constant that is lexically greater than any useful string. | ||
3534 : | |||
3535 : | =cut | ||
3536 : | |||
3537 : | sub EOF { | ||
3538 : | return "\xFF\xFF\xFF\xFF\xFF"; | ||
3539 : | } | ||
3540 : | parrello | 1.59 | |
3541 : | parrello | 1.93 | =head3 TICK |
3542 : | parrello | 1.59 | |
3543 : | parrello | 1.93 | my @results = TICK($commandString); |
3544 : | parrello | 1.59 | |
3545 : | parrello | 1.93 | Perform a back-tick operation on a command. If this is a Windows environment, any leading |
3546 : | dot-slash (C<./> will be removed. So, for example, if you were doing | ||
3547 : | parrello | 1.59 | |
3548 : | parrello | 1.93 | `./protein.cgi` |
3549 : | parrello | 1.59 | |
3550 : | parrello | 1.93 | from inside a CGI script, it would work fine in Unix, but would issue an error message |
3551 : | in Windows complaining that C<'.'> is not a valid command. If instead you code | ||
3552 : | parrello | 1.59 | |
3553 : | parrello | 1.93 | TICK("./protein.cgi") |
3554 : | parrello | 1.59 | |
3555 : | parrello | 1.93 | it will work correctly in both environments. |
3556 : | parrello | 1.59 | |
3557 : | parrello | 1.93 | =over 4 |
3558 : | parrello | 1.59 | |
3559 : | parrello | 1.93 | =item commandString |
3560 : | parrello | 1.59 | |
3561 : | parrello | 1.93 | The command string to pass to the system. |
3562 : | parrello | 1.59 | |
3563 : | =item RETURN | ||
3564 : | |||
3565 : | parrello | 1.93 | Returns the standard output from the specified command, as a list. |
3566 : | parrello | 1.59 | |
3567 : | =back | ||
3568 : | |||
3569 : | =cut | ||
3570 : | parrello | 1.93 | #: Return Type @; |
3571 : | sub TICK { | ||
3572 : | parrello | 1.59 | # Get the parameters. |
3573 : | parrello | 1.93 | my ($commandString) = @_; |
3574 : | # Chop off the dot-slash if this is Windows. | ||
3575 : | if ($FIG_Config::win_mode) { | ||
3576 : | $commandString =~ s!^\./!!; | ||
3577 : | parrello | 1.59 | } |
3578 : | parrello | 1.93 | # Activate the command and return the result. |
3579 : | return `$commandString`; | ||
3580 : | parrello | 1.59 | } |
3581 : | |||
3582 : | parrello | 1.93 | |
3583 : | parrello | 1.55 | =head3 CommaFormat |
3584 : | |||
3585 : | parrello | 1.92 | my $formatted = Tracer::CommaFormat($number); |
3586 : | parrello | 1.55 | |
3587 : | Insert commas into a number. | ||
3588 : | |||
3589 : | =over 4 | ||
3590 : | |||
3591 : | =item number | ||
3592 : | |||
3593 : | A sequence of digits. | ||
3594 : | |||
3595 : | =item RETURN | ||
3596 : | |||
3597 : | Returns the same digits with commas strategically inserted. | ||
3598 : | |||
3599 : | =back | ||
3600 : | |||
3601 : | =cut | ||
3602 : | |||
3603 : | sub CommaFormat { | ||
3604 : | # Get the parameters. | ||
3605 : | my ($number) = @_; | ||
3606 : | # Pad the length up to a multiple of three. | ||
3607 : | my $padded = "$number"; | ||
3608 : | $padded = " " . $padded while length($padded) % 3 != 0; | ||
3609 : | # This is a fancy PERL trick. The parentheses in the SPLIT pattern | ||
3610 : | # cause the delimiters to be included in the output stream. The | ||
3611 : | # GREP removes the empty strings in between the delimiters. | ||
3612 : | my $retVal = join(",", grep { $_ ne '' } split(/(...)/, $padded)); | ||
3613 : | # Clean out the spaces. | ||
3614 : | $retVal =~ s/ //g; | ||
3615 : | # Return the result. | ||
3616 : | return $retVal; | ||
3617 : | } | ||
3618 : | parrello | 1.46 | |
3619 : | |||
3620 : | parrello | 1.117 | =head3 GetMemorySize |
3621 : | |||
3622 : | my $string = Tracer::GetMemorySize(); | ||
3623 : | |||
3624 : | Return a memory size string for the current process. The string will be | ||
3625 : | in comma format, with a size indicator (K, M, G) at the end. | ||
3626 : | |||
3627 : | =cut | ||
3628 : | |||
3629 : | sub GetMemorySize { | ||
3630 : | # Get the memory size from Unix. | ||
3631 : | my ($retVal) = `ps h -o vsz $$`; | ||
3632 : | # Remove the ending new-line. | ||
3633 : | chomp $retVal; | ||
3634 : | # Format and return the result. | ||
3635 : | return CommaFormat($retVal) . "K"; | ||
3636 : | } | ||
3637 : | |||
3638 : | parrello | 1.62 | =head3 CompareLists |
3639 : | |||
3640 : | parrello | 1.92 | my ($inserted, $deleted) = Tracer::CompareLists(\@newList, \@oldList, $keyIndex); |
3641 : | parrello | 1.62 | |
3642 : | Compare two lists of tuples, and return a hash analyzing the differences. The lists | ||
3643 : | are presumed to be sorted alphabetically by the value in the $keyIndex column. | ||
3644 : | The return value contains a list of items that are only in the new list | ||
3645 : | (inserted) and only in the old list (deleted). | ||
3646 : | |||
3647 : | =over 4 | ||
3648 : | |||
3649 : | =item newList | ||
3650 : | |||
3651 : | Reference to a list of new tuples. | ||
3652 : | |||
3653 : | =item oldList | ||
3654 : | |||
3655 : | Reference to a list of old tuples. | ||
3656 : | |||
3657 : | =item keyIndex (optional) | ||
3658 : | |||
3659 : | Index into each tuple of its key field. The default is 0. | ||
3660 : | |||
3661 : | =item RETURN | ||
3662 : | |||
3663 : | Returns a 2-tuple consisting of a reference to the list of items that are only in the new | ||
3664 : | list (inserted) followed by a reference to the list of items that are only in the old | ||
3665 : | list (deleted). | ||
3666 : | |||
3667 : | =back | ||
3668 : | |||
3669 : | =cut | ||
3670 : | |||
3671 : | sub CompareLists { | ||
3672 : | # Get the parameters. | ||
3673 : | my ($newList, $oldList, $keyIndex) = @_; | ||
3674 : | if (! defined $keyIndex) { | ||
3675 : | $keyIndex = 0; | ||
3676 : | } | ||
3677 : | # Declare the return variables. | ||
3678 : | my ($inserted, $deleted) = ([], []); | ||
3679 : | # Loop through the two lists simultaneously. | ||
3680 : | my ($newI, $oldI) = (0, 0); | ||
3681 : | my ($newN, $oldN) = (scalar @{$newList}, scalar @{$oldList}); | ||
3682 : | while ($newI < $newN || $oldI < $oldN) { | ||
3683 : | # Get the current object in each list. Note that if one | ||
3684 : | # of the lists is past the end, we'll get undef. | ||
3685 : | my $newItem = $newList->[$newI]; | ||
3686 : | my $oldItem = $oldList->[$oldI]; | ||
3687 : | parrello | 1.63 | if (! defined($newItem) || defined($oldItem) && $newItem->[$keyIndex] gt $oldItem->[$keyIndex]) { |
3688 : | parrello | 1.62 | # The old item is not in the new list, so mark it deleted. |
3689 : | push @{$deleted}, $oldItem; | ||
3690 : | $oldI++; | ||
3691 : | } elsif (! defined($oldItem) || $oldItem->[$keyIndex] gt $newItem->[$keyIndex]) { | ||
3692 : | # The new item is not in the old list, so mark it inserted. | ||
3693 : | push @{$inserted}, $newItem; | ||
3694 : | $newI++; | ||
3695 : | } else { | ||
3696 : | # The item is in both lists, so push forward. | ||
3697 : | $oldI++; | ||
3698 : | $newI++; | ||
3699 : | } | ||
3700 : | } | ||
3701 : | # Return the result. | ||
3702 : | return ($inserted, $deleted); | ||
3703 : | } | ||
3704 : | |||
3705 : | parrello | 1.105 | =head3 Cmp |
3706 : | |||
3707 : | my $cmp = Tracer::Cmp($a, $b); | ||
3708 : | |||
3709 : | This method performs a universal sort comparison. Each value coming in is | ||
3710 : | parrello | 1.121 | separated into a text parts and number parts. The text |
3711 : | parrello | 1.105 | part is string compared, and if both parts are equal, then the number |
3712 : | parts are compared numerically. A stream of just numbers or a stream of | ||
3713 : | just strings will sort correctly, and a mixed stream will sort with the | ||
3714 : | numbers first. Strings with a label and a number will sort in the | ||
3715 : | parrello | 1.121 | expected manner instead of lexically. Undefined values sort last. |
3716 : | parrello | 1.105 | |
3717 : | =over 4 | ||
3718 : | |||
3719 : | =item a | ||
3720 : | |||
3721 : | First item to compare. | ||
3722 : | |||
3723 : | =item b | ||
3724 : | |||
3725 : | Second item to compare. | ||
3726 : | |||
3727 : | =item RETURN | ||
3728 : | |||
3729 : | Returns a negative number if the first item should sort first (is less), a positive | ||
3730 : | number if the first item should sort second (is greater), and a zero if the items are | ||
3731 : | equal. | ||
3732 : | |||
3733 : | =back | ||
3734 : | |||
3735 : | =cut | ||
3736 : | |||
3737 : | sub Cmp { | ||
3738 : | # Get the parameters. | ||
3739 : | my ($a, $b) = @_; | ||
3740 : | # Declare the return value. | ||
3741 : | my $retVal; | ||
3742 : | # Check for nulls. | ||
3743 : | if (! defined($a)) { | ||
3744 : | $retVal = (! defined($b) ? 0 : -1); | ||
3745 : | } elsif (! defined($b)) { | ||
3746 : | $retVal = 1; | ||
3747 : | } else { | ||
3748 : | # Here we have two real values. Parse the two strings. | ||
3749 : | parrello | 1.121 | my @aParsed = _Parse($a); |
3750 : | my @bParsed = _Parse($b); | ||
3751 : | # Loop through the first string. | ||
3752 : | while (! $retVal && @aParsed) { | ||
3753 : | # Extract the string parts. | ||
3754 : | my $aPiece = shift(@aParsed); | ||
3755 : | my $bPiece = shift(@bParsed) || ''; | ||
3756 : | # Extract the number parts. | ||
3757 : | my $aNum = shift(@aParsed); | ||
3758 : | my $bNum = shift(@bParsed) || 0; | ||
3759 : | # Compare the string parts insensitively. | ||
3760 : | $retVal = (lc($aPiece) cmp lc($bPiece)); | ||
3761 : | # If they're equal, compare them sensitively. | ||
3762 : | if (! $retVal) { | ||
3763 : | $retVal = ($aPiece cmp $bPiece); | ||
3764 : | # If they're STILL equal, compare the number parts. | ||
3765 : | if (! $retVal) { | ||
3766 : | $retVal = $aNum <=> $bNum; | ||
3767 : | } | ||
3768 : | } | ||
3769 : | parrello | 1.105 | } |
3770 : | } | ||
3771 : | # Return the result. | ||
3772 : | return $retVal; | ||
3773 : | } | ||
3774 : | |||
3775 : | parrello | 1.121 | # This method parses an input string into a string parts alternating with |
3776 : | # number parts. | ||
3777 : | parrello | 1.115 | sub _Parse { |
3778 : | parrello | 1.121 | # Get the incoming string. |
3779 : | parrello | 1.115 | my ($string) = @_; |
3780 : | parrello | 1.121 | # The pieces will be put in here. |
3781 : | my @retVal; | ||
3782 : | # Loop through as many alpha/num sets as we can. | ||
3783 : | while ($string =~ /^(\D*)(\d+)(.*)/) { | ||
3784 : | # Push the alpha and number parts into the return string. | ||
3785 : | push @retVal, $1, $2; | ||
3786 : | # Save the residual. | ||
3787 : | $string = $3; | ||
3788 : | } | ||
3789 : | # If there's still stuff left, add it to the end with a trailing | ||
3790 : | # zero. | ||
3791 : | if ($string) { | ||
3792 : | push @retVal, $string, 0; | ||
3793 : | parrello | 1.115 | } |
3794 : | parrello | 1.121 | # Return the list. |
3795 : | return @retVal; | ||
3796 : | parrello | 1.115 | } |
3797 : | |||
3798 : | parrello | 1.108 | =head3 ListEQ |
3799 : | |||
3800 : | my $flag = Tracer::ListEQ(\@a, \@b); | ||
3801 : | |||
3802 : | Return TRUE if the specified lists contain the same strings in the same | ||
3803 : | order, else FALSE. | ||
3804 : | |||
3805 : | =over 4 | ||
3806 : | |||
3807 : | =item a | ||
3808 : | |||
3809 : | Reference to the first list. | ||
3810 : | |||
3811 : | =item b | ||
3812 : | |||
3813 : | Reference to the second list. | ||
3814 : | |||
3815 : | =item RETURN | ||
3816 : | |||
3817 : | Returns TRUE if the two parameters are identical string lists, else FALSE. | ||
3818 : | |||
3819 : | =back | ||
3820 : | |||
3821 : | =cut | ||
3822 : | |||
3823 : | sub ListEQ { | ||
3824 : | # Get the parameters. | ||
3825 : | my ($a, $b) = @_; | ||
3826 : | # Declare the return variable. Start by checking the lengths. | ||
3827 : | my $n = scalar(@$a); | ||
3828 : | my $retVal = ($n == scalar(@$b)); | ||
3829 : | # Now compare the list elements. | ||
3830 : | for (my $i = 0; $retVal && $i < $n; $i++) { | ||
3831 : | $retVal = ($a->[$i] eq $b->[$i]); | ||
3832 : | } | ||
3833 : | # Return the result. | ||
3834 : | return $retVal; | ||
3835 : | } | ||
3836 : | |||
3837 : | parrello | 1.105 | =head2 CGI Script Utilities |
3838 : | |||
3839 : | =head3 ScriptSetup (deprecated) | ||
3840 : | |||
3841 : | my ($cgi, $varHash) = ScriptSetup($noTrace); | ||
3842 : | |||
3843 : | Perform standard tracing and debugging setup for scripts. The value returned is | ||
3844 : | the CGI object followed by a pre-built variable hash. At the end of the script, | ||
3845 : | the client should call L</ScriptFinish> to output the web page. | ||
3846 : | |||
3847 : | This method calls L</ETracing> to configure tracing, which allows the tracing | ||
3848 : | to be configured via the emergency tracing form on the debugging control panel. | ||
3849 : | Tracing will then be turned on automatically for all programs that use the L</ETracing> | ||
3850 : | method, which includes every program that uses this method or L</StandardSetup>. | ||
3851 : | |||
3852 : | =over 4 | ||
3853 : | |||
3854 : | =item noTrace (optional) | ||
3855 : | |||
3856 : | If specified, tracing will be suppressed. This is useful if the script wants to set up | ||
3857 : | tracing manually. | ||
3858 : | |||
3859 : | =item RETURN | ||
3860 : | |||
3861 : | Returns a two-element list consisting of a CGI query object and a variable hash for | ||
3862 : | the output page. | ||
3863 : | |||
3864 : | =back | ||
3865 : | |||
3866 : | =cut | ||
3867 : | |||
3868 : | sub ScriptSetup { | ||
3869 : | # Get the parameters. | ||
3870 : | my ($noTrace) = @_; | ||
3871 : | # Get the CGI query object. | ||
3872 : | my $cgi = CGI->new(); | ||
3873 : | # Set up tracing if it's not suppressed. | ||
3874 : | ETracing($cgi) unless $noTrace; | ||
3875 : | # Create the variable hash. | ||
3876 : | my $varHash = { results => '' }; | ||
3877 : | # Return the query object and variable hash. | ||
3878 : | return ($cgi, $varHash); | ||
3879 : | } | ||
3880 : | |||
3881 : | =head3 ScriptFinish (deprecated) | ||
3882 : | |||
3883 : | ScriptFinish($webData, $varHash); | ||
3884 : | |||
3885 : | Output a web page at the end of a script. Either the string to be output or the | ||
3886 : | name of a template file can be specified. If the second parameter is omitted, | ||
3887 : | it is assumed we have a string to be output; otherwise, it is assumed we have the | ||
3888 : | name of a template file. The template should have the variable C<DebugData> | ||
3889 : | specified in any form that invokes a standard script. If debugging mode is turned | ||
3890 : | on, a form field will be put in that allows the user to enter tracing data. | ||
3891 : | Trace messages will be placed immediately before the terminal C<BODY> tag in | ||
3892 : | the output, formatted as a list. | ||
3893 : | |||
3894 : | A typical standard script would loook like the following. | ||
3895 : | |||
3896 : | BEGIN { | ||
3897 : | # Print the HTML header. | ||
3898 : | print "CONTENT-TYPE: text/html\n\n"; | ||
3899 : | } | ||
3900 : | use Tracer; | ||
3901 : | use CGI; | ||
3902 : | use FIG; | ||
3903 : | # ... more uses ... | ||
3904 : | |||
3905 : | my ($cgi, $varHash) = ScriptSetup(); | ||
3906 : | eval { | ||
3907 : | # ... get data from $cgi, put it in $varHash ... | ||
3908 : | }; | ||
3909 : | if ($@) { | ||
3910 : | Trace("Script Error: $@") if T(0); | ||
3911 : | } | ||
3912 : | ScriptFinish("Html/MyTemplate.html", $varHash); | ||
3913 : | |||
3914 : | The idea here is that even if the script fails, you'll see trace messages and | ||
3915 : | useful output. | ||
3916 : | |||
3917 : | =over 4 | ||
3918 : | |||
3919 : | =item webData | ||
3920 : | |||
3921 : | A string containing either the full web page to be written to the output or the | ||
3922 : | name of a template file from which the page is to be constructed. If the name | ||
3923 : | of a template file is specified, then the second parameter must be present; | ||
3924 : | otherwise, it must be absent. | ||
3925 : | |||
3926 : | =item varHash (optional) | ||
3927 : | |||
3928 : | If specified, then a reference to a hash mapping variable names for a template | ||
3929 : | to their values. The template file will be read into memory, and variable markers | ||
3930 : | will be replaced by data in this hash reference. | ||
3931 : | |||
3932 : | =back | ||
3933 : | |||
3934 : | =cut | ||
3935 : | |||
3936 : | sub ScriptFinish { | ||
3937 : | # Get the parameters. | ||
3938 : | my ($webData, $varHash) = @_; | ||
3939 : | # Check for a template file situation. | ||
3940 : | my $outputString; | ||
3941 : | if (defined $varHash) { | ||
3942 : | # Here we have a template file. We need to determine the template type. | ||
3943 : | my $template; | ||
3944 : | if ($FIG_Config::template_url && $webData =~ /\.php$/) { | ||
3945 : | $template = "$FIG_Config::template_url/$webData"; | ||
3946 : | } else { | ||
3947 : | $template = "<<$webData"; | ||
3948 : | } | ||
3949 : | $outputString = PageBuilder::Build($template, $varHash, "Html"); | ||
3950 : | } else { | ||
3951 : | # Here the user gave us a raw string. | ||
3952 : | $outputString = $webData; | ||
3953 : | } | ||
3954 : | # Check for trace messages. | ||
3955 : | if ($Destination ne "NONE" && $TraceLevel > 0) { | ||
3956 : | # We have trace messages, so we want to put them at the end of the body. This | ||
3957 : | # is either at the end of the whole string or at the beginning of the BODY | ||
3958 : | # end-tag. | ||
3959 : | my $pos = length $outputString; | ||
3960 : | if ($outputString =~ m#</body>#gi) { | ||
3961 : | $pos = (pos $outputString) - 7; | ||
3962 : | } | ||
3963 : | # If the trace messages were queued, we unroll them. Otherwise, we display the | ||
3964 : | # destination. | ||
3965 : | my $traceHtml; | ||
3966 : | if ($Destination eq "QUEUE") { | ||
3967 : | $traceHtml = QTrace('Html'); | ||
3968 : | } elsif ($Destination =~ /^>>(.+)$/) { | ||
3969 : | # Here the tracing output it to a file. We code it as a hyperlink so the user | ||
3970 : | # can copy the file name into the clipboard easily. | ||
3971 : | my $actualDest = $1; | ||
3972 : | $traceHtml = "<p>Tracing output to $actualDest.</p>\n"; | ||
3973 : | } else { | ||
3974 : | # Here we have one of the special destinations. | ||
3975 : | $traceHtml = "<P>Tracing output type is $Destination.</p>\n"; | ||
3976 : | } | ||
3977 : | substr $outputString, $pos, 0, $traceHtml; | ||
3978 : | } | ||
3979 : | # Write the output string. | ||
3980 : | print $outputString; | ||
3981 : | } | ||
3982 : | |||
3983 : | parrello | 1.65 | =head3 GenerateURL |
3984 : | |||
3985 : | parrello | 1.92 | my $queryUrl = Tracer::GenerateURL($page, %parameters); |
3986 : | parrello | 1.65 | |
3987 : | Generate a GET-style URL for the specified page with the specified parameter | ||
3988 : | names and values. The values will be URL-escaped automatically. So, for | ||
3989 : | example | ||
3990 : | |||
3991 : | Tracer::GenerateURL("form.cgi", type => 1, string => "\"high pass\" or highway") | ||
3992 : | |||
3993 : | would return | ||
3994 : | |||
3995 : | parrello | 1.79 | form.cgi?type=1;string=%22high%20pass%22%20or%20highway |
3996 : | parrello | 1.65 | |
3997 : | =over 4 | ||
3998 : | |||
3999 : | =item page | ||
4000 : | |||
4001 : | Page URL. | ||
4002 : | |||
4003 : | =item parameters | ||
4004 : | |||
4005 : | Hash mapping parameter names to parameter values. | ||
4006 : | |||
4007 : | =item RETURN | ||
4008 : | |||
4009 : | Returns a GET-style URL that goes to the specified page and passes in the | ||
4010 : | specified parameters and values. | ||
4011 : | |||
4012 : | =back | ||
4013 : | |||
4014 : | =cut | ||
4015 : | |||
4016 : | sub GenerateURL { | ||
4017 : | # Get the parameters. | ||
4018 : | my ($page, %parameters) = @_; | ||
4019 : | # Prime the return variable with the page URL. | ||
4020 : | my $retVal = $page; | ||
4021 : | # Loop through the parameters, creating parameter elements in a list. | ||
4022 : | my @parmList = map { "$_=" . uri_escape($parameters{$_}) } keys %parameters; | ||
4023 : | # If the list is nonempty, tack it on. | ||
4024 : | if (@parmList) { | ||
4025 : | parrello | 1.79 | $retVal .= "?" . join(";", @parmList); |
4026 : | parrello | 1.65 | } |
4027 : | # Return the result. | ||
4028 : | return $retVal; | ||
4029 : | } | ||
4030 : | |||
4031 : | parrello | 1.78 | =head3 ApplyURL |
4032 : | |||
4033 : | parrello | 1.92 | Tracer::ApplyURL($table, $target, $url); |
4034 : | parrello | 1.78 | |
4035 : | Run through a two-dimensional table (or more accurately, a list of lists), converting the | ||
4036 : | I<$target> column to HTML text having a hyperlink to a URL in the I<$url> column. The | ||
4037 : | URL column will be deleted by this process and the target column will be HTML-escaped. | ||
4038 : | |||
4039 : | This provides a simple way to process the results of a database query into something | ||
4040 : | displayable by combining a URL with text. | ||
4041 : | |||
4042 : | =over 4 | ||
4043 : | |||
4044 : | =item table | ||
4045 : | |||
4046 : | Reference to a list of lists. The elements in the containing list will be updated by | ||
4047 : | this method. | ||
4048 : | |||
4049 : | =item target | ||
4050 : | |||
4051 : | The index of the column to be converted into HTML. | ||
4052 : | |||
4053 : | =item url | ||
4054 : | |||
4055 : | The index of the column containing the URL. Note that the URL must have a recognizable | ||
4056 : | C<http:> at the beginning. | ||
4057 : | |||
4058 : | =back | ||
4059 : | |||
4060 : | =cut | ||
4061 : | |||
4062 : | sub ApplyURL { | ||
4063 : | # Get the parameters. | ||
4064 : | my ($table, $target, $url) = @_; | ||
4065 : | # Loop through the table. | ||
4066 : | for my $row (@{$table}) { | ||
4067 : | # Apply the URL to the target cell. | ||
4068 : | $row->[$target] = CombineURL($row->[$target], $row->[$url]); | ||
4069 : | # Delete the URL from the row. | ||
4070 : | delete $row->[$url]; | ||
4071 : | } | ||
4072 : | } | ||
4073 : | |||
4074 : | =head3 CombineURL | ||
4075 : | |||
4076 : | parrello | 1.92 | my $combinedHtml = Tracer::CombineURL($text, $url); |
4077 : | parrello | 1.78 | |
4078 : | This method will convert the specified text into HTML hyperlinked to the specified | ||
4079 : | URL. The hyperlinking will only take place if the URL looks legitimate: that is, it | ||
4080 : | is defined and begins with an C<http:> header. | ||
4081 : | |||
4082 : | =over 4 | ||
4083 : | |||
4084 : | =item text | ||
4085 : | |||
4086 : | Text to return. This will be HTML-escaped automatically. | ||
4087 : | |||
4088 : | =item url | ||
4089 : | |||
4090 : | A URL to be hyperlinked to the text. If it does not look like a URL, then the text | ||
4091 : | will be returned without any hyperlinking. | ||
4092 : | |||
4093 : | =item RETURN | ||
4094 : | |||
4095 : | Returns the original text, HTML-escaped, with the URL hyperlinked to it. If the URL | ||
4096 : | doesn't look right, the HTML-escaped text will be returned without any further | ||
4097 : | modification. | ||
4098 : | |||
4099 : | =back | ||
4100 : | |||
4101 : | =cut | ||
4102 : | |||
4103 : | sub CombineURL { | ||
4104 : | # Get the parameters. | ||
4105 : | my ($text, $url) = @_; | ||
4106 : | # Declare the return variable. | ||
4107 : | my $retVal = |