Parent Directory
|
Revision Log
Changes made to simplify the implementation of XMLRPC access to the functionality of WeBWorK. There is still a problem with making sure that the proper warning mechanism is passed into the problem rendering compartment. The proper permissions for creating directories also does not get transmitted properly. I would like to change the way that the defineEnvirVars subroutine is passed into the Local.pm object. I'd like to either pass the environment as a hash or explicitly pass a reference to define EnvirVars rather than inherit it from PG. In particular I'd like to be able to define environment variables directly in RenderProblem.pm instead of faking it by defining fake problem objects.
1 #!/usr/local/bin/perl -w 2 3 # Copyright (C) 2001 Michael Gage 4 5 6 7 package WebworkWebservice::RenderProblem; 8 use WebworkWebservice; 9 use base qw(WebworkWebservice); 10 11 12 BEGIN { 13 $main::VERSION = "2.1"; 14 } 15 16 17 18 use strict; 19 use sigtrap; 20 use Carp; 21 use Safe; 22 use Apache; 23 use WeBWorK::CourseEnvironment; 24 use WeBWorK::PG::Translator; 25 use WeBWorK::PG::Local; 26 use WeBWorK::DB; 27 use WeBWorK::DB::Record; 28 use WeBWorK::DB::Record::UserProblem; 29 use WeBWorK::Constants; 30 use WeBWorK::Utils qw(runtime_use formatDateTime makeTempDirectory); 31 use WeBWorK::DB::Utils qw(global2user user2global findDefaults); 32 use WeBWorK::Utils::Tasks qw(fake_set fake_problem); 33 use WeBWorK::PG::IO; 34 use WeBWorK::PG::ImageGenerator; 35 use Benchmark; 36 use MIME::Base64 qw( encode_base64 decode_base64); 37 38 #print "rereading Webwork\n"; 39 40 41 our $WW_DIRECTORY = $WebworkWebservice::WW_DIRECTORY; 42 our $PG_DIRECTORY = $WebworkWebservice::PG_DIRECTORY; 43 our $COURSENAME = $WebworkWebservice::COURSENAME; 44 our $HOST_NAME = $WebworkWebservice::HOST_NAME; 45 our $HOSTURL ="http://$HOST_NAME:11002"; #FIXME 46 our $ce =$WebworkWebservice::SeedCE; 47 # create a local course environment for some course 48 $ce = WeBWorK::CourseEnvironment->new($WW_DIRECTORY, "", "", $COURSENAME); 49 #print "\$ce = \n", WeBWorK::Utils::pretty_print_rh($ce); 50 51 print "webwork is really ready\n\n"; 52 #other services 53 # File variables 54 #our $WARNINGS=''; 55 56 57 # imported constants 58 59 my $COURSE_TEMP_DIRECTORY = $ce->{courseDirs}->{html_tmp}; 60 my $COURSE_TEMP_URL = $HOSTURL.$ce->{courseURLs}->{html_tmp}; 61 62 my $pgMacrosDirectory = $ce->{pg_dir}.'/macros/'; 63 my $macroDirectory = $ce->{courseDirs}->{macros}.'/'; 64 my $templateDirectory = $ce->{courseDirs}->{templates}; 65 66 my %PG_environment = $ce->{pg}->{specialPGEnvironmentVars}; 67 68 69 use constant DISPLAY_MODES => { 70 # display name # mode name 71 tex => "TeX", 72 plainText => "HTML", 73 formattedText => "HTML_tth", 74 images => "HTML_dpng", 75 jsMath => "HTML_jsMath", 76 asciimath => "HTML_asciimath", 77 }; 78 79 use constant DISPLAY_MODE_FAILOVER => { 80 TeX => [], 81 HTML => [], 82 HTML_tth => [ "HTML", ], 83 HTML_dpng => [ "HTML_tth", "HTML", ], 84 HTML_jsMath => [ "HTML_dpng", "HTML_tth", "HTML", ], 85 HTML_asciimath => [ "HTML_dpng", "HTML_tth", "HTML", ], 86 # legacy modes -- these are not supported, but some problems might try to 87 # set the display mode to one of these values manually and some macros may 88 # provide rendered versions for these modes but not the one we want. 89 Latex2HTML => [ "TeX", "HTML", ], 90 HTML_img => [ "HTML_dpng", "HTML_tth", "HTML", ], 91 }; 92 93 94 95 96 97 98 99 sub renderProblem { 100 101 my $rh = shift; 102 103 ########################################### 104 # Grab the course name, if this request is going to depend on 105 # some course other than the default course 106 ########################################### 107 my $courseName; 108 my $ce; 109 my $db; 110 my $user; 111 my $beginTime = new Benchmark; 112 if (defined($rh->{course}) and $rh->{course}=~/\S/ ) { 113 $courseName = $rh->{course}; 114 } else { 115 $courseName = $COURSENAME; 116 # use the default $ce 117 } 118 #FIXME put in check to make sure the course exists. 119 eval { 120 $ce = WeBWorK::CourseEnvironment->new($WW_DIRECTORY, "", "", $courseName); 121 # Create database object for this course 122 $db = WeBWorK::DB->new($ce->{dbLayout}); 123 }; 124 $ce->{pg}->{options}->{catchWarnings}; 125 #^FIXME need better way of determining whether the course actually exists. 126 if ($@) { 127 $ce = WeBWorK::CourseEnvironment->new($WW_DIRECTORY, "", "", $COURSENAME); 128 $db = WeBWorK::DB->new($ce->{dbLayout}); 129 } 130 my $user = $rh->{user}; 131 $user = 'gage' unless defined $user and $user =~/\S/; 132 133 ########################################### 134 # Authenticate this request 135 ########################################### 136 137 138 139 ########################################### 140 # Determine the authorization level (permissions) 141 ########################################### 142 143 144 145 146 ########################################### 147 # Determine the method for accessing data 148 ########################################### 149 my $problem_source_access = $rh->{problem_source_access}; 150 # One of 151 # source_from_course_set_problem 152 # source_from_source_file_path 153 # source_from_request 154 155 my $data_access = $rh->{data_access}; 156 # One of 157 # data_from_course 158 # data_from_request 159 160 ########################################### 161 # Determine an effective user for this interaction 162 # or create one if it is not given 163 # In order: use effectiveUserName, studentLogin, or user or 'foobar' 164 ########################################### 165 my $effectiveUserName; 166 if (defined($rh->{effectiveUser}) and $rh->{effectiveUser}=~/\S/ ) { 167 $effectiveUserName = $rh->{effectiveUser}; 168 } elsif (defined($rh->{envir}->{studentLogin}) and $rh->{envir}->{studentLogin}=~/\S/ ) { 169 $effectiveUserName = $rh->{envir}->{studentLogin}; 170 } elsif (defined($user) and $user =~ /\S/ ) { 171 $effectiveUserName = $user; 172 } else { 173 $effectiveUserName = 'foobar'; 174 } 175 ################################################## 176 my $effectiveUser = $db->getUser($effectiveUserName); # checked 177 my $effectiveUserPermissionLevel; 178 my $effectiveUserPassword; 179 unless (defined $effectiveUser ) { 180 $effectiveUser = $db->newUser; 181 $effectiveUserPermissionLevel = $db->newPermissionLevel; 182 $effectiveUserPassword = $db->newPassword; 183 $effectiveUser->user_id($effectiveUserName); 184 $effectiveUserPermissionLevel->user_id($effectiveUserName); 185 $effectiveUserPassword->user_id($effectiveUserName); 186 $effectiveUserPassword->password(''); 187 $effectiveUser->last_name($rh->{envir}->{studentName}|| 'foobar'); 188 $effectiveUser->first_name(''); 189 $effectiveUser->student_id($rh->{envir}->{studentID}|| 'foobar'); 190 $effectiveUser->email_address($rh->{envir}->{email}|| ''); 191 $effectiveUser->section($rh->{envir}->{section} ||''); 192 $effectiveUser->recitation($rh->{envir}->{recitation} ||''); 193 $effectiveUser->comment(''); 194 $effectiveUser->status('C'); 195 $effectiveUser->password($rh->{envir}->{studentID}|| 'foobar'); 196 $effectiveUserPermissionLevel->permission(0); 197 } 198 #FIXME these will fail if the keys are not defined within the environment. 199 ########################################### 200 # Insure that set and problem are defined 201 # Define the set and problem information from 202 # data in the environment if necessary 203 ########################################### 204 # determine the set name and the set problem number 205 my $setName = (defined($rh->{envir}->{setNumber}) ) ? $rh->{envir}->{setNumber} : ''; 206 my $problemNumber = (defined($rh->{envir}->{probNum}) ) ? $rh->{envir}->{probNum} : 1 ; 207 my $problemSeed = (defined($rh->{envir}->{problemSeed})) ? $rh->{envir}->{problemSeed} : 1 ; 208 my $psvn = (defined($rh->{envir}->{psvn}) ) ? $rh->{envir}->{psvn} : 1234 ; 209 my $problemStatus = $rh->{problem_state}->{recorded_score}|| 0 ; 210 my $problemValue = (defined($rh->{envir}->{problemValue})) ? $rh->{envir}->{problemValue} : 1 ; 211 my $num_correct = $rh->{problem_state}->{num_correct} || 0 ; 212 my $num_incorrect = $rh->{problem_state}->{num_incorrect} || 0 ; 213 my $problemAttempted = ($num_correct && $num_incorrect); 214 my $lastAnswer = ''; 215 216 my $setRecord = $db->getMergedSet($effectiveUserName, $setName); 217 unless (defined($setRecord) and ref($setRecord) ) { 218 # if a User Set does not exist for this user and this set 219 # then we check the Global Set 220 # if that does not exist we create a fake set 221 # if it does, we add fake user data 222 my $userSetClass = $db->{set_user}->{record}; 223 my $globalSet = $db->getGlobalSet($setName); # checked 224 225 if (not defined $globalSet) { 226 $setRecord = fake_set($db); 227 } else { 228 $setRecord = global2user($userSetClass, $globalSet); 229 } 230 # initializations 231 $setRecord->set_id($setName); 232 $setRecord->set_header(""); 233 $setRecord->hardcopy_header(""); 234 $setRecord->open_date(time()-60*60*24*7); # one week ago 235 $setRecord->due_date(time()+60*60*24*7*2); # in two weeks 236 $setRecord->answer_date(time()+60*60*24*7*3); # in three weeks 237 $setRecord->psvn($rh->{envir}->{psvn}||0); 238 } 239 #warn "set Record is $setRecord"; 240 # obtain the merged problem for $effectiveUser 241 my $problemRecord = $db->getMergedProblem($effectiveUserName, $setName, $problemNumber); 242 243 # if that is not yet defined obtain the global problem, 244 # convert it to a user problem, and add fake user data 245 unless (defined $problemRecord) { 246 my $userProblemClass = $db->{problem_user}->{record}; 247 my $globalProblem = $db->getGlobalProblem($setName, $problemNumber); # checked 248 # if the global problem doesn't exist either, bail! 249 if(not defined $globalProblem) { 250 $problemRecord = fake_problem($db); 251 } else { 252 $problemRecord = global2user($userProblemClass, $globalProblem); 253 } 254 # initializations 255 $problemRecord->user_id($effectiveUserName); 256 $problemRecord->problem_id($problemNumber); 257 $problemRecord->set_id($setName); 258 $problemRecord->problem_seed($problemSeed); 259 $problemRecord->status($problemStatus); 260 $problemRecord->value($problemValue); 261 $problemRecord->attempted($problemAttempted); 262 $problemRecord->last_answer($lastAnswer); 263 $problemRecord->num_correct($num_correct); 264 $problemRecord->num_incorrect($num_incorrect); 265 } 266 # initialize problem source 267 my $problem_source; 268 my $r_problem_source =undef; 269 if (defined($rh->{source})) { 270 $problem_source = decode_base64($rh->{source}); 271 $problem_source =~ tr /\r/\n/; 272 $r_problem_source =\$problem_source; 273 } elsif (defined($rh->{sourceFilePath}) and $rh->{sourceFilePath} =/\S/) { 274 $problemRecord->source_file($rh->{sourceFilePath}); 275 } 276 $problemRecord->source_file('foobar') unless defined($problemRecord->source_file); 277 278 #warn "problem Record is $problemRecord"; 279 # now we're sure we have valid UserSet and UserProblem objects 280 # yay! 281 282 ################################################## 283 # Other initializations 284 ################################################## 285 my $translationOptions = { 286 displayMode => $rh->{envir}->{displayMode}, 287 showHints => $rh->{envir}->{showHints}, 288 showSolutions => $rh->{envir}->{showSolutions}, 289 refreshMath2img => $rh->{envir}->{showHints} || $rh->{envir}->{showSolutions}, 290 processAnswers => 1, 291 # methods for supplying the source, 292 r_source => $r_problem_source, # reference to a source file string. 293 # if reference is not defined then the path is obtained 294 # from the problem object. 295 r_envirOverrides => $rh, 296 }; 297 298 my $formFields = $rh->{envir}->{inputs_ref}; 299 my $key = $rh->{envir}->{key} || ''; 300 301 302 #check definitions 303 #warn "setRecord is ", WebworkWebservice::pretty_print_rh($setRecord); 304 #warn "problemRecord is",WebworkWebservice::pretty_print_rh($problemRecord); 305 # warn "envir is\n ",WebworkWebservice::pretty_print_rh(__PACKAGE__->defineProblemEnvir( 306 # $ce, 307 # $effectiveUser, 308 # $key, 309 # $setRecord, 310 # $problemRecord, 311 # $psvn, 312 # $formFields, 313 # $translationOptions, 314 # )); 315 ################################################# 316 317 # Other options can be over ridden by modifying 318 # $ce->{pg} 319 320 321 322 # We'll try to use this code instead so that Local does all of the work. 323 # Most of the configuration will take place in the fake course associated 324 # with XMLRPC responses 325 # problem needs to be loaded with the following: 326 # source_file 327 # status 328 # num_correct 329 # num_incorrect 330 # it doesn't seem that $effectiveUser, $set or $key is used in the subroutine 331 # except that it is passed on to defineProblemEnvironment 332 333 my $pg; 334 $pg = WebworkWebservice::RenderProblem->new( 335 $ce, 336 $effectiveUser, 337 $key, 338 $setRecord, 339 $problemRecord, 340 $setRecord->psvn, # FIXME: this field should be removed 341 $formFields, 342 # translation options 343 $translationOptions, 344 345 ); 346 347 348 349 # new version of output: 350 my $out2 = { 351 text => encode_base64( $pg->{body_text} ), 352 header_text => encode_base64( $pg->{head_text} ), 353 answers => $pg->{answers}, 354 errors => $pg->{errors}, 355 WARNINGS => encode_base64($pg->{warnings} ), 356 problem_result => $pg->{result}, 357 problem_state => $pg->{state}, 358 #PG_flag => $pg->{flags}, 359 360 361 362 }; 363 # Hack to filter out CODE references 364 foreach my $ans (keys %{$out2->{answers}}) { 365 foreach my $item (keys %{$out2->{answers}->{$ans}}) { 366 my $contents = $out2->{answers}->{$ans}->{$item}; 367 if (ref($contents) =~ /CODE/ ) { 368 #warn "removing code at $ans $item "; 369 $out2->{answers}->{$ans}->{$item} = undef; 370 } 371 } 372 373 } 374 $out2->{PG_flag}->{PROBLEM_GRADER_TO_USE} = undef; 375 my $endTime = new Benchmark; 376 $out2->{compute_time} = logTimingInfo($beginTime, $endTime); 377 # warn "flags are" , WebworkWebservice::pretty_print_rh($pg->{flags}); 378 $out2; 379 380 } 381 382 383 384 385 386 sub logTimingInfo{ 387 my ($beginTime,$endTime,) = @_; 388 my $out = ""; 389 $out .= Benchmark::timestr( Benchmark::timediff($endTime , $beginTime) ); 390 $out; 391 } 392 393 394 ###################################################################### 395 sub new { 396 shift; # throw away invocant -- we don't need it 397 my ($ce, $user, $key, $set, $problem, $psvn, $formFields, 398 $translationOptions) = @_; 399 400 my $renderer = 'WeBWorK::PG::Local'; 401 402 runtime_use $renderer; 403 # the idea is to have Local call back to the defineProblemEnvir below. 404 return WeBWorK::PG::Local::new($renderer,@_); 405 } 406 407 408 #FIXME 409 # Save these subroutines. 410 # I'd like to use this version of defineProblemEnvir instead of the 411 # the version in PG.pm That adds flexibility. 412 413 414 # sub translateDisplayModeNames($) { 415 # my $name = shift; 416 # return DISPLAY_MODES()->{$name}; 417 # } 418 # sub defineProblemEnvir { 419 # my ( 420 # $self, 421 # $ce, 422 # $user, 423 # $key, 424 # $set, 425 # $problem, 426 # $psvn, 427 # $formFields, 428 # $options, 429 # ) = @_; 430 # 431 # my %envir; 432 # 433 # # ---------------------------------------------------------------------- 434 # 435 # # PG environment variables 436 # # from docs/pglanguage/pgreference/environmentvariables as of 06/25/2002 437 # # any changes are noted by "ADDED:" or "REMOVED:" 438 # 439 # # Vital state information 440 # # ADDED: displayModeFailover, displayHintsQ, displaySolutionsQ, 441 # # refreshMath2img, texDisposition 442 # 443 # $envir{psvn} = $set->psvn; 444 # $envir{psvnNumber} = $envir{psvn}; 445 # $envir{probNum} = $problem->problem_id; 446 # $envir{questionNumber} = $envir{probNum}; 447 # $envir{fileName} = $problem->source_file; 448 # $envir{probFileName} = $envir{fileName}; 449 # $envir{problemSeed} = $problem->problem_seed; 450 # $envir{displayMode} = translateDisplayModeNames($options->{displayMode}); 451 # $envir{languageMode} = $envir{displayMode}; 452 # $envir{outputMode} = $envir{displayMode}; 453 # $envir{displayHintsQ} = $options->{showHints}; 454 # $envir{displaySolutionsQ} = $options->{showSolutions}; 455 # $envir{texDisposition} = "pdf"; # in webwork2, we use pdflatex 456 # 457 # # Problem Information 458 # # ADDED: courseName, formatedDueDate 459 # 460 # $envir{openDate} = $set->open_date; 461 # $envir{formattedOpenDate} = formatDateTime($envir{openDate}, $ce->{siteDefaults}{timezone}); 462 # $envir{dueDate} = $set->due_date; 463 # $envir{formattedDueDate} = formatDateTime($envir{dueDate}, $ce->{siteDefaults}{timezone}); 464 # $envir{formatedDueDate} = $envir{formattedDueDate}; # typo in many header files 465 # $envir{answerDate} = $set->answer_date; 466 # $envir{formattedAnswerDate} = formatDateTime($envir{answerDate}, $ce->{siteDefaults}{timezone}); 467 # $envir{numOfAttempts} = ($problem->num_correct || 0) + ($problem->num_incorrect || 0); 468 # $envir{problemValue} = $problem->value; 469 # $envir{sessionKey} = $key; 470 # $envir{courseName} = $ce->{courseName}; 471 # 472 # # Student Information 473 # # ADDED: studentID 474 # 475 # $envir{sectionName} = $user->section; 476 # $envir{sectionNumber} = $envir{sectionName}; 477 # $envir{recitationName} = $user->recitation; 478 # $envir{recitationNumber} = $envir{recitationName}; 479 # $envir{setNumber} = $set->set_id; 480 # $envir{studentLogin} = $user->user_id; 481 # $envir{studentName} = $user->first_name . " " . $user->last_name; 482 # $envir{studentID} = $user->student_id; 483 # 484 # # Answer Information 485 # # REMOVED: refSubmittedAnswers 486 # 487 # $envir{inputs_ref} = $formFields; 488 # 489 # # External Programs 490 # # ADDED: externalLaTeXPath, externalDvipngPath, 491 # # externalGif2EpsPath, externalPng2EpsPath 492 # 493 # $envir{externalTTHPath} = $ce->{externalPrograms}->{tth}; 494 # $envir{externalLaTeXPath} = $ce->{externalPrograms}->{latex}; 495 # $envir{externalDvipngPath} = $ce->{externalPrograms}->{dvipng}; 496 # $envir{externalGif2EpsPath} = $ce->{externalPrograms}->{gif2eps}; 497 # $envir{externalPng2EpsPath} = $ce->{externalPrograms}->{png2eps}; 498 # $envir{externalGif2PngPath} = $ce->{externalPrograms}->{gif2png}; 499 # 500 # # Directories and URLs 501 # # REMOVED: courseName 502 # # ADDED: dvipngTempDir 503 # # ADDED: jsMathURL 504 # # ADDED: asciimathURL 505 # 506 # $envir{cgiDirectory} = undef; 507 # $envir{cgiURL} = undef; 508 # $envir{classDirectory} = undef; 509 # $envir{courseScriptsDirectory} = $ce->{pg}->{directories}->{macros}."/"; 510 # $envir{htmlDirectory} = $ce->{courseDirs}->{html}."/"; 511 # $envir{htmlURL} = $ce->{courseURLs}->{html}."/"; 512 # $envir{macroDirectory} = $ce->{courseDirs}->{macros}."/"; 513 # $envir{templateDirectory} = $ce->{courseDirs}->{templates}."/"; 514 # $envir{tempDirectory} = $ce->{courseDirs}->{html_temp}."/"; 515 # $envir{tempURL} = $ce->{courseURLs}->{html_temp}."/"; 516 # $envir{scriptDirectory} = undef; 517 # $envir{webworkDocsURL} = $ce->{webworkURLs}->{docs}."/"; 518 # $envir{localHelpURL} = $ce->{webworkURLs}->{local_help}."/"; 519 # $envir{jsMathURL} = $ce->{webworkURLs}->{jsMath}; 520 # $envir{asciimathURL} = $ce->{webworkURLs}->{asciimath}; 521 # 522 # # Information for sending mail 523 # 524 # $envir{mailSmtpServer} = $ce->{mail}->{smtpServer}; 525 # $envir{mailSmtpSender} = $ce->{mail}->{smtpSender}; 526 # $envir{ALLOW_MAIL_TO} = $ce->{mail}->{allowedRecipients}; 527 # 528 # # Default values for evaluating answers 529 # 530 # my $ansEvalDefaults = $ce->{pg}->{ansEvalDefaults}; 531 # $envir{$_} = $ansEvalDefaults->{$_} foreach (keys %$ansEvalDefaults); 532 # 533 # # ---------------------------------------------------------------------- 534 # 535 # my $basename = "equation-$envir{psvn}.$envir{probNum}"; 536 # $basename .= ".$envir{problemSeed}" if $envir{problemSeed}; 537 # 538 # # to make grabbing these options easier, we'll pull them out now... 539 # my %imagesModeOptions = %{$ce->{pg}->{displayModeOptions}->{images}}; 540 # 541 # # Object for generating equation images 542 # $envir{imagegen} = WeBWorK::PG::ImageGenerator->new( 543 # tempDir => $ce->{webworkDirs}->{tmp}, # global temp dir 544 # latex => $envir{externalLaTeXPath}, 545 # dvipng => $envir{externalDvipngPath}, 546 # useCache => 1, 547 # cacheDir => $ce->{webworkDirs}->{equationCache}, 548 # cacheURL => $ce->{webworkURLs}->{equationCache}, 549 # cacheDB => $ce->{webworkFiles}->{equationCacheDB}, 550 # useMarkers => ($imagesModeOptions{dvipng_align} && $imagesModeOptions{dvipng_align} eq 'mysql'), 551 # dvipng_align => $imagesModeOptions{dvipng_align}, 552 # dvipng_depth_db => $imagesModeOptions{dvipng_depth_db}, 553 # ); 554 # 555 # # ADDED: jsMath options 556 # $envir{jsMath} = {%{$ce->{pg}{displayModeOptions}{jsMath}}}; 557 # 558 # # Other things... 559 # $envir{QUIZ_PREFIX} = $options->{QUIZ_PREFIX}; # used by quizzes 560 # $envir{PROBLEM_GRADER_TO_USE} = $ce->{pg}->{options}->{grader}; 561 # $envir{PRINT_FILE_NAMES_FOR} = $ce->{pg}->{specialPGEnvironmentVars}->{PRINT_FILE_NAMES_FOR}; 562 # 563 # # ADDED: __files__ 564 # # an array for mapping (eval nnn) to filenames in error messages 565 # $envir{__files__} = { 566 # root => $ce->{webworkDirs}{root}, # used to shorten filenames 567 # pg => $ce->{pg}{directories}{root}, # ditto 568 # tmpl => $ce->{courseDirs}{templates}, # ditto 569 # }; 570 # 571 # # variables for interpreting capa problems and other things to be 572 # # seen in a pg file 573 # my $specialPGEnvironmentVarHash = $ce->{pg}->{specialPGEnvironmentVars}; 574 # for my $SPGEV (keys %{$specialPGEnvironmentVarHash}) { 575 # $envir{$SPGEV} = $specialPGEnvironmentVarHash->{$SPGEV}; 576 # } 577 # 578 # return \%envir; 579 # } 580 581 582 583 584 1;
| aubreyja at gmail dot com | ViewVC Help |
| Powered by ViewVC 1.0.9 |