[system] / trunk / webwork2 / lib / WeBWorK / Utils.pm Repository:
ViewVC logotype

Annotation of /trunk/webwork2/lib/WeBWorK/Utils.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1663 - (view) (download) (as text)

1 : sh002i 440 ################################################################################
2 : sh002i 1663 # WeBWorK Online Homework Delivery System
3 :     # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/
4 :     # $CVSHeader$
5 :     #
6 :     # This program is free software; you can redistribute it and/or modify it under
7 :     # the terms of either: (a) the GNU General Public License as published by the
8 :     # Free Software Foundation; either version 2, or (at your option) any later
9 :     # version, or (b) the "Artistic License" which comes with this package.
10 :     #
11 :     # This program is distributed in the hope that it will be useful, but WITHOUT
12 :     # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13 :     # FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the
14 :     # Artistic License for more details.
15 : sh002i 440 ################################################################################
16 :    
17 : sh002i 410 package WeBWorK::Utils;
18 : sh002i 818 use base qw(Exporter);
19 : sh002i 410
20 : sh002i 455 =head1 NAME
21 :    
22 :     WeBWorK::Utils - useful utilities used by other WeBWorK modules.
23 :    
24 :     =cut
25 :    
26 : sh002i 412 use strict;
27 :     use warnings;
28 : sh002i 1257 #use Apache::DB;
29 : sh002i 412 use Date::Format;
30 :     use Date::Parse;
31 : sh002i 1145 use Errno;
32 : sh002i 1150 use File::Path qw(rmtree);
33 : sh002i 412
34 : sh002i 1145 use constant MKDIR_ATTEMPTS => 10;
35 :    
36 : sh002i 410 our @EXPORT = ();
37 : sh002i 424 our @EXPORT_OK = qw(
38 :     runtime_use
39 :     readFile
40 : sh002i 1150 readDirectory
41 : sh002i 424 formatDateTime
42 :     parseDateTime
43 : sh002i 562 writeLog
44 : gage 1387 writeCourseLog
45 : sh002i 562 writeTimingLogEntry
46 : malsyned 970 list2hash
47 :     max
48 : sh002i 427 dbDecode
49 :     dbEncode
50 : sh002i 429 decodeAnswers
51 :     encodeAnswers
52 : sh002i 424 ref2string
53 : sh002i 1111 sortByName
54 : sh002i 1145 makeTempDirectory
55 : sh002i 1150 removeTempDirectory
56 : sh002i 1145 pretty_print_rh
57 : malsyned 1287 cryptPassword
58 : sh002i 424 );
59 : sh002i 410
60 :     sub runtime_use($) {
61 :     return unless @_;
62 : sh002i 424 eval "package Main; require $_[0]; import $_[0]";
63 : sh002i 410 die $@ if $@;
64 :     }
65 :    
66 : sh002i 1257 #sub backtrace {
67 :     # my ($style) = @_;
68 :     # $style = "warn" unless $style;
69 :     # my @bt = DB->backtrace;
70 :     # shift @bt; # Remove "backtrace" from the backtrace;
71 :     # if ($style eq "die") {
72 :     # die join "\n", @bt;
73 :     # } elsif ($style eq "warn") {
74 :     # warn join "\n", @bt;
75 :     # } elsif ($style eq "print") {
76 :     # print join "\n", @bt;
77 :     # } elsif ($style eq "return") {
78 :     # return @bt;
79 :     # }
80 :     #}
81 : malsyned 1045
82 : sh002i 410 sub readFile($) {
83 :     my $fileName = shift;
84 : sh002i 1150 local $/ = undef; # slurp the whole thing into one string
85 :     open my $dh, "<", $fileName
86 :     or die "failed to read file $fileName: $!";
87 :     my $result = <$dh>;
88 :     close $dh;
89 : sh002i 410 return $result;
90 :     }
91 : sh002i 412
92 : malsyned 974 sub readDirectory($) {
93 : sh002i 1150 my $dirName = shift;
94 :     opendir my $dh, $dirName
95 : sh002i 1529 or die "Failed to read directory $dirName: $!";
96 : sh002i 1150 my @result = readdir $dh;
97 :     close $dh;
98 :     return @result;
99 : malsyned 974 }
100 :    
101 : sh002i 412 sub formatDateTime($) {
102 :     my $dateTime = shift;
103 : sh002i 558 # "standard" WeBWorK date/time format (for set definition files):
104 : sh002i 412 # %m month number, starting with 01
105 :     # %d numeric day of the month, with leading zeros (eg 01..31)
106 :     # %y year (2 digits)
107 :     # %I hour, 12 hour clock, leading 0's)
108 :     # %M minute, leading 0's
109 :     # %P am or pm (Yes %p and %P are backwards :)
110 : gage 1492 #return time2str("%m/%d/%y %I:%M%P", $dateTime);
111 : gage 1481 return time2str("%m/%d/%y at %I:%M%P", $dateTime);
112 : sh002i 412 }
113 :    
114 :     sub parseDateTime($) {
115 : sh002i 424 my $string = shift;
116 : gage 1492 # need to bring our string from "%m/%d/%y at %I:%M%P" to "%m/%d/%y %I:%M%P" format.
117 :     $string =~ s/\bat\b/ /;
118 : sh002i 737 return str2time($string);
119 : sh002i 412 }
120 : sh002i 422
121 : sh002i 562 sub writeLog($$@) {
122 :     my ($ce, $facility, @message) = @_;
123 :     unless ($ce->{webworkFiles}->{logs}->{$facility}) {
124 :     warn "There is no log file for the $facility facility defined.\n";
125 :     return;
126 :     }
127 :     my $logFile = $ce->{webworkFiles}->{logs}->{$facility};
128 :     local *LOG;
129 :     if (open LOG, ">>", $logFile) {
130 :     print LOG "[", time2str("%a %b %d %H:%M:%S %Y", time), "] @message\n";
131 :     close LOG;
132 :     } else {
133 :     warn "failed to open $logFile for writing: $!";
134 :     }
135 :     }
136 : sh002i 558
137 : gage 1387 sub writeCourseLog($$@) {
138 :     my ($ce, $facility, @message) = @_;
139 :     unless ($ce->{courseFiles}->{logs}->{$facility}) {
140 :     warn "There is no course log file for the $facility facility defined.\n";
141 :     return;
142 :     }
143 :     my $logFile = $ce->{courseFiles}->{logs}->{$facility};
144 :     local *LOG;
145 :     if (open LOG, ">>", $logFile) {
146 :     print LOG "[", time2str("%a %b %d %H:%M:%S %Y", time), "] @message\n";
147 :     close LOG;
148 :     } else {
149 :     warn "failed to open $logFile for writing: $!";
150 :     }
151 :     }
152 :    
153 :    
154 : sh002i 631 # $ce - a WeBWork::CourseEnvironment object
155 :     # $function - fully qualified function name
156 :     # $details - any information, do not use the characters '[' or ']'
157 : sh002i 692 # $beginEnd - the string "begin", "intermediate", or "end"
158 :     # use the intermediate step begun or completed for INTERMEDIATE
159 : sh002i 631 # use an empty string for $details when calling for END
160 : sh002i 562 sub writeTimingLogEntry($$$$) {
161 :     my ($ce, $function, $details, $beginEnd) = @_;
162 :     return unless defined $ce->{webworkFiles}->{logs}->{timing};
163 : sh002i 692 $beginEnd = ($beginEnd eq "begin") ? ">" : ($beginEnd eq "end") ? "<" : "-";
164 : sh002i 562 writeLog($ce, "timing", "$$ ".time." $beginEnd $function [$details]");
165 :     }
166 :    
167 : malsyned 970 sub list2hash {
168 :     map {$_ => "0"} @_;
169 :     }
170 :    
171 :     sub max {
172 :     my $soFar;
173 :     foreach my $item (@_) {
174 :     $soFar = $item unless defined $soFar;
175 :     if ($item > $soFar) {
176 :     $soFar = $item;
177 :     }
178 :     }
179 : malsyned 979 return defined $soFar ? $soFar : 0;
180 : malsyned 970 }
181 :    
182 : sh002i 429 sub decodeAnswers($) {
183 :     my $string = shift;
184 :     return unless defined $string and $string;
185 :     my @array = split m/##/, $string;
186 :     $array[$_] =~ s/\\#\\/#/g foreach 0 .. $#array;
187 : sh002i 445 push @array, "" if @array%2;
188 : sh002i 429 return @array; # it's actually a hash ;)
189 :     }
190 :    
191 :     sub encodeAnswers(\%\@) {
192 :     my %hash = %{ shift() };
193 :     my @order = @{ shift() };
194 :     my $string;
195 :     foreach my $name (@order) {
196 :     my $value = defined $hash{$name} ? $hash{$name} : "";
197 :     $name =~ s/#/\\#\\/g; # this is a WEIRD way to escape things
198 :     $value =~ s/#/\\#\\/g; # and it's not my fault!
199 : sh002i 1095 if ($value =~ m/\\$/) {
200 :     # if the value ends with a backslash, string2hash will
201 :     # interpret that as a normal escape sequence (not part
202 :     # of the weird pound escape sequence) if the next
203 :     # character is &. So we have to protect against this.
204 :     # will adding a spcae at the end of the last answer
205 :     # hurt anything? i don't think so...
206 :     $value .= " ";
207 :     }
208 : sh002i 429 $string .= "$name##$value##"; # this is also not my fault
209 :     }
210 :     $string =~ s/##$//; # remove last pair of hashs
211 :     return $string;
212 :     }
213 :    
214 : sh002i 424 sub ref2string($;$);
215 :     sub ref2string($;$) {
216 :     my $ref = shift;
217 :     my $dontExpand = shift || {};
218 :     my $refType = ref $ref;
219 : sh002i 422 my $result;
220 : sh002i 424 if ($refType and not $dontExpand->{$refType}) {
221 :     my $baseType = refBaseType($ref);
222 :     $result .= '<font size="1" color="grey">' . $refType;
223 : sh002i 425 $result .= " ($baseType)" if $baseType and $refType ne $baseType;
224 : sh002i 424 $result .= ":</font><br>";
225 :     $result .= '<table border="1" cellpadding="2">';
226 :     if ($baseType eq "HASH") {
227 :     my %hash = %$ref;
228 :     foreach (sort keys %hash) {
229 :     $result .= '<tr valign="top">';
230 :     $result .= "<td>$_</td>";
231 :     $result .= "<td>" . ref2string($hash{$_}, $dontExpand) . "</td>";
232 :     $result .= "</tr>";
233 :     }
234 :     } elsif ($baseType eq "ARRAY") {
235 :     my @array = @$ref;
236 : sh002i 429 # special case for Problem, Set, and User objects, which are defined
237 :     # using lists and contain a @FIELDS package variable:
238 :     no strict 'refs';
239 :     my @FIELDS = eval { @{$refType."::FIELDS"} };
240 :     use strict 'refs';
241 :     undef @FIELDS unless scalar @FIELDS == scalar @array and not $@;
242 : sh002i 424 foreach (0 .. $#array) {
243 :     $result .= '<tr valign="top">';
244 :     $result .= "<td>$_</td>";
245 : sh002i 429 $result .= "<td>".$FIELDS[$_]."</td>" if @FIELDS;
246 : sh002i 424 $result .= "<td>" . ref2string($array[$_], $dontExpand) . "</td>";
247 :     $result .= "</tr>";
248 :     }
249 :     } elsif ($baseType eq "SCALAR") {
250 :     my $scalar = $$ref;
251 :     $result .= '<tr valign="top">';
252 :     $result .= "<td>$scalar</td>";
253 :     $result .= "</tr>";
254 : sh002i 422 } else {
255 : sh002i 424 # perhaps a coderef? in any case, i don't feel like dealing with it!
256 :     $result .= '<tr valign="top">';
257 :     $result .= "<td>$ref</td>";
258 :     $result .= "</tr>";
259 : sh002i 422 }
260 : sh002i 424 $result .= "</table>"
261 : sh002i 422 } else {
262 : sh002i 424 $result .= defined $ref ? $ref : '<font color="red">undef</font>';
263 :     }
264 : sh002i 422 }
265 :    
266 : sh002i 424 sub refBaseType($) {
267 :     my $ref = shift;
268 : sh002i 984 $ref =~ m/(\w+)\(/; # this might not be robust...
269 :     return $1;
270 : sh002i 422 }
271 :    
272 : sh002i 1111 # p. 101, Camel, 3rd ed.
273 :     # The <=> and cmp operators return -1 if the left operand is less than the
274 :     # right operand, 0 if they are equal, and +1 if the left operand is greater
275 :     # than the right operand.
276 :    
277 :     sub sortByName {
278 :     my ($field, @items) = @_;
279 :     return sort {
280 :     my @aParts = split m/(?<=\D)(?=\d)|(?<=\d)(?=\D)/, $a->$field;
281 :     my @bParts = split m/(?<=\D)(?=\d)|(?<=\d)(?=\D)/, $b->$field;
282 :     while (@aParts and @bParts) {
283 :     my $aPart = shift @aParts;
284 :     my $bPart = shift @bParts;
285 :     my $aNumeric = $aPart =~ m/^\d*$/;
286 :     my $bNumeric = $bPart =~ m/^\d*$/;
287 :    
288 :     # numbers should come before words
289 :     return -1 if $aNumeric and not $bNumeric;
290 :     return +1 if not $aNumeric and $bNumeric;
291 :    
292 :     # both have the same type
293 :     if ($aNumeric and $bNumeric) {
294 :     next if $aPart == $bPart; # check next pair
295 :     return $aPart <=> $bPart; # compare numerically
296 :     } else {
297 :     next if $aPart eq $bPart; # check next pair
298 :     return $aPart cmp $bPart; # compare lexicographically
299 :     }
300 :     }
301 :     return +1 if @aParts; # a has more sections, should go second
302 :     return -1 if @bParts; # a had fewer sections, should go first
303 :     } @items;
304 :     }
305 :    
306 : sh002i 1145 sub makeTempDirectory($$) {
307 :     my ($parent, $basename) = @_;
308 :     # Loop until we're able to create a directory, or it fails for some
309 :     # reason other than there already being something there.
310 :     my $triesRemaining = MKDIR_ATTEMPTS;
311 :     my ($fullPath, $success);
312 :     do {
313 :     my $suffix = join "", map { ('A'..'Z','a'..'z','0'..'9')[int rand 62] } 1 .. 8;
314 :     $fullPath = "$parent/$basename.$suffix";
315 :     $success = mkdir $fullPath;
316 :     } until ($success or not $!{EEXIST});
317 :     die "Failed to create directory $fullPath: $!"
318 :     unless $success;
319 :     return $fullPath;
320 :     }
321 :    
322 : sh002i 1150 sub removeTempDirectory($) {
323 :     my ($dir) = @_;
324 :     rmtree($dir, 0, 0);
325 :     }
326 :    
327 : gage 1137 sub pretty_print_rh {
328 :     my $rh = shift;
329 :     foreach my $key (sort keys %{$rh}) {
330 :     warn " $key => ",$rh->{$key},"\n";
331 :     }
332 :     }
333 : sh002i 1145
334 : malsyned 1287 sub cryptPassword {
335 :     my ($clearPassword) = @_;
336 :     my $salt = join("", ('.','/','0'..'9','A'..'Z','a'..'z')[rand 64, rand 64]);
337 :     my $cryptPassword = crypt($clearPassword, $salt);
338 :     return $cryptPassword;
339 :     }
340 :    
341 : sh002i 422 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9