Parent Directory
|
Revision Log
Revision 1.50 - (view) (download) (as text)
1 : | olson | 1.30 | # |
2 : | # Copyright (c) 2003-2006 University of Chicago and Fellowship | ||
3 : | # for Interpretations of Genomes. All Rights Reserved. | ||
4 : | # | ||
5 : | # This file is part of the SEED Toolkit. | ||
6 : | # | ||
7 : | # The SEED Toolkit is free software. You can redistribute | ||
8 : | # it and/or modify it under the terms of the SEED Toolkit | ||
9 : | # Public License. | ||
10 : | # | ||
11 : | # You should have received a copy of the SEED Toolkit Public License | ||
12 : | # along with this program; if not write to the University of Chicago | ||
13 : | # at info@ci.uchicago.edu or the Fellowship for Interpretation of | ||
14 : | # Genomes at veronika@thefig.info or download a copy from | ||
15 : | # http://www.theseed.org/LICENSE.TXT. | ||
16 : | # | ||
17 : | |||
18 : | olson | 1.1 | package Tracer; |
19 : | |||
20 : | parrello | 1.12 | require Exporter; |
21 : | @ISA = ('Exporter'); | ||
22 : | parrello | 1.45 | @EXPORT = qw(Trace T TSetup QTrace Confess Cluck Min Max Assert Open OpenDir TICK StandardSetup ScriptSetup ScriptFinish Insure ChDir); |
23 : | parrello | 1.12 | @EXPORT_OK = qw(GetFile GetOptions Merge MergeOptions ParseCommand ParseRecord UnEscape Escape); |
24 : | use strict; | ||
25 : | use Carp qw(longmess croak); | ||
26 : | use CGI; | ||
27 : | parrello | 1.47 | use Cwd; |
28 : | parrello | 1.12 | use FIG_Config; |
29 : | parrello | 1.9 | use PageBuilder; |
30 : | parrello | 1.21 | use Digest::MD5; |
31 : | parrello | 1.36 | use File::Basename; |
32 : | parrello | 1.37 | use File::Path; |
33 : | parrello | 1.48 | use File::stat; |
34 : | olson | 1.1 | |
35 : | =head1 Tracing and Debugging Helpers | ||
36 : | |||
37 : | =head2 Introduction | ||
38 : | |||
39 : | This package provides simple tracing for debugging and reporting purposes. To use it simply call the | ||
40 : | L</TSetup> method to set the options and call L</Trace> to write out trace messages. Each trace | ||
41 : | parrello | 1.2 | message has a I<trace level> and I<category> associated with it. In addition, the tracing package itself |
42 : | has a list of categories and a single trace level set by the B<TSetup> method. Only messages whose trace | ||
43 : | olson | 1.1 | level is less than or equal to this package's trace level and whose category is activated will |
44 : | parrello | 1.2 | be written. Thus, a higher trace level on a message indicates that the message |
45 : | parrello | 1.10 | is less likely to be seen. A higher trace level passed to B<TSetup> means more trace messages will |
46 : | olson | 1.1 | appear. To generate a trace message, use the following syntax. |
47 : | |||
48 : | C<< Trace($message) if T(errors => 4); >> | ||
49 : | |||
50 : | parrello | 1.2 | This statement will produce a trace message if the trace level is 4 or more and the C<errors> |
51 : | parrello | 1.3 | category is active. Note that the special category C<main> is always active, so |
52 : | olson | 1.1 | |
53 : | parrello | 1.3 | C<< Trace($message) if T(main => 4); >> |
54 : | olson | 1.1 | |
55 : | will trace if the trace level is 4 or more. | ||
56 : | |||
57 : | If the category name is the same as the package name, all you need is the number. So, if the | ||
58 : | following call is made in the B<Sprout> package, it will appear if the C<Sprout> category is | ||
59 : | active and the trace level is 2 or more. | ||
60 : | |||
61 : | C<< Trace($message) if T(2); >> | ||
62 : | |||
63 : | parrello | 1.10 | To set up tracing, you call the L</TSetup> method. The method takes as input a trace level, a list |
64 : | olson | 1.1 | of category names, and a set of options. The trace level and list of category names are |
65 : | specified as a space-delimited string. Thus | ||
66 : | |||
67 : | C<< TSetup('3 errors Sprout ERDB', 'HTML'); >> | ||
68 : | |||
69 : | parrello | 1.7 | sets the trace level to 3, activates the C<errors>, C<Sprout>, and C<ERDB> categories, and |
70 : | parrello | 1.12 | specifies that messages should be output as HTML paragraphs. |
71 : | |||
72 : | To turn on tracing for ALL categories, use an asterisk. The call below sets every category to | ||
73 : | level 3 and writes the output to the standard error output. This sort of thing might be | ||
74 : | useful in a CGI environment. | ||
75 : | |||
76 : | C<< TSetup('3 *', 'WARN'); >> | ||
77 : | olson | 1.1 | |
78 : | In addition to HTML and file output for trace messages, you can specify that the trace messages | ||
79 : | be queued. The messages can then be retrieved by calling the L</QTrace> method. This approach | ||
80 : | is useful if you are building a web page. Instead of having the trace messages interspersed with | ||
81 : | the page output, they can be gathered together and displayed at the end of the page. This makes | ||
82 : | it easier to debug page formatting problems. | ||
83 : | |||
84 : | parrello | 1.4 | Finally, you can specify that all trace messages be emitted as warnings. |
85 : | |||
86 : | olson | 1.1 | The flexibility of tracing makes it superior to simple use of directives like C<die> and C<warn>. |
87 : | Tracer calls can be left in the code with minimal overhead and then turned on only when needed. | ||
88 : | Thus, debugging information is available and easily retrieved even when the application is | ||
89 : | being used out in the field. | ||
90 : | |||
91 : | parrello | 1.10 | There is no hard and fast rule on how to use trace levels. The following is therefore only |
92 : | a suggestion. | ||
93 : | |||
94 : | =over 4 | ||
95 : | |||
96 : | parrello | 1.32 | =item Error 0 |
97 : | parrello | 1.10 | |
98 : | Message indicates an error that may lead to incorrect results or that has stopped the | ||
99 : | application entirely. | ||
100 : | |||
101 : | parrello | 1.32 | =item Warning 1 |
102 : | parrello | 1.10 | |
103 : | Message indicates something that is unexpected but that probably did not interfere | ||
104 : | with program execution. | ||
105 : | |||
106 : | parrello | 1.32 | =item Notice 2 |
107 : | parrello | 1.10 | |
108 : | Message indicates the beginning or end of a major task. | ||
109 : | |||
110 : | parrello | 1.32 | =item Information 3 |
111 : | parrello | 1.10 | |
112 : | Message indicates a subtask. In the FIG system, a subtask generally relates to a single | ||
113 : | genome. This would be a big loop that is not expected to execute more than 500 times or so. | ||
114 : | |||
115 : | parrello | 1.32 | =item Detail 4 |
116 : | parrello | 1.10 | |
117 : | Message indicates a low-level loop iteration. | ||
118 : | |||
119 : | =back | ||
120 : | |||
121 : | olson | 1.1 | =cut |
122 : | parrello | 1.2 | |
123 : | olson | 1.1 | # Declare the configuration variables. |
124 : | |||
125 : | parrello | 1.12 | my $Destination = "NONE"; # Description of where to send the trace output. |
126 : | my $TeeFlag = 0; # TRUE if output is going to a file and to the | ||
127 : | # standard output | ||
128 : | parrello | 1.3 | my %Categories = ( main => 1 ); |
129 : | parrello | 1.12 | # hash of active category names |
130 : | my $TraceLevel = 0; # trace level; a higher trace level produces more | ||
131 : | # messages | ||
132 : | my @Queue = (); # queued list of trace messages. | ||
133 : | parrello | 1.7 | my $LastCategory = "main"; # name of the last category interrogated |
134 : | parrello | 1.11 | my $SetupCount = 0; # number of times TSetup called |
135 : | parrello | 1.12 | my $AllTrace = 0; # TRUE if we are tracing all categories. |
136 : | olson | 1.1 | |
137 : | =head2 Public Methods | ||
138 : | |||
139 : | =head3 TSetup | ||
140 : | |||
141 : | C<< TSetup($categoryList, $target); >> | ||
142 : | |||
143 : | This method is used to specify the trace options. The options are stored as package data | ||
144 : | and interrogated by the L</Trace> and L</T> methods. | ||
145 : | |||
146 : | =over 4 | ||
147 : | |||
148 : | =item categoryList | ||
149 : | |||
150 : | A string specifying the trace level and the categories to be traced, separated by spaces. | ||
151 : | The trace level must come first. | ||
152 : | |||
153 : | =item target | ||
154 : | |||
155 : | The destination for the trace output. To send the trace output to a file, specify the file | ||
156 : | name preceded by a ">" symbol. If a double symbol is used (">>"), then the data is appended | ||
157 : | parrello | 1.10 | to the file. Otherwise the file is cleared before tracing begins. Precede the first ">" |
158 : | symbol with a C<+> to echo output to a file AND to the standard output. In addition to | ||
159 : | sending the trace messages to a file, you can specify a special destination. C<HTML> will | ||
160 : | cause tracing to the standard output with each line formatted as an HTML paragraph. C<TEXT> | ||
161 : | parrello | 1.5 | will cause tracing to the standard output as ordinary text. C<ERROR> will cause trace |
162 : | parrello | 1.9 | messages to be sent to the standard error output as ordinary text. C<QUEUE> will cause trace |
163 : | parrello | 1.6 | messages to be stored in a queue for later retrieval by the L</QTrace> method. C<WARN> will |
164 : | parrello | 1.9 | cause trace messages to be emitted as warnings using the B<warn> directive. C<NONE> will |
165 : | parrello | 1.6 | cause tracing to be suppressed. |
166 : | olson | 1.1 | |
167 : | =back | ||
168 : | |||
169 : | =cut | ||
170 : | |||
171 : | sub TSetup { | ||
172 : | parrello | 1.12 | # Get the parameters. |
173 : | my ($categoryList, $target) = @_; | ||
174 : | # Parse the category list. | ||
175 : | my @categoryData = split /\s+/, $categoryList; | ||
176 : | # Extract the trace level. | ||
177 : | $TraceLevel = shift @categoryData; | ||
178 : | # Presume category-based tracing until we learn otherwise. | ||
179 : | $AllTrace = 0; | ||
180 : | # Build the category hash. Note that if we find a "*", we turn on non-category | ||
181 : | parrello | 1.33 | # tracing. We must also clear away any pre-existing data. |
182 : | parrello | 1.34 | %Categories = ( main => 1 ); |
183 : | parrello | 1.12 | for my $category (@categoryData) { |
184 : | if ($category eq '*') { | ||
185 : | $AllTrace = 1; | ||
186 : | } else { | ||
187 : | parrello | 1.13 | $Categories{lc $category} = 1; |
188 : | parrello | 1.12 | } |
189 : | } | ||
190 : | # Now we need to process the destination information. The most important special | ||
191 : | # cases are the single ">", which requires we clear the file first, and the | ||
192 : | # "+" prefix which indicates a double echo. | ||
193 : | if ($target =~ m/^\+?>>?/) { | ||
194 : | if ($target =~ m/^\+/) { | ||
195 : | $TeeFlag = 1; | ||
196 : | $target = substr($target, 1); | ||
197 : | } | ||
198 : | if ($target =~ m/^>[^>]/) { | ||
199 : | open TRACEFILE, $target; | ||
200 : | print TRACEFILE Now() . " Tracing initialized.\n"; | ||
201 : | close TRACEFILE; | ||
202 : | $Destination = ">$target"; | ||
203 : | } else { | ||
204 : | $Destination = $target; | ||
205 : | } | ||
206 : | } else { | ||
207 : | $Destination = uc($target); | ||
208 : | } | ||
209 : | # Increment the setup counter. | ||
210 : | $SetupCount++; | ||
211 : | parrello | 1.11 | } |
212 : | |||
213 : | parrello | 1.31 | =head3 StandardSetup |
214 : | |||
215 : | parrello | 1.36 | C<< my ($options, @parameters) = StandardSetup(\@categories, \%options, $parmHelp, @ARGV); >> |
216 : | parrello | 1.31 | |
217 : | This method performs standard command-line parsing and tracing setup. The return | ||
218 : | values are a hash of the command-line options and a list of the positional | ||
219 : | parameters. Tracing is automatically set up and the command-line options are | ||
220 : | validated. | ||
221 : | |||
222 : | This is a complex method that does a lot of grunt work. The parameters can | ||
223 : | be more easily understood, however, once they are examined individually. | ||
224 : | |||
225 : | The I<categories> parameter is the most obtuse. It is a reference to a list of | ||
226 : | special-purpose tracing categories. Most tracing categories are PERL package | ||
227 : | names. So, for example, if you wanted to turn on tracing inside the B<Sprout>, | ||
228 : | B<ERDB>, and B<SproutLoad> packages, you would specify the categories | ||
229 : | |||
230 : | ["Sprout", "SproutLoad", "ERDB"] | ||
231 : | |||
232 : | This would cause trace messages in the specified three packages to appear in | ||
233 : | the output. There are threer special tracing categories that are automatically | ||
234 : | handled by this method. In other words, if you used L</TSetup> you would need | ||
235 : | to include these categories manually, but if you use this method they are turned | ||
236 : | on automatically. | ||
237 : | |||
238 : | =over 4 | ||
239 : | |||
240 : | =item FIG | ||
241 : | |||
242 : | Turns on trace messages inside the B<FIG> package. | ||
243 : | |||
244 : | =item SQL | ||
245 : | |||
246 : | Traces SQL commands and activity. | ||
247 : | |||
248 : | =item Tracer | ||
249 : | |||
250 : | Traces error messages and call stacks. | ||
251 : | |||
252 : | =back | ||
253 : | |||
254 : | C<SQL> is only turned on if the C<-sql> option is specified in the command line. | ||
255 : | The trace level is specified using the C<-trace> command-line option. For example, | ||
256 : | the following command line for C<TransactFeatures> turns on SQL tracing and runs | ||
257 : | all tracing at level 3. | ||
258 : | |||
259 : | TransactFeatures -trace=3 -sql register ../xacts IDs.tbl | ||
260 : | |||
261 : | Standard tracing is output to the standard output and echoed to the file | ||
262 : | parrello | 1.38 | C<trace>I<$$>C<.log> in the FIG temporary directory, where I<$$> is the |
263 : | process ID. You can also specify the C<user> parameter to put a user ID | ||
264 : | instead of a process ID in the trace file name. So, for example | ||
265 : | parrello | 1.31 | |
266 : | parrello | 1.35 | The default trace level is 2. To get all messages, specify a trace level of 4. |
267 : | For a genome-by-genome update, use 3. | ||
268 : | parrello | 1.31 | |
269 : | parrello | 1.38 | TransactFeatures -trace=3 -sql -user=Bruce register ../xacts IDs.tbl |
270 : | |||
271 : | would send the trace output to C<traceBruce.log> in the temporary directory. | ||
272 : | |||
273 : | parrello | 1.31 | The I<options> parameter is a reference to a hash containing the command-line |
274 : | parrello | 1.36 | options, their default values, and an explanation of what they mean. Command-line |
275 : | options may be in the form of switches or keywords. In the case of a switch, the | ||
276 : | option value is 1 if it is specified and 0 if it is not specified. In the case | ||
277 : | of a keyword, the value is separated from the option name by an equal sign. You | ||
278 : | can see this last in the command-line example above. | ||
279 : | parrello | 1.31 | |
280 : | parrello | 1.42 | You can specify a different default trace level by setting C<$options->{trace}> |
281 : | prior to calling this method. | ||
282 : | |||
283 : | parrello | 1.31 | An example at this point would help. Consider, for example, the command-line utility |
284 : | C<TransactFeatures>. It accepts a list of positional parameters plus the options | ||
285 : | C<safe>, C<noAlias>, C<start>, and C<tblFiles>. To start up this command, we execute | ||
286 : | the following code. | ||
287 : | |||
288 : | my ($options, @parameters) = Tracer::StandardSetup(["DocUtils"], | ||
289 : | parrello | 1.36 | { safe => [0, "use database transactions"], |
290 : | noAlias => [0, "do not expect aliases in CHANGE transactions"], | ||
291 : | start => [' ', "start with this genome"], | ||
292 : | tblFiles => [0, "output TBL files containing the corrected IDs"] }, | ||
293 : | "command transactionDirectory IDfile", | ||
294 : | @ARGV); | ||
295 : | parrello | 1.31 | |
296 : | |||
297 : | The call to C<ParseCommand> specifies the default values for the options and | ||
298 : | stores the actual options in a hash that is returned as C<$options>. The | ||
299 : | positional parameters are returned in C<@parameters>. | ||
300 : | |||
301 : | The following is a sample command line for C<TransactFeatures>. | ||
302 : | |||
303 : | TransactFeatures -trace=2 -noAlias register ../xacts IDs.tbl | ||
304 : | |||
305 : | In this case, C<register>, C<../xacts>, and C<IDs.tbl> are the positional | ||
306 : | parameters, and would find themselves in I<@parameters> after executing the | ||
307 : | above code fragment. The tracing would be set to level 2, and the categories | ||
308 : | would be C<FIG>, C<Tracer>, and <DocUtils>. C<FIG> and C<Tracer> are standard, | ||
309 : | and C<DocUtils> was included because it came in within the first parameter | ||
310 : | to this method. The I<$options> hash would be | ||
311 : | |||
312 : | { trace => 2, sql => 0, safe => 0, | ||
313 : | noAlias => 1, start => ' ', tblFiles => 0 } | ||
314 : | |||
315 : | Use of C<StandardSetup> in this way provides a simple way of performing | ||
316 : | standard tracing setup and command-line parsing. Note that the caller is | ||
317 : | not even aware of the command-line switches C<-trace> and C<-sql>, which | ||
318 : | are used by this method to control the tracing. If additional tracing features | ||
319 : | need to be added in the future, they can be processed by this method without | ||
320 : | upsetting the command-line utilities. | ||
321 : | |||
322 : | parrello | 1.42 | If the C<background> option is specified on the command line, then the |
323 : | standard and error outputs will be directed to files in the temporary | ||
324 : | directory, using the same suffix as the trace file. So, if the command | ||
325 : | line specified | ||
326 : | |||
327 : | -user=Bruce -background | ||
328 : | |||
329 : | then the trace output would go to C<traceBruce.log>, the standard output to | ||
330 : | C<outBruce.log>, and the error output to C<errBruce.log>. This is designed to | ||
331 : | simplify starting a command in the background. | ||
332 : | |||
333 : | parrello | 1.36 | Finally, if the special option C<-h> is specified, the option names will |
334 : | be traced at level 0 and the program will exit without processing. | ||
335 : | This provides a limited help capability. For example, if the user enters | ||
336 : | |||
337 : | TransactFeatures -h | ||
338 : | |||
339 : | he would see the following output. | ||
340 : | |||
341 : | TransactFeatures [options] command transactionDirectory IDfile | ||
342 : | -trace tracing level (default 2) | ||
343 : | -sql trace SQL commands | ||
344 : | -safe use database transactions | ||
345 : | -noAlias do not expect aliases in CHANGE transactions | ||
346 : | -start start with this genome | ||
347 : | -tblFiles output TBL files containing the corrected IDs | ||
348 : | |||
349 : | parrello | 1.44 | The caller has the option of modifying the tracing scheme by placing a value |
350 : | for C<trace> in the incoming options hash. The default value can be overridden, | ||
351 : | or the tracing to the standard output can be turned off by suffixing a minus | ||
352 : | sign to the trace level. So, for example, | ||
353 : | |||
354 : | { trace => [0, "tracing level (default 0)"], | ||
355 : | ... | ||
356 : | |||
357 : | would set the default trace level to 0 instead of 2, while | ||
358 : | |||
359 : | { trace => ["2-", "tracing level (default 2)"], | ||
360 : | ... | ||
361 : | |||
362 : | would leave the default at 2, but trace only to the log file, not to the | ||
363 : | standard output. | ||
364 : | |||
365 : | parrello | 1.31 | The parameters to this method are as follows. |
366 : | |||
367 : | =over 4 | ||
368 : | |||
369 : | =item categories | ||
370 : | |||
371 : | Reference to a list of tracing category names. These should be names of | ||
372 : | packages whose internal workings will need to be debugged to get the | ||
373 : | command working. | ||
374 : | |||
375 : | =item options | ||
376 : | |||
377 : | Reference to a hash containing the legal options for the current command mapped | ||
378 : | parrello | 1.36 | to their default values and descriptions. The user can override the defaults |
379 : | by specifying the options as command-line switches prefixed by a hyphen. | ||
380 : | Tracing-related options may be added to this hash. If the C<-h> option is | ||
381 : | specified on the command line, the option descriptions will be used to | ||
382 : | parrello | 1.44 | explain the options. To turn off tracing to the standard output, add a |
383 : | minus sign to the value for C<trace> (see above). | ||
384 : | parrello | 1.36 | |
385 : | =item parmHelp | ||
386 : | |||
387 : | A string that vaguely describes the positional parameters. This is used | ||
388 : | if the user specifies the C<-h> option. | ||
389 : | parrello | 1.31 | |
390 : | parrello | 1.44 | =item argv |
391 : | parrello | 1.31 | |
392 : | List of command line parameters, including the option switches, which must | ||
393 : | precede the positional parameters and be prefixed by a hyphen. | ||
394 : | |||
395 : | =item RETURN | ||
396 : | |||
397 : | Returns a list. The first element of the list is the reference to a hash that | ||
398 : | maps the command-line option switches to their values. These will either be the | ||
399 : | default values or overrides specified on the command line. The remaining | ||
400 : | elements of the list are the position parameters, in order. | ||
401 : | |||
402 : | =back | ||
403 : | |||
404 : | =cut | ||
405 : | |||
406 : | sub StandardSetup { | ||
407 : | # Get the parameters. | ||
408 : | parrello | 1.36 | my ($categories, $options, $parmHelp, @argv) = @_; |
409 : | parrello | 1.31 | # Add the tracing options. |
410 : | parrello | 1.41 | if (! exists $options->{trace}) { |
411 : | $options->{trace} = [2, "tracing level"]; | ||
412 : | } | ||
413 : | parrello | 1.36 | $options->{sql} = [0, "turn on SQL tracing"]; |
414 : | $options->{h} = [0, "display command-line options"]; | ||
415 : | parrello | 1.38 | $options->{user} = [$$, "trace log file name suffix"]; |
416 : | parrello | 1.42 | $options->{background} = [0, "spool standard and error output"]; |
417 : | parrello | 1.36 | # Create a parsing hash from the options hash. The parsing hash |
418 : | # contains the default values rather than the default value | ||
419 : | # and the description. While we're at it, we'll memorize the | ||
420 : | # length of the longest option name. | ||
421 : | my $longestName = 0; | ||
422 : | my %parseOptions = (); | ||
423 : | for my $key (keys %{$options}) { | ||
424 : | if (length $key > $longestName) { | ||
425 : | $longestName = length $key; | ||
426 : | } | ||
427 : | $parseOptions{$key} = $options->{$key}->[0]; | ||
428 : | } | ||
429 : | parrello | 1.31 | # Parse the command line. |
430 : | parrello | 1.36 | my ($retOptions, @retParameters) = ParseCommand(\%parseOptions, @argv); |
431 : | parrello | 1.42 | # Get the logfile suffix. |
432 : | my $suffix = $retOptions->{user}; | ||
433 : | # Check for background mode. | ||
434 : | if ($retOptions->{background}) { | ||
435 : | my $outFileName = "$FIG_Config::temp/out$suffix.log"; | ||
436 : | my $errFileName = "$FIG_Config::temp/err$suffix.log"; | ||
437 : | open STDOUT, ">$outFileName"; | ||
438 : | open STDERR, ">$errFileName"; | ||
439 : | } | ||
440 : | parrello | 1.31 | # Now we want to set up tracing. First, we need to know if SQL is to |
441 : | # be traced. | ||
442 : | my @cats = @{$categories}; | ||
443 : | if ($retOptions->{sql}) { | ||
444 : | push @cats, "SQL"; | ||
445 : | } | ||
446 : | # Add the default categories. | ||
447 : | push @cats, "Tracer", "FIG"; | ||
448 : | parrello | 1.44 | # Next, we create the category string by joining the categories. |
449 : | my $cats = join(" ", @cats); | ||
450 : | # Check to determine whether or not the caller wants to turn off tracing | ||
451 : | # to the standard output. | ||
452 : | my $traceLevel = $retOptions->{trace}; | ||
453 : | my $textOKFlag = 1; | ||
454 : | if ($traceLevel =~ /^(.)-/) { | ||
455 : | $traceLevel = $1; | ||
456 : | $textOKFlag = 0; | ||
457 : | } | ||
458 : | # Now we set up the trace mode. | ||
459 : | my $traceMode; | ||
460 : | # Verify that we can open a file in the FIG temporary directory. | ||
461 : | parrello | 1.40 | my $traceFileName = "$FIG_Config::temp/trace$suffix.log"; |
462 : | if (open TESTTRACE, ">$traceFileName") { | ||
463 : | parrello | 1.44 | # Here we can trace to a file. |
464 : | $traceMode = ">$traceFileName"; | ||
465 : | if ($textOKFlag) { | ||
466 : | # Echo to standard output if the text-OK flag is set. | ||
467 : | $traceMode = "+$traceMode"; | ||
468 : | } | ||
469 : | # Close the test file. | ||
470 : | parrello | 1.40 | close TESTTRACE; |
471 : | parrello | 1.44 | } else { |
472 : | # Here we can't trace to a file. We trace to the standard output if it's | ||
473 : | # okay, and the error log otherwise. | ||
474 : | if ($textOKFlag) { | ||
475 : | $traceMode = "TEXT"; | ||
476 : | } else { | ||
477 : | $traceMode = "WARN"; | ||
478 : | } | ||
479 : | parrello | 1.40 | } |
480 : | parrello | 1.31 | # Now set up the tracing. |
481 : | parrello | 1.44 | TSetup("$traceLevel $cats", $traceMode); |
482 : | parrello | 1.36 | # Check for the "h" option. If it is specified, dump the command-line |
483 : | # options and exit the program. | ||
484 : | if ($retOptions->{h}) { | ||
485 : | $0 =~ m#[/\\](\w+)(\.pl)?$#i; | ||
486 : | Trace("$1 [options] $parmHelp") if T(0); | ||
487 : | for my $key (sort keys %{$options}) { | ||
488 : | my $name = Pad($key, $longestName, 0, ' '); | ||
489 : | my $desc = $options->{$key}->[1]; | ||
490 : | if ($options->{$key}->[0]) { | ||
491 : | $desc .= " (default " . $options->{$key}->[0] . ")"; | ||
492 : | } | ||
493 : | Trace(" $name $desc") if T(0); | ||
494 : | } | ||
495 : | exit(0); | ||
496 : | } | ||
497 : | parrello | 1.31 | # Return the parsed parameters. |
498 : | return ($retOptions, @retParameters); | ||
499 : | } | ||
500 : | |||
501 : | parrello | 1.11 | =head3 Setups |
502 : | |||
503 : | C<< my $count = Tracer::Setups(); >> | ||
504 : | |||
505 : | Return the number of times L</TSetup> has been called. | ||
506 : | |||
507 : | This method allows for the creation of conditional tracing setups where, for example, we | ||
508 : | may want to set up tracing if nobody else has done it before us. | ||
509 : | |||
510 : | =cut | ||
511 : | |||
512 : | sub Setups { | ||
513 : | parrello | 1.12 | return $SetupCount; |
514 : | olson | 1.1 | } |
515 : | |||
516 : | parrello | 1.10 | =head3 Open |
517 : | |||
518 : | C<< my $handle = Open($fileHandle, $fileSpec, $message); >> | ||
519 : | |||
520 : | parrello | 1.11 | Open a file. |
521 : | parrello | 1.10 | |
522 : | The I<$fileSpec> is essentially the second argument of the PERL C<open> | ||
523 : | function. The mode is specified using Unix-like shell information. So, for | ||
524 : | example, | ||
525 : | |||
526 : | parrello | 1.12 | Open(\*LOGFILE, '>>/usr/spool/news/twitlog', "Could not open twit log."); |
527 : | parrello | 1.10 | |
528 : | would open for output appended to the specified file, and | ||
529 : | |||
530 : | parrello | 1.12 | Open(\*DATASTREAM, "| sort -u >$outputFile", "Could not open $outputFile."); |
531 : | parrello | 1.10 | |
532 : | would open a pipe that sorts the records written and removes duplicates. Note | ||
533 : | parrello | 1.11 | the use of file handle syntax in the Open call. To use anonymous file handles, |
534 : | code as follows. | ||
535 : | parrello | 1.10 | |
536 : | parrello | 1.12 | my $logFile = Open(undef, '>>/usr/spool/news/twitlog', "Could not open twit log."); |
537 : | parrello | 1.10 | |
538 : | parrello | 1.11 | The I<$message> parameter is used if the open fails. If it is set to C<0>, then |
539 : | the open returns TRUE if successful and FALSE if an error occurred. Otherwise, a | ||
540 : | failed open will throw an exception and the third parameter will be used to construct | ||
541 : | an error message. If the parameter is omitted, a standard message is constructed | ||
542 : | using the file spec. | ||
543 : | parrello | 1.10 | |
544 : | parrello | 1.12 | Could not open "/usr/spool/news/twitlog" |
545 : | parrello | 1.10 | |
546 : | Note that the mode characters are automatically cleaned from the file name. | ||
547 : | The actual error message from the file system will be captured and appended to the | ||
548 : | message in any case. | ||
549 : | |||
550 : | parrello | 1.12 | Could not open "/usr/spool/news/twitlog": file not found. |
551 : | parrello | 1.10 | |
552 : | In some versions of PERL the only error message we get is a number, which | ||
553 : | corresponds to the C++ C<errno> value. | ||
554 : | |||
555 : | parrello | 1.12 | Could not open "/usr/spool/news/twitlog": 6. |
556 : | parrello | 1.10 | |
557 : | =over 4 | ||
558 : | |||
559 : | =item fileHandle | ||
560 : | |||
561 : | File handle. If this parameter is C<undef>, a file handle will be generated | ||
562 : | and returned as the value of this method. | ||
563 : | |||
564 : | =item fileSpec | ||
565 : | |||
566 : | File name and mode, as per the PERL C<open> function. | ||
567 : | |||
568 : | =item message (optional) | ||
569 : | |||
570 : | Error message to use if the open fails. If omitted, a standard error message | ||
571 : | will be generated. In either case, the error information from the file system | ||
572 : | parrello | 1.11 | is appended to the message. To specify a conditional open that does not throw |
573 : | an error if it fails, use C<0>. | ||
574 : | parrello | 1.10 | |
575 : | =item RETURN | ||
576 : | |||
577 : | parrello | 1.11 | Returns the name of the file handle assigned to the file, or C<undef> if the |
578 : | open failed. | ||
579 : | parrello | 1.10 | |
580 : | =back | ||
581 : | |||
582 : | =cut | ||
583 : | |||
584 : | sub Open { | ||
585 : | parrello | 1.12 | # Get the parameters. |
586 : | my ($fileHandle, $fileSpec, $message) = @_; | ||
587 : | # Attempt to open the file. | ||
588 : | my $rv = open $fileHandle, $fileSpec; | ||
589 : | # If the open failed, generate an error message. | ||
590 : | if (! $rv) { | ||
591 : | # Save the system error message. | ||
592 : | my $sysMessage = $!; | ||
593 : | # See if we need a default message. | ||
594 : | if (!$message) { | ||
595 : | # Clean any obvious mode characters and leading spaces from the | ||
596 : | # filename. | ||
597 : | my ($fileName) = FindNamePart($fileSpec); | ||
598 : | $message = "Could not open \"$fileName\""; | ||
599 : | } | ||
600 : | # Terminate with an error using the supplied message and the | ||
601 : | # error message from the file system. | ||
602 : | Confess("$message: $!"); | ||
603 : | } | ||
604 : | # Return the file handle. | ||
605 : | return $fileHandle; | ||
606 : | parrello | 1.10 | } |
607 : | |||
608 : | parrello | 1.11 | =head3 FindNamePart |
609 : | |||
610 : | C<< my ($fileName, $start, $len) = Tracer::FindNamePart($fileSpec); >> | ||
611 : | |||
612 : | Extract the portion of a file specification that contains the file name. | ||
613 : | |||
614 : | A file specification is the string passed to an C<open> call. It specifies the file | ||
615 : | mode and name. In a truly complex situation, it can specify a pipe sequence. This | ||
616 : | method assumes that the file name is whatever follows the first angle bracket | ||
617 : | sequence. So, for example, in the following strings the file name is | ||
618 : | C</usr/fig/myfile.txt>. | ||
619 : | |||
620 : | >>/usr/fig/myfile.txt | ||
621 : | </usr/fig/myfile.txt | ||
622 : | | sort -u > /usr/fig/myfile.txt | ||
623 : | |||
624 : | If the method cannot find a file name using its normal methods, it will return the | ||
625 : | whole incoming string. | ||
626 : | |||
627 : | =over 4 | ||
628 : | |||
629 : | =item fileSpec | ||
630 : | |||
631 : | File specification string from which the file name is to be extracted. | ||
632 : | |||
633 : | =item RETURN | ||
634 : | |||
635 : | Returns a three-element list. The first element contains the file name portion of | ||
636 : | the specified string, or the whole string if a file name cannot be found via normal | ||
637 : | methods. The second element contains the start position of the file name portion and | ||
638 : | the third element contains the length. | ||
639 : | |||
640 : | =back | ||
641 : | |||
642 : | =cut | ||
643 : | #: Return Type $; | ||
644 : | sub FindNamePart { | ||
645 : | # Get the parameters. | ||
646 : | my ($fileSpec) = @_; | ||
647 : | parrello | 1.12 | # Default to the whole input string. |
648 : | my ($retVal, $pos, $len) = ($fileSpec, 0, length $fileSpec); | ||
649 : | parrello | 1.11 | # Parse out the file name if we can. |
650 : | parrello | 1.12 | if ($fileSpec =~ m/(<|>>?)(.+?)(\s*)$/) { |
651 : | $retVal = $2; | ||
652 : | $len = length $retVal; | ||
653 : | $pos = (length $fileSpec) - (length $3) - $len; | ||
654 : | } | ||
655 : | parrello | 1.11 | # Return the result. |
656 : | return ($retVal, $pos, $len); | ||
657 : | } | ||
658 : | |||
659 : | =head3 OpenDir | ||
660 : | |||
661 : | parrello | 1.31 | C<< my @files = OpenDir($dirName, $filtered, $flag); >> |
662 : | parrello | 1.11 | |
663 : | Open a directory and return all the file names. This function essentially performs | ||
664 : | the functions of an C<opendir> and C<readdir>. If the I<$filtered> parameter is | ||
665 : | parrello | 1.31 | set to TRUE, all filenames beginning with a period (C<.>), dollar sign (C<$>), |
666 : | parrello | 1.33 | or pound sign (C<#>) and all filenames ending with a tilde C<~>) will be |
667 : | filtered out of the return list. If the directory does not open and I<$flag> is not | ||
668 : | set, an exception is thrown. So, for example, | ||
669 : | parrello | 1.11 | |
670 : | parrello | 1.12 | my @files = OpenDir("/Volumes/fig/contigs", 1); |
671 : | parrello | 1.29 | |
672 : | parrello | 1.11 | is effectively the same as |
673 : | |||
674 : | parrello | 1.12 | opendir(TMP, "/Volumes/fig/contigs") || Confess("Could not open /Volumes/fig/contigs."); |
675 : | parrello | 1.33 | my @files = grep { $_ !~ /^[\.\$\#]/ && $_ !~ /~$/ } readdir(TMP); |
676 : | parrello | 1.11 | |
677 : | Similarly, the following code | ||
678 : | |||
679 : | parrello | 1.31 | my @files = grep { $_ =~ /^\d/ } OpenDir("/Volumes/fig/orgs", 0, 1); |
680 : | parrello | 1.29 | |
681 : | parrello | 1.11 | Returns the names of all files in C</Volumes/fig/orgs> that begin with digits and |
682 : | parrello | 1.31 | automatically returns an empty list if the directory fails to open. |
683 : | parrello | 1.11 | |
684 : | =over 4 | ||
685 : | |||
686 : | =item dirName | ||
687 : | |||
688 : | Name of the directory to open. | ||
689 : | |||
690 : | =item filtered | ||
691 : | |||
692 : | TRUE if files whose names begin with a period (C<.>) should be automatically removed | ||
693 : | from the list, else FALSE. | ||
694 : | |||
695 : | parrello | 1.31 | =item flag |
696 : | |||
697 : | TRUE if a failure to open is okay, else FALSE | ||
698 : | |||
699 : | parrello | 1.11 | =back |
700 : | |||
701 : | =cut | ||
702 : | #: Return Type @; | ||
703 : | sub OpenDir { | ||
704 : | # Get the parameters. | ||
705 : | parrello | 1.31 | my ($dirName, $filtered, $flag) = @_; |
706 : | parrello | 1.11 | # Declare the return variable. |
707 : | parrello | 1.31 | my @retVal = (); |
708 : | parrello | 1.12 | # Open the directory. |
709 : | if (opendir(my $dirHandle, $dirName)) { | ||
710 : | # The directory opened successfully. Get the appropriate list according to the | ||
711 : | # strictures of the filter parameter. | ||
712 : | if ($filtered) { | ||
713 : | parrello | 1.33 | @retVal = grep { $_ !~ /^[\.\$\#]/ && $_ !~ /~$/ } readdir $dirHandle; |
714 : | parrello | 1.12 | } else { |
715 : | @retVal = readdir $dirHandle; | ||
716 : | } | ||
717 : | parrello | 1.31 | } elsif (! $flag) { |
718 : | # Here the directory would not open and it's considered an error. | ||
719 : | parrello | 1.12 | Confess("Could not open directory $dirName."); |
720 : | } | ||
721 : | parrello | 1.11 | # Return the result. |
722 : | return @retVal; | ||
723 : | } | ||
724 : | |||
725 : | parrello | 1.6 | =head3 SetLevel |
726 : | |||
727 : | C<< Tracer::SetLevel($newLevel); >> | ||
728 : | |||
729 : | Modify the trace level. A higher trace level will cause more messages to appear. | ||
730 : | |||
731 : | =over 4 | ||
732 : | |||
733 : | =item newLevel | ||
734 : | |||
735 : | Proposed new trace level. | ||
736 : | |||
737 : | =back | ||
738 : | |||
739 : | =cut | ||
740 : | |||
741 : | sub SetLevel { | ||
742 : | $TraceLevel = $_[0]; | ||
743 : | } | ||
744 : | |||
745 : | olson | 1.1 | =head3 Now |
746 : | |||
747 : | C<< my $string = Tracer::Now(); >> | ||
748 : | |||
749 : | Return a displayable time stamp containing the local time. | ||
750 : | |||
751 : | =cut | ||
752 : | |||
753 : | sub Now { | ||
754 : | parrello | 1.12 | my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); |
755 : | my $retVal = _p2($mon+1) . "/" . _p2($mday) . "/" . ($year + 1900) . " " . | ||
756 : | _p2($hour) . ":" . _p2($min) . ":" . _p2($sec); | ||
757 : | return $retVal; | ||
758 : | olson | 1.1 | } |
759 : | |||
760 : | # Pad a number to 2 digits. | ||
761 : | sub _p2 { | ||
762 : | parrello | 1.12 | my ($value) = @_; |
763 : | $value = "0$value" if ($value < 10); | ||
764 : | return $value; | ||
765 : | olson | 1.1 | } |
766 : | |||
767 : | =head3 LogErrors | ||
768 : | |||
769 : | C<< Tracer::LogErrors($fileName); >> | ||
770 : | |||
771 : | Route the standard error output to a log file. | ||
772 : | |||
773 : | =over 4 | ||
774 : | |||
775 : | =item fileName | ||
776 : | |||
777 : | Name of the file to receive the error output. | ||
778 : | |||
779 : | =back | ||
780 : | |||
781 : | =cut | ||
782 : | |||
783 : | sub LogErrors { | ||
784 : | parrello | 1.12 | # Get the file name. |
785 : | my ($fileName) = @_; | ||
786 : | # Open the file as the standard error output. | ||
787 : | open STDERR, '>', $fileName; | ||
788 : | olson | 1.1 | } |
789 : | |||
790 : | parrello | 1.5 | =head3 ReadOptions |
791 : | |||
792 : | C<< my %options = Tracer::ReadOptions($fileName); >> | ||
793 : | |||
794 : | Read a set of options from a file. Each option is encoded in a line of text that has the | ||
795 : | format | ||
796 : | |||
797 : | I<optionName>C<=>I<optionValue>C<; >I<comment> | ||
798 : | |||
799 : | The option name must consist entirely of letters, digits, and the punctuation characters | ||
800 : | parrello | 1.9 | C<.> and C<_>, and is case sensitive. Blank lines and lines in which the first nonblank |
801 : | character is a semi-colon will be ignored. The return hash will map each option name to | ||
802 : | parrello | 1.5 | the corresponding option value. |
803 : | |||
804 : | =over 4 | ||
805 : | |||
806 : | =item fileName | ||
807 : | |||
808 : | Name of the file containing the option data. | ||
809 : | |||
810 : | =item RETURN | ||
811 : | |||
812 : | Returns a hash mapping the option names specified in the file to their corresponding option | ||
813 : | value. | ||
814 : | |||
815 : | =back | ||
816 : | |||
817 : | =cut | ||
818 : | |||
819 : | sub ReadOptions { | ||
820 : | parrello | 1.12 | # Get the parameters. |
821 : | my ($fileName) = @_; | ||
822 : | # Open the file. | ||
823 : | (open CONFIGFILE, "<$fileName") || Confess("Could not open option file $fileName."); | ||
824 : | # Count the number of records read. | ||
825 : | my ($records, $comments) = 0; | ||
826 : | # Create the return hash. | ||
827 : | my %retVal = (); | ||
828 : | # Loop through the file, accumulating key-value pairs. | ||
829 : | while (my $line = <CONFIGFILE>) { | ||
830 : | # Denote we've read a line. | ||
831 : | $records++; | ||
832 : | # Determine the line type. | ||
833 : | if ($line =~ /^\s*[\n\r]/) { | ||
834 : | # A blank line is a comment. | ||
835 : | $comments++; | ||
836 : | } elsif ($line =~ /^\s*([A-Za-z0-9_\.]+)=([^;]*);/) { | ||
837 : | # Here we have an option assignment. | ||
838 : | retVal{$1} = $2; | ||
839 : | } elsif ($line =~ /^\s*;/) { | ||
840 : | # Here we have a text comment. | ||
841 : | $comments++; | ||
842 : | } else { | ||
843 : | # Here we have an invalid line. | ||
844 : | Trace("Invalid option statement in record $records.") if T(0); | ||
845 : | } | ||
846 : | } | ||
847 : | # Return the hash created. | ||
848 : | return %retVal; | ||
849 : | parrello | 1.5 | } |
850 : | |||
851 : | olson | 1.1 | =head3 GetOptions |
852 : | |||
853 : | C<< Tracer::GetOptions(\%defaults, \%options); >> | ||
854 : | |||
855 : | Merge a specified set of options into a table of defaults. This method takes two hash references | ||
856 : | as input and uses the data from the second to update the first. If the second does not exist, | ||
857 : | there will be no effect. An error will be thrown if one of the entries in the second hash does not | ||
858 : | exist in the first. | ||
859 : | |||
860 : | Consider the following example. | ||
861 : | |||
862 : | C<< my $optionTable = GetOptions({ dbType => 'mySQL', trace => 0 }, $options); >> | ||
863 : | |||
864 : | In this example, the variable B<$options> is expected to contain at most two options-- B<dbType> and | ||
865 : | B<trace>. The default database type is C<mySQL> and the default trace level is C<0>. If the value of | ||
866 : | B<$options> is C<< {dbType => 'Oracle'} >>, then the database type will be changed to C<Oracle> and | ||
867 : | the trace level will remain at 0. If B<$options> is undefined, then the database type and trace level | ||
868 : | will remain C<mySQL> and C<0>. If, on the other hand, B<$options> is defined as | ||
869 : | |||
870 : | C<< {databaseType => 'Oracle'} >> | ||
871 : | |||
872 : | an error will occur because the B<databaseType> option does not exist. | ||
873 : | |||
874 : | =over 4 | ||
875 : | |||
876 : | =item defaults | ||
877 : | |||
878 : | Table of default option values. | ||
879 : | |||
880 : | =item options | ||
881 : | |||
882 : | Table of overrides, if any. | ||
883 : | |||
884 : | =item RETURN | ||
885 : | |||
886 : | Returns a reference to the default table passed in as the first parameter. | ||
887 : | |||
888 : | =back | ||
889 : | |||
890 : | =cut | ||
891 : | |||
892 : | sub GetOptions { | ||
893 : | parrello | 1.12 | # Get the parameters. |
894 : | my ($defaults, $options) = @_; | ||
895 : | # Check for overrides. | ||
896 : | if ($options) { | ||
897 : | # Loop through the overrides. | ||
898 : | while (my ($option, $setting) = each %{$options}) { | ||
899 : | # Insure this override exists. | ||
900 : | if (!exists $defaults->{$option}) { | ||
901 : | croak "Unrecognized option $option encountered."; | ||
902 : | } else { | ||
903 : | # Apply the override. | ||
904 : | $defaults->{$option} = $setting; | ||
905 : | } | ||
906 : | } | ||
907 : | } | ||
908 : | # Return the merged table. | ||
909 : | return $defaults; | ||
910 : | olson | 1.1 | } |
911 : | |||
912 : | =head3 MergeOptions | ||
913 : | |||
914 : | C<< Tracer::MergeOptions(\%table, \%defaults); >> | ||
915 : | |||
916 : | Merge default values into a hash table. This method looks at the key-value pairs in the | ||
917 : | second (default) hash, and if a matching key is not found in the first hash, the default | ||
918 : | pair is copied in. The process is similar to L</GetOptions>, but there is no error- | ||
919 : | checking and no return value. | ||
920 : | |||
921 : | =over 4 | ||
922 : | |||
923 : | =item table | ||
924 : | |||
925 : | Hash table to be updated with the default values. | ||
926 : | |||
927 : | =item defaults | ||
928 : | |||
929 : | Default values to be merged into the first hash table if they are not already present. | ||
930 : | |||
931 : | =back | ||
932 : | |||
933 : | =cut | ||
934 : | |||
935 : | sub MergeOptions { | ||
936 : | parrello | 1.12 | # Get the parameters. |
937 : | my ($table, $defaults) = @_; | ||
938 : | # Loop through the defaults. | ||
939 : | while (my ($key, $value) = each %{$defaults}) { | ||
940 : | if (!exists $table->{$key}) { | ||
941 : | $table->{$key} = $value; | ||
942 : | } | ||
943 : | } | ||
944 : | olson | 1.1 | } |
945 : | |||
946 : | =head3 Trace | ||
947 : | |||
948 : | C<< Trace($message); >> | ||
949 : | |||
950 : | Write a trace message to the target location specified in L</TSetup>. If there has not been | ||
951 : | any prior call to B<TSetup>. | ||
952 : | |||
953 : | =over 4 | ||
954 : | |||
955 : | =item message | ||
956 : | |||
957 : | Message to write. | ||
958 : | |||
959 : | =back | ||
960 : | |||
961 : | =cut | ||
962 : | |||
963 : | sub Trace { | ||
964 : | parrello | 1.12 | # Get the parameters. |
965 : | my ($message) = @_; | ||
966 : | # Get the timestamp. | ||
967 : | my $timeStamp = Now(); | ||
968 : | # Format the message. Note we strip off any line terminators at the end. | ||
969 : | my $formatted = "$timeStamp <$LastCategory>: " . Strip($message); | ||
970 : | # Process according to the destination. | ||
971 : | if ($Destination eq "TEXT") { | ||
972 : | # Write the message to the standard output. | ||
973 : | print "$formatted\n"; | ||
974 : | } elsif ($Destination eq "ERROR") { | ||
975 : | # Write the message to the error output. | ||
976 : | print STDERR "$formatted\n"; | ||
977 : | } elsif ($Destination eq "QUEUE") { | ||
978 : | # Push the message into the queue. | ||
979 : | push @Queue, "$formatted"; | ||
980 : | } elsif ($Destination eq "HTML") { | ||
981 : | # Convert the message to HTML and write it to the standard output. | ||
982 : | my $escapedMessage = CGI::escapeHTML($message); | ||
983 : | print "<p>$formatted</p>\n"; | ||
984 : | parrello | 1.4 | } elsif ($Destination eq "WARN") { |
985 : | # Emit the message as a warning. | ||
986 : | warn $message; | ||
987 : | parrello | 1.12 | } elsif ($Destination =~ m/^>>/) { |
988 : | # Write the trace message to an output file. | ||
989 : | parrello | 1.14 | (open TRACING, $Destination) || die "Tracing open for \"$Destination\" failed: $!"; |
990 : | parrello | 1.12 | print TRACING "$formatted\n"; |
991 : | close TRACING; | ||
992 : | # If the Tee flag is on, echo it to the standard output. | ||
993 : | if ($TeeFlag) { | ||
994 : | print "$formatted\n"; | ||
995 : | } | ||
996 : | } | ||
997 : | olson | 1.1 | } |
998 : | |||
999 : | =head3 T | ||
1000 : | |||
1001 : | parrello | 1.2 | C<< my $switch = T($category, $traceLevel); >> |
1002 : | olson | 1.1 | |
1003 : | parrello | 1.12 | or |
1004 : | parrello | 1.2 | |
1005 : | olson | 1.1 | C<< my $switch = T($traceLevel); >> |
1006 : | |||
1007 : | Return TRUE if the trace level is at or above a specified value and the specified category | ||
1008 : | is active, else FALSE. If no category is specified, the caller's package name is used. | ||
1009 : | |||
1010 : | =over 4 | ||
1011 : | |||
1012 : | =item category | ||
1013 : | |||
1014 : | Category to which the message belongs. If not specified, the caller's package name is | ||
1015 : | used. | ||
1016 : | |||
1017 : | =item traceLevel | ||
1018 : | |||
1019 : | Relevant tracing level. | ||
1020 : | |||
1021 : | =item RETURN | ||
1022 : | |||
1023 : | TRUE if a message at the specified trace level would appear in the trace, else FALSE. | ||
1024 : | |||
1025 : | =back | ||
1026 : | |||
1027 : | =cut | ||
1028 : | |||
1029 : | sub T { | ||
1030 : | parrello | 1.12 | # Declare the return variable. |
1031 : | my $retVal = 0; | ||
1032 : | # Only proceed if tracing is turned on. | ||
1033 : | if ($Destination ne "NONE") { | ||
1034 : | # Get the parameters. | ||
1035 : | my ($category, $traceLevel) = @_; | ||
1036 : | if (!defined $traceLevel) { | ||
1037 : | # Here we have no category, so we need to get the calling package. | ||
1038 : | # The calling package is normally the first parameter. If it is | ||
1039 : | # omitted, the first parameter will be the tracelevel. So, the | ||
1040 : | # first thing we do is shift the so-called category into the | ||
1041 : | # $traceLevel variable where it belongs. | ||
1042 : | $traceLevel = $category; | ||
1043 : | my ($package, $fileName, $line) = caller; | ||
1044 : | parrello | 1.3 | # If there is no calling package, we default to "main". |
1045 : | parrello | 1.12 | if (!$package) { |
1046 : | parrello | 1.3 | $category = "main"; |
1047 : | parrello | 1.12 | } else { |
1048 : | $category = $package; | ||
1049 : | } | ||
1050 : | } | ||
1051 : | parrello | 1.7 | # Save the category name. |
1052 : | $LastCategory = $category; | ||
1053 : | parrello | 1.13 | # Convert it to lower case before we hash it. |
1054 : | $category = lc $category; | ||
1055 : | parrello | 1.12 | # Use the category and tracelevel to compute the result. |
1056 : | parrello | 1.36 | if (ref $traceLevel) { |
1057 : | Confess("Bad trace level."); | ||
1058 : | } elsif (ref $TraceLevel) { | ||
1059 : | Confess("Bad trace config."); | ||
1060 : | } | ||
1061 : | parrello | 1.12 | $retVal = ($traceLevel <= $TraceLevel && ($AllTrace || exists $Categories{$category})); |
1062 : | parrello | 1.3 | } |
1063 : | parrello | 1.12 | # Return the computed result. |
1064 : | parrello | 1.3 | return $retVal; |
1065 : | olson | 1.1 | } |
1066 : | |||
1067 : | =head3 ParseCommand | ||
1068 : | |||
1069 : | C<< my ($options, @arguments) = Tracer::ParseCommand(\%optionTable, @inputList); >> | ||
1070 : | |||
1071 : | Parse a command line consisting of a list of parameters. The initial parameters may be option | ||
1072 : | parrello | 1.2 | specifiers of the form C<->I<option> or C<->I<option>C<=>I<value>. The options are stripped |
1073 : | off and merged into a table of default options. The remainder of the command line is | ||
1074 : | olson | 1.1 | returned as a list of positional arguments. For example, consider the following invocation. |
1075 : | |||
1076 : | C<< my ($options, @arguments) = ParseCommand({ errors => 0, logFile => 'trace.log'}, @words); >> | ||
1077 : | |||
1078 : | In this case, the list @words will be treated as a command line. There are two options available, | ||
1079 : | B<errors> and B<logFile>. If @words has the following format | ||
1080 : | |||
1081 : | C<< -logFile=error.log apple orange rutabaga >> | ||
1082 : | |||
1083 : | then at the end of the invocation, C<$options> will be | ||
1084 : | |||
1085 : | C<< { errors => 0, logFile => 'error.log' } >> | ||
1086 : | |||
1087 : | and C<@arguments> will contain | ||
1088 : | |||
1089 : | C<< apple orange rutabaga >> | ||
1090 : | |||
1091 : | parrello | 1.2 | The parser allows for some escape sequences. See L</UnEscape> for a description. There is no |
1092 : | olson | 1.1 | support for quote characters. |
1093 : | |||
1094 : | =over 4 | ||
1095 : | |||
1096 : | =item optionTable | ||
1097 : | |||
1098 : | Table of default options. | ||
1099 : | |||
1100 : | =item inputList | ||
1101 : | |||
1102 : | List of words on the command line. | ||
1103 : | |||
1104 : | =item RETURN | ||
1105 : | |||
1106 : | Returns a reference to the option table and a list of the positional arguments. | ||
1107 : | |||
1108 : | =back | ||
1109 : | |||
1110 : | =cut | ||
1111 : | |||
1112 : | sub ParseCommand { | ||
1113 : | parrello | 1.12 | # Get the parameters. |
1114 : | my ($optionTable, @inputList) = @_; | ||
1115 : | # Process any options in the input list. | ||
1116 : | my %overrides = (); | ||
1117 : | while ((@inputList > 0) && ($inputList[0] =~ /^-/)) { | ||
1118 : | # Get the current option. | ||
1119 : | my $arg = shift @inputList; | ||
1120 : | # Pull out the option name. | ||
1121 : | $arg =~ /^-([^=]*)/g; | ||
1122 : | my $name = $1; | ||
1123 : | # Check for an option value. | ||
1124 : | if ($arg =~ /\G=(.*)$/g) { | ||
1125 : | # Here we have a value for the option. | ||
1126 : | $overrides{$name} = UnEscape($1); | ||
1127 : | } else { | ||
1128 : | # Here there is no value, so we use 1. | ||
1129 : | $overrides{$name} = 1; | ||
1130 : | } | ||
1131 : | } | ||
1132 : | # Merge the options into the defaults. | ||
1133 : | GetOptions($optionTable, \%overrides); | ||
1134 : | # Translate the remaining parameters. | ||
1135 : | my @retVal = (); | ||
1136 : | for my $inputParm (@inputList) { | ||
1137 : | push @retVal, UnEscape($inputParm); | ||
1138 : | } | ||
1139 : | # Return the results. | ||
1140 : | return ($optionTable, @retVal); | ||
1141 : | olson | 1.1 | } |
1142 : | |||
1143 : | parrello | 1.9 | =head3 Escape |
1144 : | |||
1145 : | C<< my $codedString = Tracer::Escape($realString); >> | ||
1146 : | |||
1147 : | parrello | 1.25 | Escape a string for use in a command length. Tabs will be replaced by C<\t>, new-lines |
1148 : | parrello | 1.28 | replaced by C<\n>, carriage returns will be deleted, and backslashes will be doubled. The |
1149 : | result is to reverse the effect of L</UnEscape>. | ||
1150 : | parrello | 1.9 | |
1151 : | =over 4 | ||
1152 : | |||
1153 : | =item realString | ||
1154 : | |||
1155 : | String to escape. | ||
1156 : | |||
1157 : | =item RETURN | ||
1158 : | |||
1159 : | Escaped equivalent of the real string. | ||
1160 : | |||
1161 : | =back | ||
1162 : | |||
1163 : | =cut | ||
1164 : | |||
1165 : | sub Escape { | ||
1166 : | parrello | 1.12 | # Get the parameter. |
1167 : | my ($realString) = @_; | ||
1168 : | # Initialize the return variable. | ||
1169 : | my $retVal = ""; | ||
1170 : | # Loop through the parameter string, looking for sequences to escape. | ||
1171 : | while (length $realString > 0) { | ||
1172 : | # Look for the first sequence to escape. | ||
1173 : | parrello | 1.27 | if ($realString =~ /^(.*?)([\n\t\r\\])/) { |
1174 : | parrello | 1.12 | # Here we found it. The text preceding the sequence is in $1. The sequence |
1175 : | # itself is in $2. First, move the clear text to the return variable. | ||
1176 : | $retVal .= $1; | ||
1177 : | parrello | 1.14 | # Strip the processed section off the real string. |
1178 : | $realString = substr $realString, (length $2) + (length $1); | ||
1179 : | parrello | 1.28 | # Get the matched character. |
1180 : | parrello | 1.12 | my $char = $2; |
1181 : | parrello | 1.28 | # If we have a CR, we are done. |
1182 : | if ($char ne "\r") { | ||
1183 : | # It's not a CR, so encode the escape sequence. | ||
1184 : | $char =~ tr/\t\n/tn/; | ||
1185 : | $retVal .= "\\" . $char; | ||
1186 : | } | ||
1187 : | parrello | 1.12 | } else { |
1188 : | # Here there are no more escape sequences. The rest of the string is | ||
1189 : | # transferred unmodified. | ||
1190 : | $retVal .= $realString; | ||
1191 : | $realString = ""; | ||
1192 : | } | ||
1193 : | } | ||
1194 : | # Return the result. | ||
1195 : | return $retVal; | ||
1196 : | parrello | 1.9 | } |
1197 : | |||
1198 : | olson | 1.1 | =head3 UnEscape |
1199 : | |||
1200 : | C<< my $realString = Tracer::UnEscape($codedString); >> | ||
1201 : | |||
1202 : | parrello | 1.25 | Replace escape sequences with their actual equivalents. C<\t> will be replaced by |
1203 : | parrello | 1.28 | a tab, C<\n> by a new-line character, and C<\\> by a backslash. C<\r> codes will |
1204 : | be deleted. | ||
1205 : | olson | 1.1 | |
1206 : | =over 4 | ||
1207 : | |||
1208 : | =item codedString | ||
1209 : | |||
1210 : | String to un-escape. | ||
1211 : | |||
1212 : | =item RETURN | ||
1213 : | |||
1214 : | Returns a copy of the original string with the escape sequences converted to their actual | ||
1215 : | values. | ||
1216 : | |||
1217 : | =back | ||
1218 : | |||
1219 : | =cut | ||
1220 : | |||
1221 : | sub UnEscape { | ||
1222 : | parrello | 1.12 | # Get the parameter. |
1223 : | my ($codedString) = @_; | ||
1224 : | # Initialize the return variable. | ||
1225 : | my $retVal = ""; | ||
1226 : | # Only proceed if the incoming string is nonempty. | ||
1227 : | if (defined $codedString) { | ||
1228 : | # Loop through the parameter string, looking for escape sequences. We can't do | ||
1229 : | parrello | 1.25 | # translating because it causes problems with the escaped slash. ("\\t" becomes |
1230 : | # "\<tab>" no matter what we do.) | ||
1231 : | parrello | 1.12 | while (length $codedString > 0) { |
1232 : | # Look for the first escape sequence. | ||
1233 : | parrello | 1.27 | if ($codedString =~ /^(.*?)\\(\\|n|t|r)/) { |
1234 : | parrello | 1.12 | # Here we found it. The text preceding the sequence is in $1. The sequence |
1235 : | # itself is in $2. First, move the clear text to the return variable. | ||
1236 : | $retVal .= $1; | ||
1237 : | $codedString = substr $codedString, (2 + length $1); | ||
1238 : | parrello | 1.28 | # Get the escape value. |
1239 : | parrello | 1.12 | my $char = $2; |
1240 : | parrello | 1.28 | # If we have a "\r", we are done. |
1241 : | if ($char ne 'r') { | ||
1242 : | # Here it's not an 'r', so we convert it. | ||
1243 : | $char =~ tr/\\tn/\\\t\n/; | ||
1244 : | $retVal .= $char; | ||
1245 : | } | ||
1246 : | parrello | 1.12 | } else { |
1247 : | # Here there are no more escape sequences. The rest of the string is | ||
1248 : | # transferred unmodified. | ||
1249 : | $retVal .= $codedString; | ||
1250 : | $codedString = ""; | ||
1251 : | } | ||
1252 : | } | ||
1253 : | } | ||
1254 : | # Return the result. | ||
1255 : | return $retVal; | ||
1256 : | olson | 1.1 | } |
1257 : | |||
1258 : | =head3 ParseRecord | ||
1259 : | |||
1260 : | C<< my @fields = Tracer::ParseRecord($line); >> | ||
1261 : | |||
1262 : | Parse a tab-delimited data line. The data line is split into field values. Embedded tab | ||
1263 : | and new-line characters in the data line must be represented as C<\t> and C<\n>, respectively. | ||
1264 : | These will automatically be converted. | ||
1265 : | |||
1266 : | =over 4 | ||
1267 : | |||
1268 : | =item line | ||
1269 : | |||
1270 : | Line of data containing the tab-delimited fields. | ||
1271 : | |||
1272 : | =item RETURN | ||
1273 : | |||
1274 : | Returns a list of the fields found in the data line. | ||
1275 : | |||
1276 : | =back | ||
1277 : | |||
1278 : | =cut | ||
1279 : | |||
1280 : | sub ParseRecord { | ||
1281 : | parrello | 1.12 | # Get the parameter. |
1282 : | my ($line) = @_; | ||
1283 : | # Remove the trailing new-line, if any. | ||
1284 : | chomp $line; | ||
1285 : | # Split the line read into pieces using the tab character. | ||
1286 : | my @retVal = split /\t/, $line; | ||
1287 : | # Trim and fix the escapes in each piece. | ||
1288 : | for my $value (@retVal) { | ||
1289 : | # Trim leading whitespace. | ||
1290 : | $value =~ s/^\s+//; | ||
1291 : | # Trim trailing whitespace. | ||
1292 : | $value =~ s/\s+$//; | ||
1293 : | # Delete the carriage returns. | ||
1294 : | $value =~ s/\r//g; | ||
1295 : | # Convert the escapes into their real values. | ||
1296 : | $value =~ s/\\t/"\t"/ge; | ||
1297 : | $value =~ s/\\n/"\n"/ge; | ||
1298 : | } | ||
1299 : | # Return the result. | ||
1300 : | return @retVal; | ||
1301 : | olson | 1.1 | } |
1302 : | |||
1303 : | =head3 Merge | ||
1304 : | |||
1305 : | C<< my @mergedList = Tracer::Merge(@inputList); >> | ||
1306 : | |||
1307 : | Sort a list of strings and remove duplicates. | ||
1308 : | |||
1309 : | =over 4 | ||
1310 : | |||
1311 : | =item inputList | ||
1312 : | |||
1313 : | List of scalars to sort and merge. | ||
1314 : | |||
1315 : | =item RETURN | ||
1316 : | |||
1317 : | Returns a list containing the same elements sorted in ascending order with duplicates | ||
1318 : | removed. | ||
1319 : | |||
1320 : | =back | ||
1321 : | |||
1322 : | =cut | ||
1323 : | |||
1324 : | sub Merge { | ||
1325 : | parrello | 1.12 | # Get the input list in sort order. |
1326 : | my @inputList = sort @_; | ||
1327 : | # Only proceed if the list has at least two elements. | ||
1328 : | if (@inputList > 1) { | ||
1329 : | # Now we want to move through the list splicing out duplicates. | ||
1330 : | my $i = 0; | ||
1331 : | while ($i < @inputList) { | ||
1332 : | # Get the current entry. | ||
1333 : | my $thisEntry = $inputList[$i]; | ||
1334 : | # Find out how many elements duplicate the current entry. | ||
1335 : | my $j = $i + 1; | ||
1336 : | my $dup1 = $i + 1; | ||
1337 : | while ($j < @inputList && $inputList[$j] eq $thisEntry) { $j++; }; | ||
1338 : | # If the number is nonzero, splice out the duplicates found. | ||
1339 : | if ($j > $dup1) { | ||
1340 : | splice @inputList, $dup1, $j - $dup1; | ||
1341 : | } | ||
1342 : | # Now the element at position $dup1 is different from the element before it | ||
1343 : | # at position $i. We push $i forward one position and start again. | ||
1344 : | $i++; | ||
1345 : | } | ||
1346 : | } | ||
1347 : | # Return the merged list. | ||
1348 : | return @inputList; | ||
1349 : | olson | 1.1 | } |
1350 : | |||
1351 : | =head3 GetFile | ||
1352 : | |||
1353 : | parrello | 1.6 | C<< my @fileContents = Tracer::GetFile($fileName); >> |
1354 : | olson | 1.1 | |
1355 : | parrello | 1.35 | or |
1356 : | |||
1357 : | C<< my $fileContents = Tracer::GetFile($fileName); >> | ||
1358 : | |||
1359 : | Return the entire contents of a file. In list context, line-ends are removed and | ||
1360 : | each line is a list element. In scalar context, line-ends are replaced by C<\n>. | ||
1361 : | olson | 1.1 | |
1362 : | =over 4 | ||
1363 : | |||
1364 : | =item fileName | ||
1365 : | |||
1366 : | Name of the file to read. | ||
1367 : | |||
1368 : | =item RETURN | ||
1369 : | |||
1370 : | parrello | 1.6 | In a list context, returns the entire file as a list with the line terminators removed. |
1371 : | parrello | 1.39 | In a scalar context, returns the entire file as a string. If an error occurs opening |
1372 : | the file, an empty list will be returned. | ||
1373 : | olson | 1.1 | |
1374 : | =back | ||
1375 : | |||
1376 : | =cut | ||
1377 : | |||
1378 : | sub GetFile { | ||
1379 : | parrello | 1.12 | # Get the parameters. |
1380 : | my ($fileName) = @_; | ||
1381 : | # Declare the return variable. | ||
1382 : | my @retVal = (); | ||
1383 : | # Open the file for input. | ||
1384 : | my $ok = open INPUTFILE, "<$fileName"; | ||
1385 : | if (!$ok) { | ||
1386 : | # If we had an error, trace it. We will automatically return a null value. | ||
1387 : | parrello | 1.16 | Trace("Could not open \"$fileName\" for input: $!") if T(0); |
1388 : | parrello | 1.12 | } else { |
1389 : | # Read the whole file into the return variable, stripping off any terminator | ||
1390 : | parrello | 1.6 | # characters. |
1391 : | my $lineCount = 0; | ||
1392 : | parrello | 1.12 | while (my $line = <INPUTFILE>) { |
1393 : | parrello | 1.6 | $lineCount++; |
1394 : | parrello | 1.9 | $line = Strip($line); |
1395 : | parrello | 1.12 | push @retVal, $line; |
1396 : | } | ||
1397 : | # Close it. | ||
1398 : | close INPUTFILE; | ||
1399 : | parrello | 1.6 | my $actualLines = @retVal; |
1400 : | parrello | 1.12 | } |
1401 : | # Return the file's contents in the desired format. | ||
1402 : | parrello | 1.9 | if (wantarray) { |
1403 : | parrello | 1.12 | return @retVal; |
1404 : | parrello | 1.6 | } else { |
1405 : | return join "\n", @retVal; | ||
1406 : | } | ||
1407 : | olson | 1.1 | } |
1408 : | |||
1409 : | =head3 QTrace | ||
1410 : | |||
1411 : | C<< my $data = QTrace($format); >> | ||
1412 : | |||
1413 : | Return the queued trace data in the specified format. | ||
1414 : | |||
1415 : | =over 4 | ||
1416 : | |||
1417 : | =item format | ||
1418 : | |||
1419 : | C<html> to format the data as an HTML list, C<text> to format it as straight text. | ||
1420 : | |||
1421 : | =back | ||
1422 : | |||
1423 : | =cut | ||
1424 : | |||
1425 : | sub QTrace { | ||
1426 : | parrello | 1.12 | # Get the parameter. |
1427 : | my ($format) = @_; | ||
1428 : | # Create the return variable. | ||
1429 : | my $retVal = ""; | ||
1430 : | parrello | 1.14 | # Only proceed if there is an actual queue. |
1431 : | if (@Queue) { | ||
1432 : | # Process according to the format. | ||
1433 : | if ($format =~ m/^HTML$/i) { | ||
1434 : | # Convert the queue into an HTML list. | ||
1435 : | $retVal = "<ul>\n"; | ||
1436 : | for my $line (@Queue) { | ||
1437 : | my $escapedLine = CGI::escapeHTML($line); | ||
1438 : | $retVal .= "<li>$escapedLine</li>\n"; | ||
1439 : | } | ||
1440 : | $retVal .= "</ul>\n"; | ||
1441 : | } elsif ($format =~ m/^TEXT$/i) { | ||
1442 : | # Convert the queue into a list of text lines. | ||
1443 : | $retVal = join("\n", @Queue) . "\n"; | ||
1444 : | } | ||
1445 : | # Clear the queue. | ||
1446 : | @Queue = (); | ||
1447 : | parrello | 1.12 | } |
1448 : | # Return the formatted list. | ||
1449 : | return $retVal; | ||
1450 : | olson | 1.1 | } |
1451 : | |||
1452 : | =head3 Confess | ||
1453 : | |||
1454 : | C<< Confess($message); >> | ||
1455 : | |||
1456 : | parrello | 1.22 | Trace the call stack and abort the program with the specified message. When used with |
1457 : | parrello | 1.9 | the OR operator and the L</Assert> method, B<Confess> can function as a debugging assert. |
1458 : | parrello | 1.6 | So, for example |
1459 : | olson | 1.1 | |
1460 : | parrello | 1.6 | C<< Assert($recNum >= 0) || Confess("Invalid record number $recNum."); >> |
1461 : | olson | 1.1 | |
1462 : | Will abort the program with a stack trace if the value of C<$recNum> is negative. | ||
1463 : | |||
1464 : | =over 4 | ||
1465 : | |||
1466 : | =item message | ||
1467 : | |||
1468 : | Message to include in the trace. | ||
1469 : | |||
1470 : | =back | ||
1471 : | |||
1472 : | =cut | ||
1473 : | |||
1474 : | sub Confess { | ||
1475 : | parrello | 1.12 | # Get the parameters. |
1476 : | my ($message) = @_; | ||
1477 : | # Trace the call stack. | ||
1478 : | parrello | 1.22 | Cluck($message); |
1479 : | parrello | 1.12 | # Abort the program. |
1480 : | croak(">>> $message"); | ||
1481 : | olson | 1.1 | } |
1482 : | |||
1483 : | parrello | 1.6 | =head3 Assert |
1484 : | |||
1485 : | C<< Assert($condition1, $condition2, ... $conditionN); >> | ||
1486 : | |||
1487 : | Return TRUE if all the conditions are true. This method can be used in conjunction with | ||
1488 : | parrello | 1.29 | the OR operator and the L</Confess> method as a debugging assert. |
1489 : | parrello | 1.6 | So, for example |
1490 : | |||
1491 : | C<< Assert($recNum >= 0) || Confess("Invalid record number $recNum."); >> | ||
1492 : | |||
1493 : | Will abort the program with a stack trace if the value of C<$recNum> is negative. | ||
1494 : | |||
1495 : | =cut | ||
1496 : | sub Assert { | ||
1497 : | my $retVal = 1; | ||
1498 : | LOOP: for my $condition (@_) { | ||
1499 : | if (! $condition) { | ||
1500 : | $retVal = 0; | ||
1501 : | last LOOP; | ||
1502 : | } | ||
1503 : | } | ||
1504 : | return $retVal; | ||
1505 : | } | ||
1506 : | |||
1507 : | olson | 1.1 | =head3 Cluck |
1508 : | |||
1509 : | C<< Cluck($message); >> | ||
1510 : | |||
1511 : | Trace the call stack. Note that for best results, you should qualify the call with a | ||
1512 : | trace condition. For example, | ||
1513 : | |||
1514 : | C<< Cluck("Starting record parse.") if T(3); >> | ||
1515 : | |||
1516 : | will only trace the stack if the trace level for the package is 3 or more. | ||
1517 : | |||
1518 : | =over 4 | ||
1519 : | |||
1520 : | =item message | ||
1521 : | |||
1522 : | Message to include in the trace. | ||
1523 : | |||
1524 : | =back | ||
1525 : | |||
1526 : | =cut | ||
1527 : | |||
1528 : | sub Cluck { | ||
1529 : | parrello | 1.12 | # Get the parameters. |
1530 : | my ($message) = @_; | ||
1531 : | parrello | 1.5 | # Trace what's happening. |
1532 : | Trace("Stack trace for event: $message"); | ||
1533 : | parrello | 1.12 | my $confession = longmess($message); |
1534 : | # Convert the confession to a series of trace messages. Note we skip any | ||
1535 : | parrello | 1.5 | # messages relating to calls into Tracer. |
1536 : | parrello | 1.12 | for my $line (split /\s*\n/, $confession) { |
1537 : | Trace($line) if ($line !~ /Tracer\.pm/); | ||
1538 : | } | ||
1539 : | olson | 1.1 | } |
1540 : | |||
1541 : | parrello | 1.5 | =head3 Min |
1542 : | |||
1543 : | C<< my $min = Min($value1, $value2, ... $valueN); >> | ||
1544 : | |||
1545 : | Return the minimum argument. The arguments are treated as numbers. | ||
1546 : | |||
1547 : | =over 4 | ||
1548 : | |||
1549 : | =item $value1, $value2, ... $valueN | ||
1550 : | |||
1551 : | List of numbers to compare. | ||
1552 : | |||
1553 : | =item RETURN | ||
1554 : | |||
1555 : | Returns the lowest number in the list. | ||
1556 : | |||
1557 : | =back | ||
1558 : | |||
1559 : | =cut | ||
1560 : | |||
1561 : | sub Min { | ||
1562 : | parrello | 1.12 | # Get the parameters. Note that we prime the return value with the first parameter. |
1563 : | my ($retVal, @values) = @_; | ||
1564 : | # Loop through the remaining parameters, looking for the lowest. | ||
1565 : | for my $value (@values) { | ||
1566 : | if ($value < $retVal) { | ||
1567 : | $retVal = $value; | ||
1568 : | } | ||
1569 : | } | ||
1570 : | # Return the minimum found. | ||
1571 : | return $retVal; | ||
1572 : | parrello | 1.5 | } |
1573 : | |||
1574 : | =head3 Max | ||
1575 : | |||
1576 : | C<< my $max = Max($value1, $value2, ... $valueN); >> | ||
1577 : | |||
1578 : | Return the maximum argument. The arguments are treated as numbers. | ||
1579 : | |||
1580 : | =over 4 | ||
1581 : | |||
1582 : | =item $value1, $value2, ... $valueN | ||
1583 : | |||
1584 : | List of numbers to compare. | ||
1585 : | |||
1586 : | =item RETURN | ||
1587 : | |||
1588 : | Returns the highest number in the list. | ||
1589 : | |||
1590 : | =back | ||
1591 : | |||
1592 : | =cut | ||
1593 : | |||
1594 : | sub Max { | ||
1595 : | parrello | 1.12 | # Get the parameters. Note that we prime the return value with the first parameter. |
1596 : | my ($retVal, @values) = @_; | ||
1597 : | # Loop through the remaining parameters, looking for the highest. | ||
1598 : | for my $value (@values) { | ||
1599 : | if ($value > $retVal) { | ||
1600 : | $retVal = $value; | ||
1601 : | } | ||
1602 : | } | ||
1603 : | # Return the maximum found. | ||
1604 : | return $retVal; | ||
1605 : | parrello | 1.5 | } |
1606 : | |||
1607 : | =head3 AddToListMap | ||
1608 : | |||
1609 : | C<< Tracer::AddToListMap(\%hash, $key, $value); >> | ||
1610 : | |||
1611 : | Add a key-value pair to a hash of lists. If no value exists for the key, a singleton list | ||
1612 : | is created for the key. Otherwise, the new value is pushed onto the list. | ||
1613 : | |||
1614 : | =over 4 | ||
1615 : | |||
1616 : | =item hash | ||
1617 : | |||
1618 : | Reference to the target hash. | ||
1619 : | |||
1620 : | =item key | ||
1621 : | |||
1622 : | Key for which the value is to be added. | ||
1623 : | |||
1624 : | =item value | ||
1625 : | |||
1626 : | Value to add to the key's value list. | ||
1627 : | |||
1628 : | =back | ||
1629 : | |||
1630 : | =cut | ||
1631 : | |||
1632 : | sub AddToListMap { | ||
1633 : | # Get the parameters. | ||
1634 : | my ($hash, $key, $value) = @_; | ||
1635 : | # Process according to whether or not the key already has a value. | ||
1636 : | if (! exists $hash->{$key}) { | ||
1637 : | $hash->{$key} = [$value]; | ||
1638 : | } else { | ||
1639 : | push @{$hash->{$key}}, $value; | ||
1640 : | } | ||
1641 : | } | ||
1642 : | olson | 1.1 | |
1643 : | parrello | 1.7 | =head3 DebugMode |
1644 : | |||
1645 : | C<< if (Tracer::DebugMode) { ...code... } >> | ||
1646 : | |||
1647 : | parrello | 1.22 | Return TRUE if debug mode has been turned on, else output an error |
1648 : | page and return FALSE. | ||
1649 : | parrello | 1.7 | |
1650 : | Certain CGI scripts are too dangerous to exist in the production | ||
1651 : | environment. This method provides a simple way to prevent them | ||
1652 : | parrello | 1.21 | from working unless they are explicitly turned on by creating a password |
1653 : | cookie via the B<SetPassword> script. If debugging mode | ||
1654 : | parrello | 1.22 | is not turned on, an error web page will be output directing the |
1655 : | user to enter in the correct password. | ||
1656 : | parrello | 1.7 | |
1657 : | =cut | ||
1658 : | |||
1659 : | sub DebugMode { | ||
1660 : | parrello | 1.12 | # Declare the return variable. |
1661 : | parrello | 1.21 | my $retVal = 0; |
1662 : | parrello | 1.12 | # Check the debug configuration. |
1663 : | parrello | 1.21 | my $password = CGI::cookie("DebugMode"); |
1664 : | my $encrypted = Digest::MD5::md5_hex($password); | ||
1665 : | if ($encrypted eq "252dec43280e0c0d6a75ffcec486e61d") { | ||
1666 : | parrello | 1.12 | $retVal = 1; |
1667 : | } else { | ||
1668 : | # Here debug mode is off, so we generate an error page. | ||
1669 : | parrello | 1.9 | my $pageString = PageBuilder::Build("<Html/ErrorPage.html", {}, "Html"); |
1670 : | parrello | 1.12 | print $pageString; |
1671 : | } | ||
1672 : | # Return the determination indicator. | ||
1673 : | parrello | 1.18 | return $retVal; |
1674 : | parrello | 1.9 | } |
1675 : | |||
1676 : | =head3 Strip | ||
1677 : | |||
1678 : | C<< my $string = Tracer::Strip($line); >> | ||
1679 : | |||
1680 : | Strip all line terminators off a string. This is necessary when dealing with files | ||
1681 : | that may have been transferred back and forth several times among different | ||
1682 : | operating environments. | ||
1683 : | |||
1684 : | =over 4 | ||
1685 : | |||
1686 : | =item line | ||
1687 : | |||
1688 : | Line of text to be stripped. | ||
1689 : | |||
1690 : | =item RETURN | ||
1691 : | |||
1692 : | The same line of text with all the line-ending characters chopped from the end. | ||
1693 : | |||
1694 : | =back | ||
1695 : | |||
1696 : | =cut | ||
1697 : | |||
1698 : | sub Strip { | ||
1699 : | parrello | 1.12 | # Get a copy of the parameter string. |
1700 : | my ($string) = @_; | ||
1701 : | parrello | 1.29 | my $retVal = (defined $string ? $string : ""); |
1702 : | parrello | 1.9 | # Strip the line terminator characters. |
1703 : | $retVal =~ s/(\r|\n)+$//g; | ||
1704 : | parrello | 1.12 | # Return the result. |
1705 : | return $retVal; | ||
1706 : | parrello | 1.9 | } |
1707 : | |||
1708 : | =head3 Pad | ||
1709 : | |||
1710 : | C<< my $paddedString = Tracer::Pad($string, $len, $left, $padChar); >> | ||
1711 : | |||
1712 : | Pad a string to a specified length. The pad character will be a | ||
1713 : | space, and the padding will be on the right side unless specified | ||
1714 : | in the third parameter. | ||
1715 : | |||
1716 : | =over 4 | ||
1717 : | |||
1718 : | =item string | ||
1719 : | |||
1720 : | String to be padded. | ||
1721 : | |||
1722 : | =item len | ||
1723 : | |||
1724 : | Desired length of the padded string. | ||
1725 : | |||
1726 : | =item left (optional) | ||
1727 : | |||
1728 : | TRUE if the string is to be left-padded; otherwise it will be padded on the right. | ||
1729 : | |||
1730 : | =item padChar (optional) | ||
1731 : | |||
1732 : | parrello | 1.22 | Character to use for padding. The default is a space. |
1733 : | |||
1734 : | parrello | 1.9 | =item RETURN |
1735 : | |||
1736 : | parrello | 1.22 | Returns a copy of the original string with the pad character added to the |
1737 : | specified end so that it achieves the desired length. | ||
1738 : | parrello | 1.9 | |
1739 : | =back | ||
1740 : | |||
1741 : | =cut | ||
1742 : | |||
1743 : | sub Pad { | ||
1744 : | parrello | 1.12 | # Get the parameters. |
1745 : | my ($string, $len, $left, $padChar) = @_; | ||
1746 : | # Compute the padding character. | ||
1747 : | if (! defined $padChar) { | ||
1748 : | $padChar = " "; | ||
1749 : | } | ||
1750 : | # Compute the number of spaces needed. | ||
1751 : | my $needed = $len - length $string; | ||
1752 : | # Copy the string into the return variable. | ||
1753 : | my $retVal = $string; | ||
1754 : | # Only proceed if padding is needed. | ||
1755 : | if ($needed > 0) { | ||
1756 : | # Create the pad string. | ||
1757 : | my $pad = $padChar x $needed; | ||
1758 : | # Affix it to the return value. | ||
1759 : | if ($left) { | ||
1760 : | $retVal = $pad . $retVal; | ||
1761 : | } else { | ||
1762 : | $retVal .= $pad; | ||
1763 : | } | ||
1764 : | } | ||
1765 : | # Return the result. | ||
1766 : | return $retVal; | ||
1767 : | parrello | 1.7 | } |
1768 : | |||
1769 : | parrello | 1.29 | =head3 EOF |
1770 : | |||
1771 : | This is a constant that is lexically greater than any useful string. | ||
1772 : | |||
1773 : | =cut | ||
1774 : | |||
1775 : | sub EOF { | ||
1776 : | return "\xFF\xFF\xFF\xFF\xFF"; | ||
1777 : | } | ||
1778 : | |||
1779 : | parrello | 1.15 | =head3 TICK |
1780 : | |||
1781 : | C<< my @results = TICK($commandString); >> | ||
1782 : | |||
1783 : | Perform a back-tick operation on a command. If this is a Windows environment, any leading | ||
1784 : | dot-slash (C<./> will be removed. So, for example, if you were doing | ||
1785 : | |||
1786 : | `./protein.cgi` | ||
1787 : | |||
1788 : | from inside a CGI script, it would work fine in Unix, but would issue an error message | ||
1789 : | in Windows complaining that C<'.'> is not a valid command. If instead you code | ||
1790 : | |||
1791 : | TICK("./protein.cgi") | ||
1792 : | |||
1793 : | it will work correctly in both environments. | ||
1794 : | |||
1795 : | =over 4 | ||
1796 : | |||
1797 : | =item commandString | ||
1798 : | |||
1799 : | The command string to pass to the system. | ||
1800 : | |||
1801 : | =item RETURN | ||
1802 : | |||
1803 : | Returns the standard output from the specified command, as a list. | ||
1804 : | |||
1805 : | =back | ||
1806 : | |||
1807 : | =cut | ||
1808 : | #: Return Type @; | ||
1809 : | sub TICK { | ||
1810 : | # Get the parameters. | ||
1811 : | my ($commandString) = @_; | ||
1812 : | # Chop off the dot-slash if this is Windows. | ||
1813 : | if ($FIG_Config::win_mode) { | ||
1814 : | $commandString =~ s!^\./!!; | ||
1815 : | } | ||
1816 : | # Activate the command and return the result. | ||
1817 : | return `$commandString`; | ||
1818 : | } | ||
1819 : | |||
1820 : | parrello | 1.35 | =head3 ScriptSetup |
1821 : | |||
1822 : | C<< my ($query, $varHash) = ScriptSetup(); >> | ||
1823 : | |||
1824 : | Perform standard tracing and debugging setup for scripts. The value returned is | ||
1825 : | the CGI object followed by a pre-built variable hash. | ||
1826 : | |||
1827 : | The C<Trace> query parameter is used to determine whether or not tracing is active and | ||
1828 : | which trace modules (other than C<Tracer> and C<FIG>) should be turned on. Specifying | ||
1829 : | the C<CGI> trace module will trace parameter and environment information. Parameters are | ||
1830 : | traced at level 3 and environment variables at level 4. At the end of the script, the | ||
1831 : | client should call L</ScriptFinish> to output the web page. | ||
1832 : | |||
1833 : | =cut | ||
1834 : | |||
1835 : | sub ScriptSetup { | ||
1836 : | # Get the CGI query object. | ||
1837 : | my $query = CGI->new(); | ||
1838 : | # Check for tracing. Set it up if the user asked for it. | ||
1839 : | if ($query->param('Trace')) { | ||
1840 : | # Set up tracing to be queued for display at the bottom of the web page. | ||
1841 : | TSetup($query->param('Trace') . " FIG Tracer", "QUEUE"); | ||
1842 : | # Trace the parameter and environment data. | ||
1843 : | if (T(CGI => 3)) { | ||
1844 : | # Here we want to trace the parameter data. | ||
1845 : | my @names = $query->param; | ||
1846 : | for my $parmName (sort @names) { | ||
1847 : | # Note we skip "Trace", which is for our use only. | ||
1848 : | if ($parmName ne 'Trace') { | ||
1849 : | my @values = $query->param($parmName); | ||
1850 : | Trace("CGI: $parmName = " . join(", ", @values)); | ||
1851 : | } | ||
1852 : | } | ||
1853 : | } | ||
1854 : | if (T(CGI => 4)) { | ||
1855 : | # Here we want the environment data too. | ||
1856 : | for my $envName (sort keys %ENV) { | ||
1857 : | Trace("ENV: $envName = $ENV{$envName}"); | ||
1858 : | } | ||
1859 : | } | ||
1860 : | } else { | ||
1861 : | # Here tracing is to be turned off. All we allow is errors traced into the | ||
1862 : | # error log. | ||
1863 : | TSetup("0", "WARN"); | ||
1864 : | } | ||
1865 : | # Create the variable hash. | ||
1866 : | my $varHash = { DebugData => '' }; | ||
1867 : | # If we're in DEBUG mode, set up the debug mode data for forms. | ||
1868 : | if (Tracer::DebugMode) { | ||
1869 : | $varHash->{DebugData} = GetFile("Html/DebugFragment.html"); | ||
1870 : | } | ||
1871 : | # Return the query object and variable hash. | ||
1872 : | return ($query, $varHash); | ||
1873 : | } | ||
1874 : | |||
1875 : | =head3 ScriptFinish | ||
1876 : | |||
1877 : | C<< ScriptFinish($webData, $varHash); >> | ||
1878 : | |||
1879 : | Output a web page at the end of a script. Either the string to be output or the | ||
1880 : | name of a template file can be specified. If the second parameter is omitted, | ||
1881 : | it is assumed we have a string to be output; otherwise, it is assumed we have the | ||
1882 : | name of a template file. The template should have the variable C<DebugData> | ||
1883 : | specified in any form that invokes a standard script. If debugging mode is turned | ||
1884 : | on, a form field will be put in that allows the user to enter tracing data. | ||
1885 : | Trace messages will be placed immediately before the terminal C<BODY> tag in | ||
1886 : | the output, formatted as a list. | ||
1887 : | |||
1888 : | A typical standard script would loook like the following. | ||
1889 : | |||
1890 : | BEGIN { | ||
1891 : | # Print the HTML header. | ||
1892 : | print "CONTENT-TYPE: text/html\n\n"; | ||
1893 : | } | ||
1894 : | use Tracer; | ||
1895 : | use CGI; | ||
1896 : | use FIG; | ||
1897 : | # ... more uses ... | ||
1898 : | |||
1899 : | my ($query, $varHash) = ScriptSetup(); | ||
1900 : | eval { | ||
1901 : | # ... get data from $query, put it in $varHash ... | ||
1902 : | }; | ||
1903 : | if ($@) { | ||
1904 : | Trace("Script Error: $@") if T(0); | ||
1905 : | } | ||
1906 : | ScriptFinish("Html/MyTemplate.html", $varHash); | ||
1907 : | |||
1908 : | The idea here is that even if the script fails, you'll see trace messages and | ||
1909 : | useful output. | ||
1910 : | |||
1911 : | =over 4 | ||
1912 : | |||
1913 : | =item webData | ||
1914 : | |||
1915 : | A string containing either the full web page to be written to the output or the | ||
1916 : | name of a template file from which the page is to be constructed. If the name | ||
1917 : | of a template file is specified, then the second parameter must be present; | ||
1918 : | otherwise, it must be absent. | ||
1919 : | |||
1920 : | =item varHash (optional) | ||
1921 : | |||
1922 : | If specified, then a reference to a hash mapping variable names for a template | ||
1923 : | to their values. The template file will be read into memory, and variable markers | ||
1924 : | will be replaced by data in this hash reference. | ||
1925 : | |||
1926 : | parrello | 1.37 | =back |
1927 : | |||
1928 : | parrello | 1.35 | =cut |
1929 : | |||
1930 : | sub ScriptFinish { | ||
1931 : | # Get the parameters. | ||
1932 : | my ($webData, $varHash) = @_; | ||
1933 : | # Check for a template file situation. | ||
1934 : | my $outputString; | ||
1935 : | if (defined $varHash) { | ||
1936 : | # Here we have a template file. We need to apply the variables to the template. | ||
1937 : | $outputString = PageBuilder::Build("<$webData", $varHash, "Html"); | ||
1938 : | } else { | ||
1939 : | # Here the user gave us a raw string. | ||
1940 : | $outputString = $webData; | ||
1941 : | } | ||
1942 : | # Check for trace messages. | ||
1943 : | if ($Destination eq "QUEUE") { | ||
1944 : | # We have trace messages, so we want to put them at the end of the body. This | ||
1945 : | # is either at the end of the whole string or at the beginning of the BODY | ||
1946 : | # end-tag. | ||
1947 : | my $pos = length $outputString; | ||
1948 : | if ($outputString =~ m#</body>#gi) { | ||
1949 : | $pos = (pos $outputString) - 7; | ||
1950 : | } | ||
1951 : | substr $outputString, $pos, 0, QTrace('Html'); | ||
1952 : | } | ||
1953 : | # Write the output string. | ||
1954 : | print $outputString; | ||
1955 : | } | ||
1956 : | |||
1957 : | parrello | 1.37 | =head3 Insure |
1958 : | |||
1959 : | C<< Insure($dirName); >> | ||
1960 : | |||
1961 : | Insure a directory is present. | ||
1962 : | |||
1963 : | =over 4 | ||
1964 : | |||
1965 : | =item dirName | ||
1966 : | |||
1967 : | Name of the directory to check. If it does not exist, it will be created. | ||
1968 : | |||
1969 : | =back | ||
1970 : | |||
1971 : | =cut | ||
1972 : | |||
1973 : | sub Insure { | ||
1974 : | my ($dirName) = @_; | ||
1975 : | if (! -d $dirName) { | ||
1976 : | Trace("Creating $dirName directory.") if T(2); | ||
1977 : | parrello | 1.43 | eval { mkpath $dirName; }; |
1978 : | if ($@) { | ||
1979 : | Confess("Error creating $dirName: $@"); | ||
1980 : | } | ||
1981 : | } | ||
1982 : | } | ||
1983 : | |||
1984 : | =head3 ChDir | ||
1985 : | |||
1986 : | C<< ChDir($dirName); >> | ||
1987 : | |||
1988 : | Change to the specified directory. | ||
1989 : | |||
1990 : | =over 4 | ||
1991 : | |||
1992 : | =item dirName | ||
1993 : | |||
1994 : | Name of the directory to which we want to change. | ||
1995 : | |||
1996 : | =back | ||
1997 : | |||
1998 : | =cut | ||
1999 : | |||
2000 : | sub ChDir { | ||
2001 : | my ($dirName) = @_; | ||
2002 : | if (! -d $dirName) { | ||
2003 : | Confess("Cannot change to directory $dirName: no such directory."); | ||
2004 : | } else { | ||
2005 : | Trace("Changing to directory $dirName.") if T(4); | ||
2006 : | my $okFlag = chdir $dirName; | ||
2007 : | if (! $okFlag) { | ||
2008 : | Confess("Error switching to directory $dirName."); | ||
2009 : | } | ||
2010 : | parrello | 1.37 | } |
2011 : | } | ||
2012 : | |||
2013 : | parrello | 1.46 | =head3 SetPermissions |
2014 : | |||
2015 : | parrello | 1.49 | C<< Tracer::SetPermissions($dirName, $group, $mask, %otherMasks); >> |
2016 : | parrello | 1.46 | |
2017 : | Set the permissions for a directory and all the files and folders inside it. | ||
2018 : | In addition, the group ownership will be changed to the specified value. | ||
2019 : | |||
2020 : | This method is more vulnerable than most to permission and compatability | ||
2021 : | problems, so it does internal error recovery. | ||
2022 : | |||
2023 : | =over 4 | ||
2024 : | |||
2025 : | =item dirName | ||
2026 : | |||
2027 : | Name of the directory to process. | ||
2028 : | |||
2029 : | =item group | ||
2030 : | |||
2031 : | Name of the group to be assigned. | ||
2032 : | |||
2033 : | =item mask | ||
2034 : | |||
2035 : | Permission mask. Bits that are C<1> in this mask will be ORed into the | ||
2036 : | permission bits of any file or directory that does not already have them | ||
2037 : | set to 1. | ||
2038 : | |||
2039 : | parrello | 1.49 | =item otherMasks |
2040 : | |||
2041 : | Map of search patterns to permission masks. If a directory name matches | ||
2042 : | one of the patterns, that directory and all its members and subdirectories | ||
2043 : | will be assigned the new pattern. For example, the following would | ||
2044 : | assign 01664 to most files, but would use 01777 for directories named C<tmp>. | ||
2045 : | |||
2046 : | Tracer::SetPermissions($dirName, 'fig', 01664, '^tmp$' => 01777); | ||
2047 : | |||
2048 : | The list is ordered, so the following would use 0777 for C<tmp1> and | ||
2049 : | 0666 for C<tmp>, C<tmp2>, or C<tmp3>. | ||
2050 : | |||
2051 : | Tracer::SetPermissions($dirName, 'fig', 01664, '^tmp1' => 0777, | ||
2052 : | '^tmp' => 0666); | ||
2053 : | |||
2054 : | Note that the pattern matches are all case-insensitive, and only directory | ||
2055 : | names are matched, not file names. | ||
2056 : | |||
2057 : | parrello | 1.46 | =back |
2058 : | |||
2059 : | =cut | ||
2060 : | |||
2061 : | sub SetPermissions { | ||
2062 : | # Get the parameters. | ||
2063 : | parrello | 1.49 | my ($dirName, $group, $mask, @otherMasks) = @_; |
2064 : | parrello | 1.46 | # Set up for error recovery. |
2065 : | eval { | ||
2066 : | parrello | 1.49 | # Switch to the specified directory. |
2067 : | parrello | 1.46 | ChDir($dirName); |
2068 : | # Get the group ID. | ||
2069 : | my $gid = getgrnam($group); | ||
2070 : | parrello | 1.50 | # Get the mask for tracing. |
2071 : | my $traceMask = sprintf("%04o", $mask); | ||
2072 : | Trace("Fixing permissions for directory $dirName using group $group($gid) and mask $traceMask.") if T(2); | ||
2073 : | parrello | 1.46 | my $fixCount = 0; |
2074 : | my $lookCount = 0; | ||
2075 : | # @dirs will be a stack of directories to be processed. | ||
2076 : | my @dirs = (getcwd()); | ||
2077 : | while (scalar(@dirs) > 0) { | ||
2078 : | # Get the current directory. | ||
2079 : | my $dir = pop @dirs; | ||
2080 : | parrello | 1.49 | # Check for a match to one of the specified directory names. To do |
2081 : | # that, we need to pull the individual part of the name off of the | ||
2082 : | # whole path. | ||
2083 : | my $simpleName = $dir; | ||
2084 : | if ($dir =~ m!/(.+)$!) { | ||
2085 : | $simpleName = $1; | ||
2086 : | } | ||
2087 : | # Search for a match. | ||
2088 : | my $match = 0; | ||
2089 : | my $i; | ||
2090 : | for ($i = 0; $i < $#otherMasks && ! $match; $i += 2) { | ||
2091 : | my $pattern = $otherMasks[$i]; | ||
2092 : | if ($simpleName =~ /$pattern/i) { | ||
2093 : | $match = 1; | ||
2094 : | parrello | 1.46 | } |
2095 : | parrello | 1.49 | } |
2096 : | # Check for a match. | ||
2097 : | if ($match && $otherMasks[$i+1] != $mask) { | ||
2098 : | # This directory matches one of the incoming patterns, and it's | ||
2099 : | # a different mask, so we process it recursively with that mask. | ||
2100 : | SetPermissions($dir, $group, $otherMasks[$i+1], @otherMasks); | ||
2101 : | } else { | ||
2102 : | # Here we can process normally. Get all of the non-hidden members. | ||
2103 : | my @submems = OpenDir($dir, 1); | ||
2104 : | for my $submem (@submems) { | ||
2105 : | # Get the full name. | ||
2106 : | my $thisMem = "$dir/$submem"; | ||
2107 : | Trace("Checking member $thisMem.") if T(4); | ||
2108 : | $lookCount++; | ||
2109 : | if ($lookCount % 1000 == 0) { | ||
2110 : | parrello | 1.50 | Trace("$lookCount members examined. Current is $thisMem. Mask is $traceMask") if T(3); |
2111 : | parrello | 1.49 | } |
2112 : | # Fix the group. | ||
2113 : | chown -1, $gid, $thisMem; | ||
2114 : | # Insure this member is not a symlink. | ||
2115 : | if (! -l $thisMem) { | ||
2116 : | # Get its info. | ||
2117 : | my $fileInfo = stat $thisMem; | ||
2118 : | # Only proceed if we got the info. Otherwise, it's a hard link | ||
2119 : | # and we want to skip it anyway. | ||
2120 : | if ($fileInfo) { | ||
2121 : | my $fileMode = $fileInfo->mode; | ||
2122 : | if (($fileMode & $mask) == 0) { | ||
2123 : | # Fix this member. | ||
2124 : | $fileMode |= $mask; | ||
2125 : | chmod $fileMode, $thisMem; | ||
2126 : | $fixCount++; | ||
2127 : | } | ||
2128 : | # If it's a subdirectory, stack it. | ||
2129 : | if (-d $thisMem) { | ||
2130 : | push @dirs, $thisMem; | ||
2131 : | } | ||
2132 : | parrello | 1.46 | } |
2133 : | } | ||
2134 : | } | ||
2135 : | } | ||
2136 : | } | ||
2137 : | Trace("$lookCount files and directories processed, $fixCount fixed.") if T(2); | ||
2138 : | }; | ||
2139 : | # Check for an error. | ||
2140 : | if ($@) { | ||
2141 : | Confess("SetPermissions error: $@"); | ||
2142 : | } | ||
2143 : | } | ||
2144 : | |||
2145 : | redwards | 1.8 | 1; |
MCS Webmaster | ViewVC Help |
Powered by ViewVC 1.0.3 |