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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2858 - (download) (as text) (annotate)
Mon Oct 4 18:28:16 2004 UTC (8 years, 7 months ago) by sh002i
File size: 22155 byte(s)
complete rewrite of parseDateTime to close bug #693.

- using modified version of WW1's unformatDateAndTime() function for
actual parsing.
- intelligent treatment of two, three, and four digit years consistent
with Time::Local.
- attempt to interpret time zone specification using DateTime::TimeZone
and then Time::Zone. if neither succeeds, an expection is thrown.
- if timezone is not specified, $display_tz is actually used.

It never ceases to amaze me how tricky time zone handling is. It appears
that I have it right now, but please test.

    1 ################################################################################
    2 # WeBWorK Online Homework Delivery System
    3 # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/
    4 # $CVSHeader: webwork2/lib/WeBWorK/Utils.pm,v 1.54 2004/09/24 15:21:50 gage Exp $
    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 ################################################################################
   16 
   17 package WeBWorK::Utils;
   18 use base qw(Exporter);
   19 
   20 =head1 NAME
   21 
   22 WeBWorK::Utils - useful utilities used by other WeBWorK modules.
   23 
   24 =cut
   25 
   26 use strict;
   27 use warnings;
   28 #use Apache::DB;
   29 use DateTime;
   30 use Date::Parse;
   31 use Date::Format;
   32 use Time::Zone;
   33 #use Date::Manip;
   34 #use DateTime::Format::DateManip;
   35 use Errno;
   36 use File::Path qw(rmtree);
   37 use Carp;
   38 
   39 use constant MKDIR_ATTEMPTS => 10;
   40 
   41 # "standard" WeBWorK date/time format (for set definition files):
   42 #     %m/%d/%y at %I:%M%P
   43 # where:
   44 #     %m = month number, starting with 01
   45 #     %d = numeric day of the month, with leading zeros (eg 01..31)
   46 #     %Y = year (4 digits)
   47 #     %I = hour, 12 hour clock, leading 0's)
   48 #     %M = minute, leading 0's
   49 #     %P = am or pm (Yes %p and %P are backwards :)
   50 use constant DATE_FORMAT => "%m/%d/%Y at %I:%M%P %Z";
   51 
   52 our @EXPORT    = ();
   53 our @EXPORT_OK = qw(
   54   runtime_use
   55   readFile
   56   readDirectory
   57   listFilesRecursive
   58   surePathToFile
   59   makeTempDirectory
   60   removeTempDirectory
   61   formatDateTime
   62   parseDateTime
   63   textDateTime
   64   intDateTime
   65   writeLog
   66   writeCourseLog
   67   writeTimingLogEntry
   68   list2hash
   69   ref2string
   70   decodeAnswers
   71   encodeAnswers
   72   max
   73   pretty_print_rh
   74   cryptPassword
   75   dequote
   76   undefstr
   77   sortByName
   78 );
   79 
   80 ################################################################################
   81 # Lowlevel thingies
   82 ################################################################################
   83 
   84 sub runtime_use($) {
   85   croak "runtime_use: no module specified" unless $_[0];
   86   eval "package Main; require $_[0]; import $_[0]";
   87   die $@ if $@;
   88 }
   89 
   90 #sub backtrace($) {
   91 # my ($style) = @_;
   92 # $style = "warn" unless $style;
   93 # my @bt = DB->backtrace;
   94 # shift @bt; # Remove "backtrace" from the backtrace;
   95 # if ($style eq "die") {
   96 #   die join "\n", @bt;
   97 # } elsif ($style eq "warn") {
   98 #   warn join "\n", @bt;
   99 # } elsif ($style eq "print") {
  100 #   print join "\n", @bt;
  101 # } elsif ($style eq "return") {
  102 #   return @bt;
  103 # }
  104 #}
  105 
  106 ################################################################################
  107 # Filesystem interaction
  108 ################################################################################
  109 
  110 # Convert Windows and Mac (classic) line endings to UNIX line endings in a string.
  111 # Windows uses CRLF, Mac uses CR, UNIX uses LF. (CR is ASCII 15, LF if ASCII 12)
  112 sub force_eoln($) {
  113   my ($string) = @_;
  114   $string =~ s/\015\012?/\012/g;
  115   return $string;
  116 }
  117 
  118 sub readFile($) {
  119   my $fileName = shift;
  120   local $/ = undef; # slurp the whole thing into one string
  121   open my $dh, "<", $fileName
  122     or die "failed to read file $fileName: $!";
  123   my $result = <$dh>;
  124   close $dh;
  125   return force_eoln($result);
  126 }
  127 
  128 sub readDirectory($) {
  129   my $dirName = shift;
  130   opendir my $dh, $dirName
  131     or die "Failed to read directory $dirName: $!";
  132   my @result = readdir $dh;
  133   close $dh;
  134   return @result;
  135 }
  136 
  137 =item @matches = listFilesRecusive($dir, $match_qr, $prune_qr, $match_full, $prune_full)
  138 
  139 Traverses the directory tree rooted at $dir, returning a list of files, named
  140 pipes, and sockets matching the regular expression $match_qr. Directories
  141 matching the regular expression $prune_qr are not visited.
  142 
  143 $match_full and $prune_full are boolean values that indicate whether $match_qr
  144 and $prune_qr, respectively, should be applied to the bare directory entry
  145 (false) or to the path to the directory entry relative to $dir.
  146 
  147 @matches is a list of paths relative to $dir.
  148 
  149 =cut
  150 
  151 sub listFilesRecursiveHelper($$$$$$);
  152 sub listFilesRecursive($;$$$$) {
  153   my ($dir, $match_qr, $prune_qr, $match_full, $prune_full) = @_;
  154   return listFilesRecursiveHelper($dir, "", $match_qr, $prune_qr, $match_full, $prune_full);
  155 }
  156 
  157 sub listFilesRecursiveHelper($$$$$$) {
  158   my ($base_dir, $curr_dir, $match_qr, $prune_qr, $match_full, $prune_full) = @_;
  159 
  160   my $full_dir = "$base_dir/$curr_dir";
  161 
  162   my @dir_contents = readDirectory($full_dir);
  163 
  164   my @matches;
  165 
  166   foreach my $dir_entry (@dir_contents) {
  167     my $full_path = "$full_dir/$dir_entry";
  168     if (-d $full_path or -l $full_path) {
  169       # standard things to skip
  170       next if $dir_entry eq ".";
  171       next if $dir_entry eq "..";
  172 
  173       # skip unreadable directories (and broken symlinks, incidentally)
  174       unless (-r $full_path) {
  175         warn "Directory/symlink $full_path not readable";
  176         next;
  177       }
  178 
  179       # check $prune_qr
  180       my $subdir = ($curr_dir eq "") ? $dir_entry : "$curr_dir/$dir_entry";
  181       if (defined $prune_qr) {
  182         my $prune_string = $prune_full ? $subdir : $dir_entry;
  183         next if $prune_string =~ m/$prune_qr/;
  184       }
  185 
  186       # everything looks good, time to recurse!
  187       push @matches, listFilesRecursiveHelper($base_dir, $subdir, $match_qr, $prune_qr, $match_full, $prune_full);
  188     } elsif (-f $full_path or -p $full_path or -S $full_path) {
  189       my $file = ($curr_dir eq "") ? $dir_entry : "$curr_dir/$dir_entry";
  190       my $match_string = $match_full ? $file : $dir_entry;
  191       if (not defined $match_string or $match_string =~ m/$match_qr/) {
  192         push @matches, $file;
  193       }
  194     }
  195   }
  196 
  197   return @matches;
  198 }
  199 
  200 # A very useful macro for making sure that all of the directories to a file have
  201 # been constructed.
  202 sub surePathToFile($$) {
  203   # constructs intermediate
  204   # the input path must be the path relative to this starting directory
  205   my $start_directory = shift;
  206   my $path = shift;
  207   my $delim = "/"; #&getDirDelim();
  208   unless ($start_directory and $path ) {
  209     warn "missing directory<br> surePathToFile  start_directory   path ";
  210     return '';
  211   }
  212   # use the permissions/group on the start directory itself as a template
  213   my ($perms, $groupID) = (stat $start_directory)[2,5];
  214   #warn "&urePathToTmpFile: perms=$perms groupID=$groupID\n";
  215 
  216   # if the path starts with $start_directory (which is permitted but optional) remove this initial segment
  217   $path =~ s|^$start_directory|| if $path =~ m|^$start_directory|;
  218   #$path = convertPath($path);
  219 
  220 
  221   # find the nodes on the given path
  222         my @nodes = split("$delim",$path);
  223 
  224   # create new path
  225   $path = $start_directory; #convertPath("$tmpDirectory");
  226 
  227   while (@nodes>1) {
  228     $path = $path . shift (@nodes) . "/"; #convertPath($path . shift (@nodes) . "/");
  229     #FIXME  this make directory command may not be fool proof.
  230     unless (-e $path) {
  231       mkdir($path, $perms)
  232         or warn "Failed to create directory $path";
  233     }
  234 
  235   }
  236 
  237   $path = $path . shift(@nodes); #convertPath($path . shift(@nodes));
  238   return $path;
  239 }
  240 
  241 sub makeTempDirectory($$) {
  242   my ($parent, $basename) = @_;
  243   # Loop until we're able to create a directory, or it fails for some
  244   # reason other than there already being something there.
  245   my $triesRemaining = MKDIR_ATTEMPTS;
  246   my ($fullPath, $success);
  247   do {
  248     my $suffix = join "", map { ('A'..'Z','a'..'z','0'..'9')[int rand 62] } 1 .. 8;
  249     $fullPath = "$parent/$basename.$suffix";
  250     $success = mkdir $fullPath;
  251   } until ($success or not $!{EEXIST});
  252   die "Failed to create directory $fullPath: $!"
  253     unless $success;
  254   return $fullPath;
  255 }
  256 
  257 sub removeTempDirectory($) {
  258   my ($dir) = @_;
  259   rmtree($dir, 0, 0);
  260 }
  261 
  262 ################################################################################
  263 # Date/time processing
  264 ################################################################################
  265 
  266 =head2 Date/time processing
  267 
  268 =over
  269 
  270 =item $dateTime = parseDateTime($string, $display_tz)
  271 
  272 Parses $string as a datetime. If $display_tz is given, $string is assumed to be
  273 in that timezone. Otherwise, the server's timezone is used. The result,
  274 $dateTime, is an integer UNIX datetime (epoch) in the server's timezone.
  275 
  276 =cut
  277 
  278 # This is a modified version of the subroutine of the same name from WeBWorK
  279 # 1.9.05's scripts/FILE.pl (v1.13). It has been modified to understand time
  280 # zones. The time zone specification must appear at the end of the string and be
  281 # preceded by whitespace. The return value is a list consisting of the following
  282 # elements:
  283 #
  284 #     ($second, $minute, $hour, $day, $month, $year, $zone)
  285 #
  286 # $second, $minute, $hour, $day, and $month are zero-indexed. $year is the
  287 # number of years since 1900. $zone is a string (hopefully) representing the
  288 # time zone.
  289 #
  290 # Error handling has also been improved. Exceptions are now thrown for errors,
  291 # and more information is given abou the nature of errors.
  292 #
  293 sub unformatDateAndTime {
  294   my ($string) = @_;
  295   my $orgString =$string;
  296   $string =~ s|^\s+||;
  297   $string =~ s|\s+$||;
  298   $string =~ s|at| at |i; ## OK if forget to enter spaces or use wrong case
  299   $string =~ s|AM| AM|i;  ## OK if forget to enter spaces or use wrong case
  300   $string =~ s|PM| PM|i;  ## OK if forget to enter spaces or use wrong case
  301   $string =~ s|,| at |; ## start translating old form of date/time to new form
  302 
  303   my($date,$at,$time,$AMPM,$TZ) = split(/\s+/,$string);
  304   unless ($time =~ /:/) {
  305     {  ##bare block for 'case" structure
  306       $time =~ /(\d\d)(\d\d)/;
  307       my $tmp_hour = $1;
  308       my $tmp_min = $2;
  309       if ($tmp_hour eq '00') {$time = "12:$tmp_min"; $AMPM = 'AM';last;}
  310       if ($tmp_hour eq '12') {$time = "12:$tmp_min"; $AMPM = 'PM';last;}
  311       if ($tmp_hour < 12) {$time = "$tmp_hour:$tmp_min"; $AMPM = 'AM';last;}
  312       if ($tmp_hour < 24) {
  313         $tmp_hour = $tmp_hour - 12;
  314         $time = "$tmp_hour:$tmp_min";
  315         $AMPM = 'PM';
  316       }
  317     }  ##end of bare block for 'case" structure
  318 
  319   }
  320 
  321   my ($mday, $mon, $year, $wday, $yday,$sec, $pm, $min, $hour);
  322   $sec=0;
  323   $time =~ /^([0-9]+)\s*\:\s*([0-9]*)/;
  324   $min=$2;
  325   $hour = $1;
  326   if ($hour < 1 or $hour > 12) {
  327     die "Incorrect date/time format $orgString. Hour must be in the range [1,12]. Correct format is MM/DD/YYYY at HH:MM AMPM ZONE\n";
  328   }
  329   if ($min < 0 or $min > 59) {
  330     die "Incorrect date/time format $orgString. Minute must be in the range [0-59]. Correct format is MM/DD/YYYY at HH:MM AMPM ZONE\n";
  331   }
  332   $pm = 0;
  333   $pm = 12 if ($AMPM =~/PM/ and $hour < 12);
  334   $hour += $pm;
  335   $hour = 0 if ($AMPM =~/AM/ and $hour == 12);
  336   $date =~  m!([0-9]+)\s*/\s*([0-9]+)/\s*([0-9]+)! ;
  337   $mday =$2;
  338   $mon=($1-1);
  339   if ($mday < 1 or $mday > 31) {
  340     die "Incorrect date/time format $orgString. Day must be in the range [1,31]. Correct format is MM/DD/YY at HH:MM AMPM ZONE\n";
  341   }
  342   if ($mon < 0 or $mon > 11) {
  343     die "Incorrect date/time format $orgString. Month must be in the range [1,12]. Correct format is MM/DD/YY at HH:MM AMPM ZONE\n";
  344   }
  345   $year=$3;
  346   $wday="";
  347   $yday="";
  348   return ($sec, $min, $hour, $mday, $mon, $year, $TZ);
  349 }
  350 
  351 
  352 sub parseDateTime($;$) {
  353   my ($string, $display_tz) = @_;
  354   $display_tz ||= "local";
  355   #warn "parseDateTime('$string', '$display_tz')\n";
  356 
  357   # use WeBWorK 1 date parsing routine
  358   my ($second, $minute, $hour, $day, $month, $year, $zone) = unformatDateAndTime($string);
  359   my $zone_str = defined $zone ? $zone : "UNDEF";
  360   #warn "\tunformatDateAndTime: $second $minute $hour $day $month $year $zone_str\n";
  361 
  362   # DateTime expects month 1-12, not 0-11
  363   $month++;
  364 
  365   # Do what Time::Local does to ambiguous years
  366   {
  367     my $ThisYear     = (localtime())[5]; # FIXME: should be relative to $string's timezone
  368     my $Breakpoint   = ($ThisYear + 50) % 100;
  369     my $NextCentury  = $ThisYear - $ThisYear % 100;
  370        $NextCentury += 100 if $Breakpoint < 50;
  371     my $Century      = $NextCentury - 100;
  372     my $SecOff       = 0;
  373 
  374     if ($year >= 1000) {
  375       # leave alone
  376     } elsif ($year < 100 and $year >= 0) {
  377       $year += ($year > $Breakpoint) ? $Century : $NextCentury;
  378       $year += 1900;
  379     } else {
  380       $year += 1900;
  381     }
  382   }
  383 
  384   my $epoch;
  385 
  386   if (defined $zone and $zone ne "") {
  387     if (DateTime::TimeZone->is_valid_name($zone)) {
  388       #warn "\t\$zone is valid according to DateTime::TimeZone\n";
  389 
  390       my $dt = new DateTime(
  391         year      => $year,
  392         month     => $month,
  393         day       => $day,
  394         hour      => $hour,
  395         minute    => $minute,
  396         second    => $second,
  397         time_zone => $zone,
  398       );
  399       #warn "\t\$dt = ", $dt->strftime(DATE_FORMAT), "\n";
  400 
  401       $epoch = $dt->epoch;
  402       #warn "\t\$dt->epoch = $epoch\n";
  403     } else {
  404       #warn "\t\$zone is invalid according to DateTime::TimeZone, so we ask Time::Zone\n";
  405 
  406       # treat the date/time as UTC
  407       my $dt = new DateTime(
  408         year      => $year,
  409         month     => $month,
  410         day       => $day,
  411         hour      => $hour,
  412         minute    => $minute,
  413         second    => $second,
  414         time_zone => "UTC",
  415       );
  416       #warn "\t\$dt = ", $dt->strftime(DATE_FORMAT), "\n";
  417 
  418       # convert to an epoch value
  419       my $utc_epoch = $dt->epoch
  420         or die "Date/time '$string' not representable as an epoch. Get more bits!\n";
  421       #warn "\t\$utc_epoch = $utc_epoch\n";
  422 
  423       # get offset for supplied timezone and utc_epoch
  424       my $offset = tz_offset($zone, $utc_epoch) or die "Time zone '$zone' not recognized.\n";
  425       #warn "\t\$zone is valid according to Time::Zone (\$offset = $offset)\n";
  426 
  427       #$epoch = $utc_epoch + $offset;
  428       ##warn "\t\$epoch = \$utc_epoch + \$offset = $epoch\n";
  429 
  430       $dt->subtract(seconds => $offset);
  431       #warn "\t\$dt - \$offset = ", $dt->strftime(DATE_FORMAT), "\n";
  432 
  433       $epoch = $dt->epoch;
  434       #warn "\t\$epoch = $epoch\n";
  435     }
  436   } else {
  437     #warn "\t\$zone not supplied, using \$display_tz\n";
  438 
  439     my $dt = new DateTime(
  440       year      => $year,
  441       month     => $month,
  442       day       => $day,
  443       hour      => $hour,
  444       minute    => $minute,
  445       second    => $second,
  446       time_zone => $display_tz,
  447     );
  448     #warn "\t\$dt = ", $dt->strftime(DATE_FORMAT), "\n";
  449 
  450     $epoch = $dt->epoch;
  451     #warn "\t\$epoch = $epoch\n";
  452   }
  453 
  454   return $epoch;
  455 }
  456 
  457 =item $string = formatDateTime($dateTime, $display_tz)
  458 
  459 Formats the UNIX datetime $dateTime in the standard WeBWorK datetime format.
  460 $dateTime is assumed to be in the server's time zone. If $display_tz is given,
  461 the datetime is converted from the server's timezone to the timezone specified.
  462 
  463 =cut
  464 
  465 sub formatDateTime($;$) {
  466   my ($dateTime, $display_tz) = @_;
  467   $display_tz ||= "local";
  468   #warn "formatDateTime('$dateTime', '$display_tz')\n";
  469 
  470   my $dt = DateTime->from_epoch(epoch => $dateTime, time_zone => $display_tz);
  471   #warn "\t\$dt = ", $dt->strftime(DATE_FORMAT), "\n";
  472   return $dt->strftime(DATE_FORMAT);
  473 }
  474 
  475 =item $string = textDateTime($string_or_dateTime)
  476 
  477 Accepts a UNIX datetime or a formatted string, returns a formatted string.
  478 
  479 =cut
  480 
  481 sub textDateTime($) {
  482   return ($_[0] =~ m/^\d*$/) ? formatDateTime($_[0]) : $_[0];
  483 }
  484 
  485 =item $dateTIme = intDateTime($string_or_dateTime)
  486 
  487 Accepts a UNIX datetime or a formatted string, returns a UNIX datetime.
  488 
  489 =cut
  490 
  491 sub intDateTime($) {
  492   return ($_[0] =~ m/^\d*$/) ?  $_[0] : parseDateTime($_[0]);
  493 }
  494 
  495 =back
  496 
  497 =cut
  498 
  499 ################################################################################
  500 # Logging
  501 ################################################################################
  502 
  503 sub writeLog($$@) {
  504   my ($ce, $facility, @message) = @_;
  505   unless ($ce->{webworkFiles}->{logs}->{$facility}) {
  506     warn "There is no log file for the $facility facility defined.\n";
  507     return;
  508   }
  509   my $logFile = $ce->{webworkFiles}->{logs}->{$facility};
  510   local *LOG;
  511   if (open LOG, ">>", $logFile) {
  512     print LOG "[", time2str("%a %b %d %H:%M:%S %Y", time), "] @message\n";
  513     close LOG;
  514   } else {
  515     warn "failed to open $logFile for writing: $!";
  516   }
  517 }
  518 
  519 sub writeCourseLog($$@) {
  520   my ($ce, $facility, @message) = @_;
  521   unless ($ce->{courseFiles}->{logs}->{$facility}) {
  522     warn "There is no course log file for the $facility facility defined.\n";
  523     return;
  524   }
  525   my $logFile = $ce->{courseFiles}->{logs}->{$facility};
  526   local *LOG;
  527   if (open LOG, ">>", $logFile) {
  528     print LOG "[", time2str("%a %b %d %H:%M:%S %Y", time), "] @message\n";
  529     close LOG;
  530   } else {
  531     warn "failed to open $logFile for writing: $!";
  532   }
  533 }
  534 
  535 # $ce - a WeBWork::CourseEnvironment object
  536 # $function - fully qualified function name
  537 # $details - any information, do not use the characters '[' or ']'
  538 # $beginEnd - the string "begin", "intermediate", or "end"
  539 # use the intermediate step begun or completed for INTERMEDIATE
  540 # use an empty string for $details when calling for END
  541 sub writeTimingLogEntry($$$$) {
  542   my ($ce, $function, $details, $beginEnd) = @_;
  543   return unless defined $ce->{webworkFiles}->{logs}->{timing};
  544   $beginEnd = ($beginEnd eq "begin") ? ">" : ($beginEnd eq "end") ? "<" : "-";
  545   writeLog($ce, "timing", "$$ ".time." $beginEnd $function [$details]");
  546 }
  547 
  548 ################################################################################
  549 # Data munging
  550 ################################################################################
  551 
  552 sub list2hash(@) {
  553   map {$_ => "0"} @_;
  554 }
  555 
  556 sub refBaseType($) {
  557   my $ref = shift;
  558   $ref =~ m/(\w+)\(/; # this might not be robust...
  559   return $1;
  560 }
  561 
  562 sub ref2string($;$);
  563 sub ref2string($;$) {
  564   my $ref = shift;
  565   my $dontExpand = shift || {};
  566   my $refType = ref $ref;
  567   my $result;
  568   if ($refType and not $dontExpand->{$refType}) {
  569     my $baseType = refBaseType($ref);
  570     $result .= '<font size="1" color="grey">' . $refType;
  571     $result .= " ($baseType)" if $baseType and $refType ne $baseType;
  572     $result .= ":</font><br>";
  573     $result .= '<table border="1" cellpadding="2">';
  574     if ($baseType eq "HASH") {
  575       my %hash = %$ref;
  576       foreach (sort keys %hash) {
  577         $result .= '<tr valign="top">';
  578         $result .= "<td>$_</td>";
  579         $result .= "<td>" . ref2string($hash{$_}, $dontExpand) . "</td>";
  580         $result .= "</tr>";
  581       }
  582     } elsif ($baseType eq "ARRAY") {
  583       my @array = @$ref;
  584       # special case for Problem, Set, and User objects, which are defined
  585       # using lists and contain a @FIELDS package variable:
  586       no strict 'refs';
  587       my @FIELDS = eval { @{$refType."::FIELDS"} };
  588       use strict 'refs';
  589       undef @FIELDS unless scalar @FIELDS == scalar @array and not $@;
  590       foreach (0 .. $#array) {
  591         $result .= '<tr valign="top">';
  592         $result .= "<td>$_</td>";
  593         $result .= "<td>".$FIELDS[$_]."</td>" if @FIELDS;
  594         $result .= "<td>" . ref2string($array[$_], $dontExpand) . "</td>";
  595         $result .= "</tr>";
  596       }
  597     } elsif ($baseType eq "SCALAR") {
  598       my $scalar = $$ref;
  599       $result .= '<tr valign="top">';
  600       $result .= "<td>$scalar</td>";
  601       $result .= "</tr>";
  602     } else {
  603       # perhaps a coderef? in any case, i don't feel like dealing with it!
  604       $result .= '<tr valign="top">';
  605       $result .= "<td>$ref</td>";
  606       $result .= "</tr>";
  607     }
  608     $result .= "</table>"
  609   } else {
  610     $result .= defined $ref ? $ref : '<font color="red">undef</font>';
  611   }
  612 }
  613 
  614 sub decodeAnswers($) {
  615   my $string = shift;
  616   return unless defined $string and $string;
  617   my @array = split m/##/, $string;
  618   $array[$_] =~ s/\\#\\/#/g foreach 0 .. $#array;
  619   push @array, "" if @array%2;
  620   return @array; # it's actually a hash ;)
  621 }
  622 
  623 sub encodeAnswers(\%\@) {
  624   my %hash = %{ shift() };
  625   my @order = @{ shift() };
  626   my $string = "";
  627   foreach my $name (@order) {
  628     my $value = defined $hash{$name} ? $hash{$name} : "";
  629     $name  =~ s/#/\\#\\/g; # this is a WEIRD way to escape things
  630     $value =~ s/#/\\#\\/g; # and it's not my fault!
  631     if ($value =~ m/\\$/) {
  632       # if the value ends with a backslash, string2hash will
  633       # interpret that as a normal escape sequence (not part
  634       # of the weird pound escape sequence) if the next
  635       # character is &. So we have to protect against this.
  636       # will adding a spcae at the end of the last answer
  637       # hurt anything? i don't think so...
  638       $value .= " ";
  639     }
  640     $string .= "$name##$value##"; # this is also not my fault
  641   }
  642   $string =~ s/##$//; # remove last pair of hashs
  643   return $string;
  644 }
  645 
  646 sub max(@) {
  647   my $soFar;
  648   foreach my $item (@_) {
  649     $soFar = $item unless defined $soFar;
  650     if ($item > $soFar) {
  651       $soFar = $item;
  652     }
  653   }
  654   return defined $soFar ? $soFar : 0;
  655 }
  656 
  657 sub pretty_print_rh($) {
  658   my $rh = shift;
  659   foreach my $key (sort keys %{$rh})  {
  660     warn "  $key => ",$rh->{$key},"\n";
  661   }
  662 }
  663 
  664 sub cryptPassword($) {
  665   my ($clearPassword) = @_;
  666   my $salt = join("", ('.','/','0'..'9','A'..'Z','a'..'z')[rand 64, rand 64]);
  667   my $cryptPassword = crypt($clearPassword, $salt);
  668   return $cryptPassword;
  669 }
  670 
  671 # from the Perl Cookbook, first edition, page 25:
  672 sub dequote($) {
  673   local $_ = shift;
  674   my ($white, $leader); # common whitespace and common leading string
  675   if (/^\s*(?:([^\w\s]+)(\s*).*\n)(?:\s*\1\2?.*\n)+$/) {
  676     ($white, $leader) = ($2, quotemeta($1));
  677   } else {
  678     ($white, $leader) = (/^(\s+)/, '');
  679   }
  680   s/^\s*?$leader(?:$white)?//gm;
  681   return $_;
  682 }
  683 
  684 sub undefstr($@) {
  685   map { defined $_ ? $_ : $_[0] } @_[1..$#_];
  686 }
  687 
  688 ################################################################################
  689 # Sorting
  690 ################################################################################
  691 
  692 # p. 101, Camel, 3rd ed.
  693 # The <=> and cmp operators return -1 if the left operand is less than the
  694 # right operand, 0 if they are equal, and +1 if the left operand is greater
  695 # than the right operand.
  696 sub sortByName($@) {
  697   my ($field, @items) = @_;
  698   return sort {
  699     my @aParts = split m/(?<=\D)(?=\d)|(?<=\d)(?=\D)/, defined $field ? $a->$field : $a;
  700     my @bParts = split m/(?<=\D)(?=\d)|(?<=\d)(?=\D)/, defined $field ? $b->$field : $b;
  701     while (@aParts and @bParts) {
  702       my $aPart = shift @aParts;
  703       my $bPart = shift @bParts;
  704       my $aNumeric = $aPart =~ m/^\d*$/;
  705       my $bNumeric = $bPart =~ m/^\d*$/;
  706 
  707       # numbers should come before words
  708       return -1 if     $aNumeric and not $bNumeric;
  709       return +1 if not $aNumeric and     $bNumeric;
  710 
  711       # both have the same type
  712       if ($aNumeric and $bNumeric) {
  713         next if $aPart == $bPart; # check next pair
  714         return $aPart <=> $bPart; # compare numerically
  715       } else {
  716         next if $aPart eq $bPart; # check next pair
  717         return $aPart cmp $bPart; # compare lexicographically
  718       }
  719     }
  720     return +1 if @aParts; # a has more sections, should go second
  721     return -1 if @bParts; # a had fewer sections, should go first
  722   } @items;
  723 }
  724 
  725 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9