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

Diff of /trunk/webwork2/lib/WeBWorK/ContentGenerator/Problem.pm

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

Revision 414 Revision 415
1package WeBWorK::ContentGenerator::Problem; 1package WeBWorK::ContentGenerator::Problem;
2our @ISA = qw(WeBWorK::ContentGenerator); 2use base qw(WeBWorK::ContentGenerator);
3 3
4use strict; 4use strict;
5use warnings; 5use warnings;
6use lib '/home/malsyned/xmlrpc/daemon'; 6use Apache::Constants qw(:common);
7use lib '/Users/gage/webwork-modperl/lib';
8use PGtranslator5;
9use WeBWorK::ContentGenerator; 7use WeBWorK::ContentGenerator;
10use Apache::Constants qw(:common); 8use WeBWorK::PG;
11 9
12############################################################################### 10# "Classic" form fields from processProblem8.pl
13# Configuration 11#
14############################################################################### 12# user - user ID
15my $USER_DIRECTORY = '/Users/gage'; 13# key - session key
16my $COURSE_SCRIPTS_DIRECTORY = "$USER_DIRECTORY/webwork/system/courseScripts/"; 14# course - course name
17my $MACRO_DIRECTORY = "$USER_DIRECTORY/webwork-modperl/courses/demoCourse/templates/macros/"; 15# probSetKey - USUALLY known as the PSVN
18my $TEMPLATE_DIRECTORY = "$USER_DIRECTORY/rochester_problib/"; 16# probNum - problem number a.k.a. ID a.k.a. name
19my $TEMP_URL = "http://127.0.0.1/~gage/rochester_problibtmp/"; 17#
20##my $HTML_DIRECTORY = "/Users/gage/Sites/rochester_problib/" #already obtained from courseEnvironment 18# Mode - display mode (HTML, HTML_tth, or typeset or whatever it's called)
21my $HTML_URL = "http://127.0.0.1/~gage/rochester_problib/"; 19# show_old_answers - whether or not student's old answers should be filled in
22my $TEMP_DIRECTORY = ""; # has to be here... for now 20# ShowAns - asks for correct answer to be shown -- only available for instructors
23 21# answer$i - student answers
24############################################################################### 22# showEdit - checks if the ShowEditor button should be shown and clicked
25# End configuration 23# showSol - checks if the solution button ishould be shown and clicked
26############################################################################### 24#
25# source - contains modified problem source when called from the web-based problem editor
26# seed - contains problem seed when called from the web-based problem editor
27# readSourceFromHTMLQ - if true, problem is read from 'source' instead of file
28# action - submit button clicked to invoke script (alledgedly)
29# 'Save updated version'
30# 'Read problem from disk'
31# 'Submit Answers'
32# 'Preview Answers'
33# 'Preview Again'
34# probFileName - name of the PG file being edited
35# languageType - afaik, always set to 'pg'
27 36
28sub title { 37sub title {
29 my ($self, $problem_set, $problem) = @_; 38 my ($self, $problem_set, $problem) = @_;
30 my $r = $self->{r}; 39 my $r = $self->{r};
31 my $user = $r->param('user'); 40 my $user = $r->param('user');
32 return "Problem $problem of problem set $problem_set for $user"; 41 return "Problem $problem of problem set $problem_set for $user";
33} 42}
34 43
35###############################################################################
36#
37# INITIALIZATION
38#
39# The following code initializes an instantiation of PGtranslator5 in the
40# parent process. This initialized object is then share with each of the
41# children forked from this parent process by the daemon.
42#
43# As far as I can tell, the child processes don't share any variable values even
44# though their namespaces are the same.
45###############################################################################
46# First some dummy values to use for testing.
47# These should be available from the problemEnvironment(it might be ok to assume that PG and dangerousMacros
48# live in the courseScripts (system level macros) directory.
49
50#print STDERR "Begin intitalization\n";
51my $dummy_envir = { courseScriptsDirectory => $COURSE_SCRIPTS_DIRECTORY,
52 displayMode => 'HTML_tth',
53 macroDirectory => $MACRO_DIRECTORY,
54 cgiURL => 'foo_cgiURL'};
55
56
57my $PG_PL = "${COURSE_SCRIPTS_DIRECTORY}PG.pl";
58my $DANGEROUS_MACROS_PL = "${COURSE_SCRIPTS_DIRECTORY}dangerousMacros.pl";
59my @MODULE_LIST = ( "Exporter", "DynaLoader", "GD", "WWPlot", "Fun",
60 "Circle", "Label", "PGrandom", "Units", "Hermite",
61 "List", "Match","Multiple", "Select", "AlgParser",
62 "AnswerHash", "Fraction", "VectorField", "Complex1",
63 "Complex", "MatrixReal1", "Matrix","Distributions",
64 "Regression"
65);
66my @EXTRA_PACKAGES = ( "AlgParserWithImplicitExpand", "Expr",
67 "ExprWithImplicitExpand", "AnswerEvaluator",
68
69);
70my $INITIAL_MACRO_PACKAGES = <<END_OF_TEXT;
71 DOCUMENT();
72 loadMacros(
73 "PGbasicmacros.pl",
74 "PGchoicemacros.pl",
75 "PGanswermacros.pl",
76 "PGnumericalmacros.pl",
77 "PGgraphmacros.pl",
78 "PGauxiliaryFunctions.pl",
79 "PGmatrixmacros.pl",
80 "PGcomplexmacros.pl",
81 "PGstatisticsmacros.pl"
82
83 );
84
85 TEXT("Hello world");
86
87 ENDDOCUMENT();
88
89END_OF_TEXT
90
91#These here documents have their drawbacks. KEEP END_OF_TEXT left justified!!!!!!
92
93###############################################################################
94# Now to define the body subroutine which does the hard work.
95###############################################################################
96
97
98#my $SOURCE1 = $INITIAL_MACRO_PACKAGES;
99
100sub body { 44sub body {
101 my ($self, $problem_set, $problem) = @_; 45 my ($self, $problem_set, $problem) = @_;
102 my $r = $self->{r};
103 my $courseEnvironment = $self->{courseEnvironment};
104 my $user = $r->param('user');
105 46
106 my $rh = {}; # this needs to be set to a hash containing CGI params 47 # we have to call init_translator like this:
48 my $pt = WeBWorK::PG->new($courseEnv, $userName, $setName, $problemNumber, $formData);
107 49
108
109 my $SOURCE1 = readFile("$problem_set/$problem.pg");
110 print STDERR "SOURCEFILE: \n$SOURCE1\n\n";
111
112 ###########################################################################
113 # The pg problem class should have a method for installing it's problemEnvironment
114 ###########################################################################
115
116 my $problemEnvir_rh = defineProblemEnvir($self);
117
118
119 ##################################################################################
120 # Prime the PGtranslator object and set it loose
121 ##################################################################################
122
123
124 ###############################################################################
125
126 ###############################################################################
127 #Create the PG translator.
128 ###############################################################################
129
130 my $pt = new PGtranslator5; #pt stands for problem translator;
131
132
133 # All of these hard coded directories need to be drawn from courseEnvironment.
134 # In addition I don't think that PGtranslator uses this stack internally yet.
135 # Passing these directories through the problemEnvironment variable is what
136 # is currently being done, but I don't think it is quite right, at least for most
137 # of them.
138
139
140 $pt ->rh_directories( { courseScriptsDirectory => $COURSE_SCRIPTS_DIRECTORY,
141 macroDirectory => $MACRO_DIRECTORY,
142 ,
143 templateDirectory => $TEMPLATE_DIRECTORY,
144 tempDirectory => $TEMP_DIRECTORY,
145 }
146 );
147
148 ###############################################################################
149 # First we load the modules from courseScripts directory.
150 # These do the "heavy lifting" in terms of formatting, creating graphs, and
151 # performing other heavy duty algorithms.
152 # 50 #
153 ###############################################################################
154 51
155 $pt -> evaluate_modules( @MODULE_LIST); 52 # ----- this is not a place of honor -----
156 $pt -> load_extra_packages( @EXTRA_PACKAGES );
157 53
158 ###############################################################################
159 # Load the environment constants. Some are used by the PGtranslator object but
160 # most of them are installed inside the Safe compartment where the problem
161 # runs.
162 ###############################################################################
163 #$pt -> environment($dummy_envir);
164 $pt -> environment($problemEnvir_rh);
165
166
167 # I've forgotten what this does exactly :-)
168 $pt->initialize();
169
170 ###############################################################################
171 # PG.pl contains the basic code which defines the problem interface, input and output.
172 # dangerousMacros.pl contains subroutines which have access to the hard drive and
173 # and the directory structure. All use of external resources by the problem is supposed
174 # to go through these subroutines. The idea is to put the potentially dangerous
175 # algorithms in on place so they can be watched closely.
176 # These two files are evaluated in the Safe compartment without any restrictions.
177 # They have full use of the perl commands.
178 ###############################################################################
179 my $loadErrors = $pt -> unrestricted_load($PG_PL );
180 print STDERR "$loadErrors\n" if ($loadErrors);
181 $loadErrors = $pt -> unrestricted_load($DANGEROUS_MACROS_PL);
182 print STDERR "$loadErrors\n" if ($loadErrors);
183
184 ###############################################################################
185 # Now set the mask to restrict the operations which can be performed within
186 # a problem or a macro file.
187 ###############################################################################
188 $pt-> set_mask();
189
190 # print "\nPG.pl: $PG_PL<br>\n";
191 # print "DANGEROUS_MACROS_PL: $DANGEROUS_MACROS_PL<br>\n";
192 # print "Print dummy environment<br>\n";
193 # print pretty_print_rh($dummy_envir), "<p>\n\n";
194
195 # Read in the source code for the problem
196
197 #$INITIAL_MACRO_PACKAGES =~ tr /\r/\n/; # change everything to unix line endings.
198 $SOURCE1 =~ tr /\r/\n/;
199 #print STDERR "Source again \n $SOURCE1";
200 $pt->source_string( $SOURCE1 );
201
202 ###############################################################################
203 # Install a safety filter for screening student answers. The default is now the blank
204 # filter since the answer evaluators do a pretty good job of recompiling and screening
205 # student's answers. Still, you could prohibit back ticks, or something of the kind.
206 ###############################################################################
207
208 $pt ->rf_safety_filter( \&safetyFilter); # install blank safety filter
209
210
211 print STDERR "New PGtranslator object inititialization completed.<br>\n";
212 ################################################################################
213 ## This ends the initialization of the PGtranslator object
214 ################################################################################
215
216
217 ################################################################################
218 # Run the problem (output the html text) but also store it within the object. 54 # Run the problem (output the html text) but also store it within the object.
219 # The correct answers are also calculated and stored within the object 55 # The correct answers are also calculated and stored within the object
220 ################################################################################
221 $pt ->translate(); 56 $pt ->translate();
222 57
223 #print problem output 58 # print problem output
224 print "Problem goes here<p>\n"; 59 print "Problem goes here<p>\n";
225 print "Problem output <br>\n"; 60 print "Problem output <br>\n";
226 print "################################################################################<br><br>"; 61 print "<HR>";
227 print ${$pt->r_text()}; 62 print ${$pt->r_text()};
228 print "<br><br>################################################################################<br>"; 63 print "<HR>";
229 print "<p>End of problem output<br>"; 64 print "<p>End of problem output<br>";
230 65
231 66
232 #print source code 67 # print source code
233 print "Source code<pre>\n"; 68 print "Source code<pre>\n";
234 print $SOURCE1; 69 print $SOURCE1;
235 print "</pre>End source code<p>"; 70 print "</pre>End source code<p>";
236 ################################################################################ 71
237 # The format for the output is described here. We'll need a local variable 72 # The format for the output is described here. We'll need a local variable
238 # to handle the warnings. From within the problem the warning command 73 # to handle the warnings. From within the problem the warning command
239 # has been slaved to the __WARNINGS__ routine which is defined in Global. 74 # has been slaved to the __WARNINGS__ routine which is defined in Global.
240 # We'll need to provide an alternate mechanism. 75 # We'll need to provide an alternate mechanism.
241 # The base64 encoding is only needed for xml transmission. 76 # The base64 encoding is only needed for xml transmission.
242 ################################################################################ 77 print "<hr>";
243 print "################################################################################<br>";
244 print "Warnings output<br>"; 78 print "Warnings output<br>";
245 my $WARNINGS = "Let this be a warning:"; 79 my $WARNINGS = "Let this be a warning:";
246 80
247 print $WARNINGS; 81 print $WARNINGS;
248 82
249 ################################################################################
250 # Install the standard problem grader. See gage/xmlrpc/daemon.pm or processProblem8 for detailed 83 # Install the standard problem grader. See gage/xmlrpc/daemon.pm or processProblem8 for detailed
251 # code on how to choose which problem grader to install, depending on courseEnvironment and problem data. 84 # code on how to choose which problem grader to install, depending on courseEnvironment and problem data.
252 # See also PG.pl which provides for problem by problem overrides. 85 # See also PG.pl which provides for problem by problem overrides.
253 ################################################################################
254
255 $pt->rf_problem_grader($pt->rf_std_problem_grader); 86 $pt->rf_problem_grader($pt->rf_std_problem_grader);
256 87
257 ################################################################################
258 # creates and stores a hash of answer results inside the object: $rh_answer_results 88 # creates and stores a hash of answer results inside the object: $rh_answer_results
259 ################################################################################
260 $pt -> process_answers($rh->{envir}->{inputs_ref}); 89 $pt -> process_answers($rh->{envir}->{inputs_ref});
261 90
262 91
263 # THE UPDATE AND GRADING LOGIC COULD USE AN OVERHAUL. IT WAS SOMEWHAT CONSTRAINED 92 # THE UPDATE AND GRADING LOGIC COULD USE AN OVERHAUL. IT WAS SOMEWHAT CONSTRAINED
264 # BY LEGACY CONDITIONS IN THE ORIGINAL PROCESSPROBLEM8. IT'S NOT BAD 93 # BY LEGACY CONDITIONS IN THE ORIGINAL PROCESSPROBLEM8. IT'S NOT BAD
265 # BUT IT COULD PROBABLY BE MADE A LITTLE MORE STRAIGHT FORWARD. 94 # BUT IT COULD PROBABLY BE MADE A LITTLE MORE STRAIGHT FORWARD.
266 ################################################################################ 95 #
267 # updates the problem state stored by the translator object from the problemEnvironment data 96 # updates the problem state stored by the translator object from the problemEnvironment data
268 ################################################################################
269 97
270 # $pt->rh_problem_state({ recorded_score => $rh->{problem_state}->{recorded_score}, 98 # $pt->rh_problem_state({ recorded_score => $rh->{problem_state}->{recorded_score},
271 # num_of_correct_ans => $rh->{problem_state}->{num_of_correct_ans} , 99 # num_of_correct_ans => $rh->{problem_state}->{num_of_correct_ans} ,
272 # num_of_incorrect_ans => $rh->{problem_state}->{num_of_incorrect_ans} 100 # num_of_incorrect_ans => $rh->{problem_state}->{num_of_incorrect_ans}
273 # } ); 101 # } );
274 ################################################################################ 102
275 # grade the problem (and update the problem state again.) 103 # grade the problem (and update the problem state again.)
276 ################################################################################ 104 #
277
278 # Define an entry order -- the default is the order they are received from the browser. 105 # Define an entry order -- the default is the order they are received from the browser.
279 # (Which as I understand it is NOT guaranteed to be the Left->Right Up-> Down order we're 106 # (Which as I understand it is NOT guaranteed to be the Left->Right Up-> Down order we're
280 # used to in the West. 107 # used to in the West.
281 108
282 my %PG_FLAGS = $pt->h_flags; 109 my %PG_FLAGS = $pt->h_flags;
299 WARNINGS => $WARNINGS, #encode_base64($WARNINGS ), 126 WARNINGS => $WARNINGS, #encode_base64($WARNINGS ),
300 problem_result => $rh_problem_result, 127 problem_result => $rh_problem_result,
301 problem_state => $rh_problem_state, 128 problem_state => $rh_problem_state,
302 PG_flag => \%PG_FLAGS 129 PG_flag => \%PG_FLAGS
303 }; 130 };
304 ########################################################################################## 131
305 # Debugging printout of environment tables 132 # Debugging printout of environment tables
306 ##########################################################################################
307
308 print "<P>Request item<P>\n\n"; 133 print "<P>Request item<P>\n\n";
309 print "<TABLE border=\"3\">"; 134 print "<TABLE border=\"3\">";
310 print $self->print_form_data('<tr><td>','</td><td>','</td></tr>'); 135 print $self->print_form_data('<tr><td>','</td><td>','</td></tr>');
311 print "</table>\n"; 136 print "</table>\n";
312 print "path info <br>\n"; 137 print "path info <br>\n";
314 print "<P>\n\ncourseEnvironment<P>\n\n"; 139 print "<P>\n\ncourseEnvironment<P>\n\n";
315 print pretty_print_rh($courseEnvironment); 140 print pretty_print_rh($courseEnvironment);
316 print "<P>\n\nproblemEnvironment<P>\n\n"; 141 print "<P>\n\nproblemEnvironment<P>\n\n";
317 print pretty_print_rh($problemEnvir_rh); 142 print pretty_print_rh($problemEnvir_rh);
318 143
319 ##########################################################################################
320 # End
321 ##########################################################################################
322 ""; 144 "";
323} 145}
324# End the"body" routine for the Problem object.
325 146
326
327sub safetyFilter {
328 my $answer = shift; # accepts one answer and checks it
329 my $submittedAnswer = $answer;
330 $answer = '' unless defined $answer;
331 my ($errorno);
332 $answer =~ tr/\000-\037/ /;
333 #### Return if answer field is empty ########
334 unless ($answer =~ /\S/) {
335# $errorno = "<BR>No answer was submitted.";
336 $errorno = 0; ## don't report blank answer as error
337
338 return ($answer,$errorno);
339 }
340 ######### replace ^ with ** (for exponentiation)
341 # $answer =~ s/\^/**/g;
342 ######### Return if forbidden characters are found
343 unless ($answer =~ /^[a-zA-Z0-9_\-\+ \t\/@%\*\.\n^\(\)]+$/ ) {
344 $answer =~ tr/a-zA-Z0-9_\-\+ \t\/@%\*\.\n^\(\)/#/c;
345 $errorno = "<BR>There are forbidden characters in your answer: $submittedAnswer<BR>";
346
347 return ($answer,$errorno);
348 }
349
350 $errorno = 0;
351 return($answer, $errorno);
352}
353
354
355
356
357########################################################################################
358# This is the problemEnvironment structure that needs to be filled out in order to provide
359# information to PGtranslator which in turn supports the problem environment
360########################################################################################
361
362sub defineProblemEnvir {
363 my $self = shift;
364 my $r = $self->{r};
365 my $courseEnvironment = $self->{courseEnvironment};
366 my %envir=();
367# $envir{'refSubmittedAnswers'} = $refSubmittedAnswers if defined($refSubmittedAnswers);
368 $envir{'psvnNumber'} = 123456789;
369 $envir{'psvn'} = 123456789;
370 $envir{'studentName'} = 'Jane Doe';
371 $envir{'studentLogin'} = 'jd001m';
372 $envir{'studentID'} = 'xxx-xx-4321';
373 $envir{'sectionName'} = 'gage';
374 $envir{'sectionNumber'} = '111foobar';
375 $envir{'recitationName'} = 'gage_recitation';
376 $envir{'recitationNumber'} = '11_foobar recitation';
377 $envir{'setNumber'} = 'setAlgebraicGeometry';
378 $envir{'questionNumber'} = 43;
379 $envir{'probNum'} = 43;
380 $envir{'openDate'} = 3014438528;
381 $envir{'formattedOpenDate'} = '3/4/02';
382 $envir{'dueDate'} = 4014438528;
383 $envir{'formattedDueDate'} = '10/4/04';
384 $envir{'answerDate'} = 4014438528;
385 $envir{'formattedAnswerDate'} = '10/4/04';
386 $envir{'problemValue'} = 1;
387 $envir{'fileName'} = 'problem1';
388 $envir{'probFileName'} = 'problem1';
389 $envir{'languageMode'} = 'HTML_tth';
390 $envir{'displayMode'} = 'HTML_tth';
391 $envir{'outputMode'} = 'HTML_tth';
392 $envir{'courseName'} = $courseEnvironment ->{courseName};
393 $envir{'sessionKey'} = 'asdf';
394
395# initialize constants for PGanswermacros.pl
396 $envir{'numRelPercentTolDefault'} = .1;
397 $envir{'numZeroLevelDefault'} = 1E-14;
398 $envir{'numZeroLevelTolDefault'} = 1E-12;
399 $envir{'numAbsTolDefault'} = .001;
400 $envir{'numFormatDefault'} = '';
401 $envir{'functRelPercentTolDefault'} = .1;
402 $envir{'functZeroLevelDefault'} = 1E-14;
403 $envir{'functZeroLevelTolDefault'} = 1E-12;
404 $envir{'functAbsTolDefault'} = .001;
405 $envir{'functNumOfPoints'} = 3;
406 $envir{'functVarDefault'} = 'x';
407 $envir{'functLLimitDefault'} = .0000001;
408 $envir{'functULimitDefault'} = .9999999;
409 $envir{'functMaxConstantOfIntegration'} = 1E8;
410# kludge check definition of number of attempts again. The +1 is because this is used before the current answer is evaluated.
411 $envir{'numOfAttempts'} = 2; #&getProblemNumOfCorrectAns($probNum,$psvn)
412 # &getProblemNumOfIncorrectAns($probNum,$psvn)+1;
413
414#
415#
416# defining directorys and URLs
417 $envir{'templateDirectory'} = $courseEnvironment ->{courseDirs}->{templates};
418############ $envir{'classDirectory'} = $Global::classDirectory;
419# $envir{'cgiDirectory'} = $Global::cgiDirectory;
420# $envir{'cgiURL'} = getWebworkCgiURL();
421
422# $envir{'scriptDirectory'} = $Global::scriptDirectory;##omit
423 $envir{'webworkDocsURL'} = 'http://webwork.math.rochester.edu';
424 $envir{'externalTTHPath'} = '/usr/local/bin/tth';
425
426
427#
428 $envir{'inputs_ref'} = $r->param;
429 $envir{'problemSeed'} = 3245;
430 $envir{'displaySolutionsQ'} = 1;
431 $envir{'displayHintsQ'} = 1;
432
433# Directory values -- do we really need them here?
434 $envir{courseScriptsDirectory} = $COURSE_SCRIPTS_DIRECTORY;
435 $envir{macroDirectory} = $MACRO_DIRECTORY;
436 $envir{templateDirectory} = $TEMPLATE_DIRECTORY;
437 $envir{tempDirectory} = $TEMP_DIRECTORY;
438 $envir{tempURL} = $TEMP_URL;
439 $envir{htmlURL} = $HTML_URL;
440 $envir{'htmlDirectory'} = $courseEnvironment ->{courseDirectory}->{html};
441 # here is a way to pass environment variables defined in webworkCourse.ph
442# my $k;
443# foreach $k (keys %Global::PG_environment ) {
444# $envir{$k} = $Global::PG_environment{$k};
445# }
446 \%envir;
447}
448
449########################################################################################
450# This recursive pretty_print function will print a hash and its sub hashes.
451########################################################################################
452sub pretty_print_rh { 147sub pretty_print_rh {
453 my $r_input = shift; 148 my $r_input = shift;
454 my $out = ''; 149 my $out = '';
455 if ( not ref($r_input) ) { 150 if ( not ref($r_input) ) {
456 $out = $r_input; # not a reference 151 $out = $r_input; # not a reference
495 $@=''; 190 $@='';
496 $SIG{__DIE__} = $save_SIG_die_trap; 191 $SIG{__DIE__} = $save_SIG_die_trap;
497 $out; 192 $out;
498} 193}
499 194
500###### 1951;
501# Utility for slurping souce files
502#######
503 196
504sub readFile { 197__END__
505 my $input = shift; # The set and problem: 'set0/prob1.pg'
506 my $filePath =$TEMPLATE_DIRECTORY .$input;
507 print STDERR "Reading problem from file $filePath \n";
508 print STDERR "<br>Reading problem from file $filePath <br>\n";
509 my $out;
510 print "The file is readable = ", -r $filePath, "\n";
511 if (-r $filePath) {
512 open IN, "<$filePath" or print STDERR "Hey, this file was supposed to be readable\n";
513 local($/)=undef;
514 $out = <IN>;
515 close(IN);
516 } else {
517 print "Could not read file at |$filePath|";
518 print STDERR "Could not read file at |$filePath|";
519 }
520 return($out);
521}
522 198
523my $foo =0; 199my $foo =0;
524 200
525# The warning mechanism. This needs to be turned into an object of its own 201# The warning mechanism. This needs to be turned into an object of its own
526############### 202###############
660 }; 336 };
661 337
662 338
663 339
664} 340}
665
6661;

Legend:
Removed from v.414  
changed lines
  Added in v.415

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9