Parent Directory
|
Revision Log
Revision 4674 - (view) (download) (as text)
| 1 : | sh002i | 440 | ################################################################################ |
| 2 : | sh002i | 1663 | # WeBWorK Online Homework Delivery System |
| 3 : | sh002i | 3973 | # Copyright © 2000-2006 The WeBWorK Project, http://openwebwork.sf.net/ |
| 4 : | sh002i | 4674 | # $CVSHeader: webwork2/lib/WeBWorK/PG.pm,v 1.68 2006/08/24 21:16:36 sh002i Exp $ |
| 5 : | sh002i | 1663 | # |
| 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 | 424 | package WeBWorK::PG; |
| 18 : | sh002i | 414 | |
| 19 : | sh002i | 455 | =head1 NAME |
| 20 : | |||
| 21 : | sh002i | 1244 | WeBWorK::PG - Invoke one of several PG rendering methods using an easy-to-use |
| 22 : | API. | ||
| 23 : | sh002i | 455 | |
| 24 : | =cut | ||
| 25 : | |||
| 26 : | sh002i | 414 | use strict; |
| 27 : | use warnings; | ||
| 28 : | sh002i | 1557 | use WeBWorK::PG::ImageGenerator; |
| 29 : | use WeBWorK::Utils qw(runtime_use formatDateTime makeTempDirectory); | ||
| 30 : | sh002i | 4397 | use WeBWorK::Utils::RestrictedClosureClass; |
| 31 : | sh002i | 414 | |
| 32 : | sh002i | 2192 | use constant DISPLAY_MODES => { |
| 33 : | # display name # mode name | ||
| 34 : | tex => "TeX", | ||
| 35 : | plainText => "HTML", | ||
| 36 : | formattedText => "HTML_tth", | ||
| 37 : | images => "HTML_dpng", | ||
| 38 : | jsMath => "HTML_jsMath", | ||
| 39 : | dpvc | 2200 | asciimath => "HTML_asciimath", |
| 40 : | dpvc | 4386 | LaTeXMathML => "HTML_LaTeXMathML", |
| 41 : | sh002i | 2192 | }; |
| 42 : | |||
| 43 : | sh002i | 1196 | sub new { |
| 44 : | sh002i | 1244 | shift; # throw away invocant -- we don't need it |
| 45 : | my ($ce, $user, $key, $set, $problem, $psvn, $formFields, | ||
| 46 : | $translationOptions) = @_; | ||
| 47 : | sh002i | 415 | |
| 48 : | sh002i | 1244 | my $renderer = $ce->{pg}->{renderer}; |
| 49 : | sh002i | 562 | |
| 50 : | sh002i | 1244 | runtime_use $renderer; |
| 51 : | sh002i | 414 | |
| 52 : | sh002i | 1244 | return $renderer->new(@_); |
| 53 : | sh002i | 414 | } |
| 54 : | |||
| 55 : | sh002i | 1557 | sub defineProblemEnvir { |
| 56 : | my ( | ||
| 57 : | $self, | ||
| 58 : | $ce, | ||
| 59 : | $user, | ||
| 60 : | $key, | ||
| 61 : | $set, | ||
| 62 : | $problem, | ||
| 63 : | $psvn, | ||
| 64 : | $formFields, | ||
| 65 : | $options, | ||
| 66 : | sh002i | 4397 | $extras, |
| 67 : | sh002i | 1557 | ) = @_; |
| 68 : | |||
| 69 : | my %envir; | ||
| 70 : | |||
| 71 : | # ---------------------------------------------------------------------- | ||
| 72 : | |||
| 73 : | # PG environment variables | ||
| 74 : | # from docs/pglanguage/pgreference/environmentvariables as of 06/25/2002 | ||
| 75 : | # any changes are noted by "ADDED:" or "REMOVED:" | ||
| 76 : | |||
| 77 : | # Vital state information | ||
| 78 : | sh002i | 2192 | # ADDED: displayModeFailover, displayHintsQ, displaySolutionsQ, |
| 79 : | # refreshMath2img, texDisposition | ||
| 80 : | sh002i | 1557 | |
| 81 : | sh002i | 2192 | $envir{psvn} = $set->psvn; |
| 82 : | $envir{psvnNumber} = $envir{psvn}; | ||
| 83 : | $envir{probNum} = $problem->problem_id; | ||
| 84 : | $envir{questionNumber} = $envir{probNum}; | ||
| 85 : | $envir{fileName} = $problem->source_file; | ||
| 86 : | $envir{probFileName} = $envir{fileName}; | ||
| 87 : | $envir{problemSeed} = $problem->problem_seed; | ||
| 88 : | $envir{displayMode} = translateDisplayModeNames($options->{displayMode}); | ||
| 89 : | $envir{languageMode} = $envir{displayMode}; | ||
| 90 : | $envir{outputMode} = $envir{displayMode}; | ||
| 91 : | $envir{displayHintsQ} = $options->{showHints}; | ||
| 92 : | $envir{displaySolutionsQ} = $options->{showSolutions}; | ||
| 93 : | $envir{texDisposition} = "pdf"; # in webwork2, we use pdflatex | ||
| 94 : | sh002i | 1557 | |
| 95 : | # Problem Information | ||
| 96 : | # ADDED: courseName, formatedDueDate | ||
| 97 : | |||
| 98 : | $envir{openDate} = $set->open_date; | ||
| 99 : | sh002i | 2776 | $envir{formattedOpenDate} = formatDateTime($envir{openDate}, $ce->{siteDefaults}{timezone}); |
| 100 : | sh002i | 1557 | $envir{dueDate} = $set->due_date; |
| 101 : | sh002i | 2776 | $envir{formattedDueDate} = formatDateTime($envir{dueDate}, $ce->{siteDefaults}{timezone}); |
| 102 : | sh002i | 1557 | $envir{formatedDueDate} = $envir{formattedDueDate}; # typo in many header files |
| 103 : | $envir{answerDate} = $set->answer_date; | ||
| 104 : | sh002i | 2776 | $envir{formattedAnswerDate} = formatDateTime($envir{answerDate}, $ce->{siteDefaults}{timezone}); |
| 105 : | sh002i | 1557 | $envir{numOfAttempts} = ($problem->num_correct || 0) + ($problem->num_incorrect || 0); |
| 106 : | $envir{problemValue} = $problem->value; | ||
| 107 : | $envir{sessionKey} = $key; | ||
| 108 : | $envir{courseName} = $ce->{courseName}; | ||
| 109 : | |||
| 110 : | # Student Information | ||
| 111 : | # ADDED: studentID | ||
| 112 : | |||
| 113 : | $envir{sectionName} = $user->section; | ||
| 114 : | $envir{sectionNumber} = $envir{sectionName}; | ||
| 115 : | $envir{recitationName} = $user->recitation; | ||
| 116 : | $envir{recitationNumber} = $envir{recitationName}; | ||
| 117 : | $envir{setNumber} = $set->set_id; | ||
| 118 : | $envir{studentLogin} = $user->user_id; | ||
| 119 : | $envir{studentName} = $user->first_name . " " . $user->last_name; | ||
| 120 : | $envir{studentID} = $user->student_id; | ||
| 121 : | |||
| 122 : | # Answer Information | ||
| 123 : | # REMOVED: refSubmittedAnswers | ||
| 124 : | |||
| 125 : | $envir{inputs_ref} = $formFields; | ||
| 126 : | |||
| 127 : | # External Programs | ||
| 128 : | # ADDED: externalLaTeXPath, externalDvipngPath, | ||
| 129 : | # externalGif2EpsPath, externalPng2EpsPath | ||
| 130 : | |||
| 131 : | $envir{externalTTHPath} = $ce->{externalPrograms}->{tth}; | ||
| 132 : | $envir{externalLaTeXPath} = $ce->{externalPrograms}->{latex}; | ||
| 133 : | $envir{externalDvipngPath} = $ce->{externalPrograms}->{dvipng}; | ||
| 134 : | $envir{externalGif2EpsPath} = $ce->{externalPrograms}->{gif2eps}; | ||
| 135 : | $envir{externalPng2EpsPath} = $ce->{externalPrograms}->{png2eps}; | ||
| 136 : | $envir{externalGif2PngPath} = $ce->{externalPrograms}->{gif2png}; | ||
| 137 : | gage | 4099 | $envir{externalCheckUrl} = $ce->{externalPrograms}->{checkurl}; |
| 138 : | sh002i | 1557 | # Directories and URLs |
| 139 : | # REMOVED: courseName | ||
| 140 : | # ADDED: dvipngTempDir | ||
| 141 : | dpvc | 2166 | # ADDED: jsMathURL |
| 142 : | dpvc | 2200 | # ADDED: asciimathURL |
| 143 : | sh002i | 3355 | # ADDED: macrosPath |
| 144 : | # REMOVED: macrosDirectory, courseScriptsDirectory | ||
| 145 : | dpvc | 4386 | # ADDED: LaTeXMathML |
| 146 : | sh002i | 1557 | |
| 147 : | $envir{cgiDirectory} = undef; | ||
| 148 : | $envir{cgiURL} = undef; | ||
| 149 : | $envir{classDirectory} = undef; | ||
| 150 : | gage | 4091 | $envir{macrosPath} = $ce->{pg}->{directories}{macrosPath}; |
| 151 : | gage | 4099 | $envir{appletPath} = $ce->{pg}->{directories}{appletPath}; |
| 152 : | $envir{pgDirectories} = $ce->{pg}->{directories}; | ||
| 153 : | sh002i | 1557 | $envir{htmlDirectory} = $ce->{courseDirs}->{html}."/"; |
| 154 : | $envir{htmlURL} = $ce->{courseURLs}->{html}."/"; | ||
| 155 : | $envir{templateDirectory} = $ce->{courseDirs}->{templates}."/"; | ||
| 156 : | $envir{tempDirectory} = $ce->{courseDirs}->{html_temp}."/"; | ||
| 157 : | $envir{tempURL} = $ce->{courseURLs}->{html_temp}."/"; | ||
| 158 : | $envir{scriptDirectory} = undef; | ||
| 159 : | $envir{webworkDocsURL} = $ce->{webworkURLs}->{docs}."/"; | ||
| 160 : | jj | 2170 | $envir{localHelpURL} = $ce->{webworkURLs}->{local_help}."/"; |
| 161 : | dpvc | 3224 | $envir{jsMathURL} = $ce->{webworkURLs}->{jsMath}; |
| 162 : | dpvc | 2200 | $envir{asciimathURL} = $ce->{webworkURLs}->{asciimath}; |
| 163 : | dpvc | 4386 | $envir{LaTeXMathMLURL} = $ce->{webworkURLs}->{LaTeXMathML}; |
| 164 : | sh002i | 4376 | $envir{server_root_url} = $ce->{apache_root_url}; |
| 165 : | sh002i | 1557 | |
| 166 : | # Information for sending mail | ||
| 167 : | |||
| 168 : | $envir{mailSmtpServer} = $ce->{mail}->{smtpServer}; | ||
| 169 : | $envir{mailSmtpSender} = $ce->{mail}->{smtpSender}; | ||
| 170 : | $envir{ALLOW_MAIL_TO} = $ce->{mail}->{allowedRecipients}; | ||
| 171 : | |||
| 172 : | # Default values for evaluating answers | ||
| 173 : | |||
| 174 : | my $ansEvalDefaults = $ce->{pg}->{ansEvalDefaults}; | ||
| 175 : | $envir{$_} = $ansEvalDefaults->{$_} foreach (keys %$ansEvalDefaults); | ||
| 176 : | |||
| 177 : | # ---------------------------------------------------------------------- | ||
| 178 : | |||
| 179 : | sh002i | 4397 | # ADDED: ImageGenerator for images mode |
| 180 : | if (defined $extras->{image_generator}) { | ||
| 181 : | # only allow access to the add() method | ||
| 182 : | $envir{imagegen} = new WeBWorK::Utils::RestrictedClosureClass($extras->{image_generator}, "add"); | ||
| 183 : | } | ||
| 184 : | sh002i | 2440 | |
| 185 : | dpvc | 2983 | # ADDED: jsMath options |
| 186 : | $envir{jsMath} = {%{$ce->{pg}{displayModeOptions}{jsMath}}}; | ||
| 187 : | sh002i | 1557 | |
| 188 : | # Other things... | ||
| 189 : | $envir{QUIZ_PREFIX} = $options->{QUIZ_PREFIX}; # used by quizzes | ||
| 190 : | $envir{PROBLEM_GRADER_TO_USE} = $ce->{pg}->{options}->{grader}; | ||
| 191 : | $envir{PRINT_FILE_NAMES_FOR} = $ce->{pg}->{specialPGEnvironmentVars}->{PRINT_FILE_NAMES_FOR}; | ||
| 192 : | dpvc | 2699 | |
| 193 : | # ADDED: __files__ | ||
| 194 : | # an array for mapping (eval nnn) to filenames in error messages | ||
| 195 : | $envir{__files__} = { | ||
| 196 : | root => $ce->{webworkDirs}{root}, # used to shorten filenames | ||
| 197 : | pg => $ce->{pg}{directories}{root}, # ditto | ||
| 198 : | tmpl => $ce->{courseDirs}{templates}, # ditto | ||
| 199 : | }; | ||
| 200 : | sh002i | 1557 | |
| 201 : | jj | 2217 | # variables for interpreting capa problems and other things to be |
| 202 : | # seen in a pg file | ||
| 203 : | my $specialPGEnvironmentVarHash = $ce->{pg}->{specialPGEnvironmentVars}; | ||
| 204 : | for my $SPGEV (keys %{$specialPGEnvironmentVarHash}) { | ||
| 205 : | $envir{$SPGEV} = $specialPGEnvironmentVarHash->{$SPGEV}; | ||
| 206 : | } | ||
| 207 : | sh002i | 1557 | |
| 208 : | return \%envir; | ||
| 209 : | } | ||
| 210 : | |||
| 211 : | sub translateDisplayModeNames($) { | ||
| 212 : | my $name = shift; | ||
| 213 : | sh002i | 2192 | return DISPLAY_MODES()->{$name}; |
| 214 : | sh002i | 1557 | } |
| 215 : | |||
| 216 : | sub oldSafetyFilter { | ||
| 217 : | my $answer = shift; # accepts one answer and checks it | ||
| 218 : | my $submittedAnswer = $answer; | ||
| 219 : | $answer = '' unless defined $answer; | ||
| 220 : | my ($errorno); | ||
| 221 : | $answer =~ tr/\000-\037/ /; | ||
| 222 : | # Return if answer field is empty | ||
| 223 : | unless ($answer =~ /\S/) { | ||
| 224 : | #$errorno = "<BR>No answer was submitted."; | ||
| 225 : | $errorno = 0; ## don't report blank answer as error | ||
| 226 : | return ($answer,$errorno); | ||
| 227 : | } | ||
| 228 : | # replace ^ with ** (for exponentiation) | ||
| 229 : | # $answer =~ s/\^/**/g; | ||
| 230 : | # Return if forbidden characters are found | ||
| 231 : | unless ($answer =~ /^[a-zA-Z0-9_\-\+ \t\/@%\*\.\n^\[\]\(\)\,\|]+$/ ) { | ||
| 232 : | $answer =~ tr/a-zA-Z0-9_\-\+ \t\/@%\*\.\n^\(\)/#/c; | ||
| 233 : | $errorno = "<BR>There are forbidden characters in your answer: $submittedAnswer<BR>"; | ||
| 234 : | return ($answer,$errorno); | ||
| 235 : | } | ||
| 236 : | $errorno = 0; | ||
| 237 : | return($answer, $errorno); | ||
| 238 : | } | ||
| 239 : | |||
| 240 : | sub nullSafetyFilter { | ||
| 241 : | return shift, 0; # no errors | ||
| 242 : | } | ||
| 243 : | |||
| 244 : | sh002i | 414 | 1; |
| 245 : | sh002i | 440 | |
| 246 : | __END__ | ||
| 247 : | |||
| 248 : | =head1 SYNOPSIS | ||
| 249 : | |||
| 250 : | $pg = WeBWorK::PG->new( | ||
| 251 : | sh002i | 1239 | $ce, # a WeBWorK::CourseEnvironment object |
| 252 : | sh002i | 818 | $user, # a WeBWorK::DB::Record::User object |
| 253 : | sh002i | 440 | $sessionKey, |
| 254 : | sh002i | 818 | $set, # a WeBWorK::DB::Record::UserSet object |
| 255 : | $problem, # a WeBWorK::DB::Record::UserProblem object | ||
| 256 : | sh002i | 502 | $psvn, |
| 257 : | $formFields # in &WeBWorK::Form::Vars format | ||
| 258 : | sh002i | 440 | { # translation options |
| 259 : | displayMode => "images", # (plainText|formattedText|images) | ||
| 260 : | showHints => 1, # (0|1) | ||
| 261 : | showSolutions => 0, # (0|1) | ||
| 262 : | refreshMath2img => 0, # (0|1) | ||
| 263 : | processAnswers => 1, # (0|1) | ||
| 264 : | }, | ||
| 265 : | ); | ||
| 266 : | |||
| 267 : | $translator = $pg->{translator}; # WeBWorK::PG::Translator | ||
| 268 : | $body = $pg->{body_text}; # text string | ||
| 269 : | $header = $pg->{head_text}; # text string | ||
| 270 : | $answerHash = $pg->{answers}; # WeBWorK::PG::AnswerHash | ||
| 271 : | $result = $pg->{result}; # hash reference | ||
| 272 : | $state = $pg->{state}; # hash reference | ||
| 273 : | $errors = $pg->{errors}; # text string | ||
| 274 : | $warnings = $pg->{warnings}; # text string | ||
| 275 : | $flags = $pg->{flags}; # hash reference | ||
| 276 : | |||
| 277 : | =head1 DESCRIPTION | ||
| 278 : | |||
| 279 : | sh002i | 1244 | WeBWorK::PG is a factory for modules which use the WeBWorK::PG API. Notable |
| 280 : | modules which use this API (and exist) are WeBWorK::PG::Local and | ||
| 281 : | WeBWorK::PG::Remote. The course environment key $pg{renderer} is consulted to | ||
| 282 : | determine which render to use. | ||
| 283 : | sh002i | 440 | |
| 284 : | sh002i | 1244 | =head1 THE WEBWORK::PG API |
| 285 : | sh002i | 440 | |
| 286 : | sh002i | 1244 | Modules which support this API must implement the following method: |
| 287 : | |||
| 288 : | sh002i | 440 | =over |
| 289 : | |||
| 290 : | sh002i | 1557 | =item new ENVIRONMENT, USER, KEY, SET, PROBLEM, PSVN, FIELDS, OPTIONS |
| 291 : | sh002i | 440 | |
| 292 : | The C<new> method creates a translator, initializes it using the parameters | ||
| 293 : | specified, translates a PG file, and processes answers. It returns a reference | ||
| 294 : | to a blessed hash containing the results of the translation process. | ||
| 295 : | |||
| 296 : | =back | ||
| 297 : | |||
| 298 : | =head2 Parameters | ||
| 299 : | |||
| 300 : | =over | ||
| 301 : | |||
| 302 : | =item ENVIRONMENT | ||
| 303 : | |||
| 304 : | a WeBWorK::CourseEnvironment object | ||
| 305 : | |||
| 306 : | =item USER | ||
| 307 : | |||
| 308 : | sh002i | 502 | a WeBWorK::User object |
| 309 : | sh002i | 440 | |
| 310 : | =item KEY | ||
| 311 : | |||
| 312 : | the session key of the current session | ||
| 313 : | |||
| 314 : | =item SET | ||
| 315 : | |||
| 316 : | sh002i | 502 | a WeBWorK::Set object |
| 317 : | sh002i | 440 | |
| 318 : | =item PROBLEM | ||
| 319 : | |||
| 320 : | sh002i | 818 | a WeBWorK::DB::Record::UserProblem object. The contents of the source_file |
| 321 : | field can specify a PG file either by absolute path or path relative to the | ||
| 322 : | "templates" directory. I<The caller should remove taint from this value before | ||
| 323 : | passing!> | ||
| 324 : | sh002i | 440 | |
| 325 : | sh002i | 502 | =item PSVN |
| 326 : | |||
| 327 : | the problem set version number | ||
| 328 : | |||
| 329 : | =item FIELDS | ||
| 330 : | |||
| 331 : | a reference to a hash (as returned by &WeBWorK::Form::Vars) containing form | ||
| 332 : | fields submitted by a problem processor. The translator will look for fields | ||
| 333 : | like "AnSwEr[0-9]" containing submitted student answers. | ||
| 334 : | |||
| 335 : | sh002i | 440 | =item OPTIONS |
| 336 : | |||
| 337 : | a reference to a hash containing the following data: | ||
| 338 : | |||
| 339 : | =over | ||
| 340 : | |||
| 341 : | =item displayMode | ||
| 342 : | |||
| 343 : | one of "plainText", "formattedText", or "images" | ||
| 344 : | |||
| 345 : | =item showHints | ||
| 346 : | |||
| 347 : | boolean, render hints | ||
| 348 : | |||
| 349 : | =item showSolutions | ||
| 350 : | |||
| 351 : | boolean, render solutions | ||
| 352 : | |||
| 353 : | =item refreshMath2img | ||
| 354 : | |||
| 355 : | boolean, force images created by math2img (in "images" mode) to be recreated, | ||
| 356 : | sh002i | 1244 | even if the PG source has not been updated. FIXME: remove this option. |
| 357 : | sh002i | 440 | |
| 358 : | =item processAnswers | ||
| 359 : | |||
| 360 : | boolean, call answer evaluators and graders | ||
| 361 : | |||
| 362 : | =back | ||
| 363 : | |||
| 364 : | =back | ||
| 365 : | |||
| 366 : | =head2 RETURN VALUE | ||
| 367 : | |||
| 368 : | The C<new> method returns a blessed hash reference containing the following | ||
| 369 : | fields. More information can be found in the documentation for | ||
| 370 : | WeBWorK::PG::Translator. | ||
| 371 : | |||
| 372 : | =over | ||
| 373 : | |||
| 374 : | =item translator | ||
| 375 : | |||
| 376 : | The WeBWorK::PG::Translator object used to render the problem. | ||
| 377 : | |||
| 378 : | =item head_text | ||
| 379 : | |||
| 380 : | HTML code for the E<lt>headE<gt> block of an resulting web page. Used for | ||
| 381 : | JavaScript features. | ||
| 382 : | |||
| 383 : | =item body_text | ||
| 384 : | |||
| 385 : | HTML code for the E<lt>bodyE<gt> block of an resulting web page. | ||
| 386 : | |||
| 387 : | =item answers | ||
| 388 : | |||
| 389 : | An C<AnswerHash> object containing submitted answers, and results of answer | ||
| 390 : | evaluation. | ||
| 391 : | |||
| 392 : | =item result | ||
| 393 : | |||
| 394 : | A hash containing the results of grading the problem. | ||
| 395 : | |||
| 396 : | =item state | ||
| 397 : | |||
| 398 : | A hash containing the new problem state. | ||
| 399 : | |||
| 400 : | =item errors | ||
| 401 : | |||
| 402 : | A string containing any errors encountered while rendering the problem. | ||
| 403 : | |||
| 404 : | =item warnings | ||
| 405 : | |||
| 406 : | A string containing any warnings encountered while rendering the problem. | ||
| 407 : | |||
| 408 : | =item flags | ||
| 409 : | |||
| 410 : | A hash containing PG_flags (see the Translator docs). | ||
| 411 : | |||
| 412 : | =back | ||
| 413 : | |||
| 414 : | sh002i | 1557 | =head1 METHODS PROVIDED BY THE BASE CLASS |
| 415 : | |||
| 416 : | The following methods are provided for use by subclasses of WeBWorK::PG. | ||
| 417 : | |||
| 418 : | =over | ||
| 419 : | |||
| 420 : | =item defineProblemEnvir ENVIRONMENT, USER, KEY, SET, PROBLEM, PSVN, FIELDS, OPTIONS | ||
| 421 : | |||
| 422 : | Generate a problem environment hash to pass to the renderer. | ||
| 423 : | |||
| 424 : | =item translateDisplayModeNames NAME | ||
| 425 : | |||
| 426 : | NAME contains | ||
| 427 : | |||
| 428 : | =back | ||
| 429 : | |||
| 430 : | sh002i | 440 | =head1 AUTHOR |
| 431 : | |||
| 432 : | Written by Sam Hathaway, sh002i (at) math.rochester.edu. | ||
| 433 : | |||
| 434 : | =cut |
| aubreyja at gmail dot com | ViewVC Help |
| Powered by ViewVC 1.0.9 |