#! /usr/bin/perl # Copyright (C) 2005 Nick Urbanik # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # for i in *;do b=$(basename "$i" .gift); grep -c "$b" "$i" /dev/null;done|grep -v /dev/null # 1.105.1.gift:26 # 1.105.2.gift:8 # 1.106.1.gift:13 # 1.106.2.gift:7 # 1.107.2.gift:4 # 1.107.3.gift:4 # 1.107.4.gift:5 # 1.108.1.gift:12 # 1.108.2.gift:3 # 1.108.5.gift:3 # 1.109.1.gift:8 # 1.111.1.gift:16 # 1.111.2.gift:8 # 1.111.3.gift:5 # 1.111.4.gift:12 # 1.111.5.gift:12 # 1.111.6.gift:4 # 1.112.1.gift:11 # 1.112.3.gift:32 # 1.112.4.gift:10 # 1.113.1.gift:18 # 1.113.2.gift:8 # 1.113.3.gift:12 # 1.113.4.gift:25 # 1.113.5.gift:5 # 1.113.7.gift:2 # 1.114.1.gift:2 # 1.114.2.gift:2 # 1.114.3.gift:2 # 1.114.4.gift:1 # No topic.gift:5 use warnings; use strict; use File::Basename; use Carp; use constant DEBUG_OPTIONS => 0; use constant DEBUG_TRUE_FALSE => 1; use constant DEBUG_STATS => 1; use constant DEBUG_UNDERSCORES => 0; use constant DEBUG_MULTIPLE_ANSWERS => 0; use constant DEBUG_PRE => 1; use constant GIFT_DIR => "gift"; our $to_standard_output = 0; # Special Characters ~ = # { } : # These symbols ~ = # { } control the operation of this filter and # cannot be used as normal text within questions. Since these symbols # have a special role in determining the operation of this filter, # they are called "control characters." But sometimes you may want to # use one of these characters, for example to show a mathematical # formula in a question. The way to get around this problem is # "escaping" the control characters. This means simply putting a # backslash (\) before a control character so the filter will know # that you want to use it as a literal character instead of as a # control character. # Now we also quote '\n'. sub quote_special($) { my ( $line ) = @_; return $line unless defined $line; return $line if $line =~ /^#/; $line =~ s/~/\\~/g; $line =~ s/=/\\=/g; $line =~ s/\{/\\{/g; $line =~ s/\}/\\}/g; $line =~ s/#/\\#/g; $line =~ s/\\n/\\\\n/g; # NO: We would destroy our html formatting: #$line =~ s//\>/g; #foreach my $special ( ( '~', '=', '{', '}', '#' ) ) { # $line =~ s/$special/\\$special/g; #} return $line; } sub read_paras($\@) { my ( $questionfile, $paras ) = @_; open QUESTIONS, '<', $questionfile or die "Unable to open $questionfile: $!"; { # Treat things a paragraph at a time: local $/ = ""; @$paras = ; chomp @$paras; } close QUESTIONS or die "Unable to close $questionfile: $!"; foreach my $p ( @$paras ) { $p = quote_special $p; } return $paras; } # Format of the topics file: # Comments begin with a hash in the first column # \d+\s[\d.]+\s* # I.e., # question_number topic_number_with_possible_decimal_points sub read_topics($\@) { my ( $topicsfile, $topics ) = @_; open TOPICS, '<', $topicsfile or die "Unable to open $topicsfile: $!"; while ( ) { next if /^\s*#/; if ( /^\s*(\d+)\s+([\d.]+)\s*$/ ) { $topics->[ $1 ] = $2; } elsif ( /^\s*(\d+)\s+/ ) { $topics->[ $1 ] = "No-topic"; } else { next; } } close TOPICS or die "Unable to close $topicsfile: $!"; return $topics; } sub usage() { my $prog = basename $0; print < "answer"; # This field is an array of paragraphs: use constant QUESTION => "question"; # This field is an integer: use constant QNUMBER => "qnumber"; # An optional paragraph: use constant EXPLANATION => "explanation"; # essentially an enumerated type: use constant QTYPE => "qtype"; # This is a concatenation of comment paragraphs into one string: use constant COMMENT => "comment"; # An array of options for mc and mcma questions: use constant OPTIONS => "options"; # Possible values of %*_question{QTYPE()}: use constant MC => "mc"; # multiple choice, multiple answer: use constant MCMA => "mcma"; # fill in the blank: use constant FITB => "fitb"; use constant TF => "true_or_false"; # Note that we do not process blocks of options while reading: we # process them afterwards, when processing the data in the data structure. # We have finished the last question and are starting a new one # if we find a new one or a comment. # each line represents one paragraph # In quotes "literal": literal text # bare text: one non-optional paragraph # [optional 1 paragraph] # {optional multiple paragraphs} # {"#" comment} # "QUESTION NO: " \d+ # question-block # {question-block} # "Answer:" answer # ["Explanation: " explanation] sub read_command_line_args() { @ARGV == 3 or usage; my $questionfile = shift @ARGV; my $topicsfile = shift @ARGV; my $section = uc shift @ARGV; if ( $section !~ /^[A-Za-z]$/ ) { die "cannot recognise section $section\n"; } return ( $questionfile, $topicsfile, $section ); } # Copes with a limit of one
...
per question paragraph. sub process_preformatted_html(\%) { my ( $q ) = @_; foreach my $ques ( @{$q->{QUESTION()}} ) { my $numpre = () = $ques =~ m!(
)!ig;;
        next unless $numpre;
        if ( $numpre > 1 ) {
            warn "Question $q->{QNUMBER()} HAS MORE THAN ONE 
 block ",
                "skipping this one.\n";
            next;
        }
        my ( $before, $pre, $after ) = $ques =~ m!(.*)
(.*)
(.*)!is; warn "FOUND section in
 tags: '$pre'\n" if DEBUG_PRE;
        while ( $pre =~ s/^( *) /$1\ /m ) {}
        $pre =~ s/\n/\\n\n/mg;
        $pre =~ s//\>/g;
        warn "
 section after processing: '$pre'\n" if DEBUG_PRE;
        $ques = $before . "
" . $pre . "
" . $after; } } sub parse_questions_from_paras(\@\@) { my ( $paras, $questions ) = @_; my ( %question ); my %option_num = ( A => 0, B => 1, C => 2, D => 3, E => 4 ); #my $completed_question; #my $have_comment; #my $explanation; #my $is_mc; PARA: for ( my $i = 0; $i < @$paras; ++$i ) { $_ = $paras->[ $i ]; if ( m/^Answer:\s+(.*)/sm ) { my $answer = $1; if ( $answer =~ /^[A-E]$/ ) { $question{ANSWER()} = [ $option_num{$answer} ]; next PARA; } elsif ( $answer =~ /^[A-E](?:,\s*[A-E])+/ ) { $question{QTYPE()} = MCMA; $question{ANSWER()} = [ map { $option_num{$_} } split /,\s*/, $answer ]; } else { $question{ANSWER()} = [ split /\n/, $answer ]; $question{QTYPE()} = FITB; } while ( exists $paras->[ $i + 1 ] and $paras->[ $i + 1 ] !~ /^(?:Explanation|#|QUESTION)/ ) { warn "FOUND EXTRA ANSWER PARAGARPH: $paras->[ $i + 1 ]\n"; ++$i; $_ = $paras->[ $i ]; warn "EXTRA PARAGRAPH AFTER ANSWER: '$_'\n" } } elsif ( m/^QUESTION\s+NO/i ) { # Start of a new question unless ( m/^QUESTION\s+NO:\s+(\d+)/i ) { print STDERR "BAD QUESTION NUMBER: $_\n"; next; } if ( exists $question{ANSWER()} ) { # Then this is the start of a new question push @$questions, { %question }; %question = (); } $question{QNUMBER()} = $1; if ( ! defined $question{QNUMBER()} ) { print STDERR "BAD QUESTION: $_\n"; } s/^QUESTION[^\n]+\n//s; $question{QUESTION()} = [ $_ ]; while ( exists $paras->[ $i + 1 ] and $paras->[ $i + 1 ] !~ /^Answer/ ) { ++$i; $_ = $paras->[ $i ]; if ( /^A\..*^B\./ms ) { # This is an mc or mcma question, and these are options: my @opts = split /^[A-E]\. /m, $_; shift @opts unless $opts[ 0 ]; chomp @opts; if ( DEBUG_OPTIONS ) { print STDERR "Found ", scalar @opts, " options\n"; for ( my $i = 0; $i < @opts; ++$i ) { print STDERR "opt $i: $opts[ $i ]\n"; } } $question{OPTIONS()} = [ @opts ]; $question{QTYPE()} = MC; if ( @opts == 2 and grep { $_ eq "true" or $_ eq "false" } @opts ) { print STDERR "TRUE/FALSE\n" if DEBUG_TRUE_FALSE; $question{QTYPE()} = TF; } next; } else { push @{$question{QUESTION()}}, $_; } } } elsif ( m/^#/ ) { # Found a comment # Then this is the start of a new question push @$questions, { %question } if %question; %question = (); $question{COMMENT()} = $_; } elsif ( m/^Explanation:\s*(\S.*)$/s ) { $question{EXPLANATION()} = $1; } } # Put in the last question: push @$questions, { %question } if %question; } # Second parameter is the output file handle. sub print_options(\%$) { my ( $q, $ofh ) = @_; if ( not defined $q->{ANSWER()} ) { warn "DANGER! NO ANSWER FOR THIS QUESTION $q->{QNUMBER()}\n"; } else { print $ofh "{\n"; if ( $q->{QTYPE()} eq MC ) { for ( my $i = 0; $i < @{$q->{OPTIONS()}}; ++$i ) { if ( $i == $q->{ANSWER()}->[ 0 ] ) { print $ofh "\t=$q->{OPTIONS()}->[ $i ]"; print $ofh "#$q->{EXPLANATION()}" if exists $q->{EXPLANATION()}; print $ofh "\n"; } else { print $ofh "\t~$q->{OPTIONS()}->[ $i ]\n"; } } } elsif ( $q->{QTYPE()} eq MCMA ) { my $correctprefix = sprintf "~%%%.5g%%", 100 / @{$q->{ANSWER()}}; my $wrongprefix = sprintf "~%%%.5g%%", -100 / ( @{$q->{OPTIONS()}} - @{$q->{ANSWER()}} ); my $correct; for ( my $i = 0; $i < @{$q->{OPTIONS()}}; ++$i ) { print $ofh "\t"; $correct = 0; for ( my $j = 0; $j < @{$q->{ANSWER()}}; ++$j ) { if ( not defined $q->{ANSWER()}->[ $j ] ) { warn "UNDEFINED ANSWER FOR QUESTION $q->{QNUMBER()}\n"; } elsif ( $q->{ANSWER()}->[ $j ] !~ /^\d+$/ ) { warn "mcma QUESTION $q->{QNUMBER()} ", "has answer $q->{ANSWER()}->[ $j ]\n"; } elsif ( $i == $q->{ANSWER()}->[ $j ] ) { $correct = 1; } } print $ofh $correct ? $correctprefix : $wrongprefix; print $ofh $q->{OPTIONS()}->[ $i ]; print $ofh "#$q->{EXPLANATION()}" if exists $q->{EXPLANATION()}; print $ofh "\n"; } } elsif ( $q->{QTYPE()} eq FITB ) { foreach my $a ( @{$q->{ANSWER()}} ) { print $ofh "\t=$a"; print $ofh "#$q->{EXPLANATION()}" if exists $q->{EXPLANATION()}; print $ofh "\n"; } } elsif ( $q->{QTYPE()} eq TF ) { print $ofh "\t" . $q->{OPTIONS()}->[ $q->{ANSWER()}->[ 0 ] ]; print $ofh "#$q->{EXPLANATION()}" if exists $q->{EXPLANATION()}; print $ofh "\n"; } elsif ( not exists $q->{QTYPE()} ) { warn "NO QUESTION TYPE FOR QUESTION $q->{QNUMBER()}\n"; } else { warn "UNKNOWN QUESTION TYPE $q->{QTYPE()} ", "FOR QUESTION $q->{QNUMBER()}\n"; } print $ofh "}"; # print join( "\n", @{$q->{ANSWER()}} ); } } sub print_one_question(\%$$) { my ( $q, $topic, $section ) = @_; carp "UNDEFINED QUESTION\n" and return unless %$q; if ( not exists $q->{QNUMBER()} ) { carp "This question has no question number!\n"; if ( exists $q->{QUESTION()} ) { print STDERR "OFFENDING QUESTION NO: ", join( "\n", @{$q->{QUESTION()}} ) . "\n"; } print STDERR "OFFENDING QUESTION: \n"; while ( my ( $key, $value ) = each %$q ) { print "'$key' => '$value'\n"; } return; } my $output_file = GIFT_DIR . "/$topic.gift"; my $ofh; if ( $to_standard_output ) { $ofh = *STDOUT; } else { open $ofh, '>>', $output_file or die "unable to append to $output_file: $!"; } print $ofh "// $topic\n"; print $ofh "// Section $section Question " . $q->{QNUMBER()} . "\n"; # How many sets of underscores do we have in the question that are # to be filled in? If there is just one, then lets make this a # Missing Word format question. # See http://dev.perl.org/perl6/rfc/110.html for how this works: my $blanks_count = () = $q->{QUESTION()}->[ 0 ] =~ /(_{3,})/g; print $ofh "// Type: " . $q->{QTYPE()} . "\n" if defined $q->{QTYPE()}; print $ofh "// Missing Word format\n" if $blanks_count == 1; if ( exists $q->{COMMENT()} ) { $q->{COMMENT()} =~ s!^#!//!gm; print $ofh $q->{COMMENT()} . "\n"; } process_preformatted_html %$q; if ( $blanks_count > 1 ) { warn "TWO OR MORE BLANKS: $q->{QUESTION()}->[ 0 ]\n" if DEBUG_UNDERSCORES; } elsif ( $blanks_count == 1 ) { warn "ONE BLANK: $q->{QUESTION()}->[ 0 ]\n" if DEBUG_UNDERSCORES; my ( $irst_part, $second_part ) = $q->{QUESTION()}->[ 0 ] =~ /(.*?)_{3,}(.*)/s; print $ofh "[html]", $irst_part; print_options %$q, $ofh; print $ofh $second_part, "\n"; print join( "\n", @{$q->{QUESTION()}}[ 1..@{$q->{QUESTION()}} - 1 ] ), "\n" if @{$q->{QUESTION()}} > 1; } if ( $blanks_count != 1 ) { print $ofh "[html]" . join( "\n", @{$q->{QUESTION()}} ), "\n"; print_options %$q, $ofh; } #print "\n" . $q->{EXPLANATION()} if exists $q->{EXPLANATION()}; print $ofh "\n\n\n"; } sub print_questions(\@\@$) { my ( $questions, $topics, $section ) = @_; mkdir GIFT_DIR unless -d GIFT_DIR; foreach my $q ( @$questions ) { my $topic = "No-topic"; $topic = $topics->[ $q->{QNUMBER()} ] if exists $q->{QNUMBER()} && exists $topics->[ $q->{QNUMBER()} ]; print_one_question %$q, $topic, $section; } } # Want to find: # Max number of question paragraphs # average number of question paragraphs # Max number of answer paragraphs # average number of answer paragraphs # Which questions have three paragraphs or more # Which answers have more than one paragraph sub calc_question_stats(\@) { my ( $qes ) = @_; my $total = @$qes; my ( $totq, $tota, $maxq, $maxa ) = ( 0, 0, 0, 0 ); my $counter = -1; foreach my $q ( @$qes ) { ++$counter; warn "UNDEFINED QUESTION: $counter\n" and next unless defined $q; if ( not exists $q->{QNUMBER()} and not exists $q->{QUESTION()} ) { warn "NO QUESTION FOR QUESTION $counter\n"; next; } if ( exists $q->{QNUMBER()} and not exists $q->{QUESTION()} ) { warn "NO QUESTION FOR QUESTION $q->{QNUMBER()}\n"; } else { $totq += @{$q->{QUESTION()}}; $maxq = @{$q->{QUESTION()}} if @{$q->{QUESTION()}} > $maxq; } $tota += @{$q->{ANSWER()}} if defined $q->{ANSWER()}; $maxa = @{$q->{ANSWER()}} if defined $q->{ANSWER()} and @{$q->{ANSWER()}} > $maxa; print STDERR "HAS MORE THAN TWO PARAGRAPHS: Q", $q->{QNUMBER()}, "\n" if @{$q->{QUESTION()}} > 2; print STDERR "HAS MORE THAN ONE PARAGRAPH IN ANSWER: Q", $q->{QNUMBER()}, "\n" if DEBUG_MULTIPLE_ANSWERS and defined $q->{ANSWER()} and @{$q->{ANSWER()}} > 1; } my ( $avgq, $avga ) = ( $totq / $total, $tota / $total ); print STDERR "total: $total, totQ: $totq, totA: $tota, maxQ: $maxq, maxA: $maxa, avgQ: $avgq, avgA: $avga\n"; } sub main() { my ( $questionfile, $topicsfile, $section ) = read_command_line_args; my ( @topics, @paras, @questions ); read_topics $topicsfile, @topics; read_paras $questionfile, @paras; parse_questions_from_paras @paras, @questions; print_questions @questions, @topics, $section; calc_question_stats @questions if DEBUG_STATS; } main; __END__ # The grammar of the question excludes options A and B. QUESTION NO: l00 The _____ is used by the local host to determine which hosts are on the local subnet, and which hosts are on remote networks. A. DNS B. ARP C. gateway D. netmask E. routing protocol Answer: D should transform to (given the fact that this comes from subtopic 1.112.1 in file testprawn-topics-section-a.txt): // 1.112.1 // Section A question 100 // The grammar of the question excludes options A and B. [HTML]The { ~DNS ~ARP ~gateway =netmask ~routing protocol } is used by the local host to determine which hosts are on the local subnet, and which hosts are on remote networks. Second EXAMPLE: # THIS IS RATHER OBSCURE AND OBSOLETE: QUESTION NO: 105 What is the command to check the syntax of your /etc/inetd.conf? Answer: tcpdchk should transform to // 1.113.1 // Section B question 105 [html]What is the command to check the syntax of your /etc/inetd.conf? {=tcpdchk} # THIS IS EASY IF YOU USE THE ps unix form of ps commands, but # discriminates against those who use the bsd form. QUESTION NO: 107 What command was typed in to produce the output shown below. The entries shown are the full output of the command, less the actual command.

Type the command and the options to reproduce similar output.

USER  PID %CPU %MEM  VSZ  RSS TTY   STAT START TIME COMMAND
root  394  0.0  0.0 1200  444 ttyl  S    01:05 0:00 /sbin/getty 38400 tty1
root  396  0.0  0.0 1200  444 tty3  S    01:05 0:00 /sbin/getty 38400 tty3
root  397  0.0  0.0 1200  444 tty4  S    0l:05 0:00 /sbin/getty 38400 tty4
root  398  0.0  0.0 1200  444 tty5  S    0l:05 0:00 /sbin/getty 38400 tty5
root  399  0.0  0.0 l200  444 tty6  S    0l:05 0:00 /sbin/getty 38400 tty6
root  423  0.0  0.0 1200  444 tty2  S    01:06 0:00 /sbin/getty 38400 tty2
root  426  0.2  0.3 2880 1964 pts/0 S    0l:07 0:00 -bash
Answer: ps -au should translate to: // 1.114.3 // Section B question 107 // THIS IS EASY IF YOU USE THE ps unix form of ps commands, but // discriminates against those who use the bsd form. [HTML]What command was typed in to produce the output shown below. The entries shown are the full output of the command, less the actual command.

Type the command and the options to reproduce similar output.

USER  PID %CPU %MEM  VSZ  RSS TTY   STAT START TIME COMMAND
root  394  0.0  0.0 1200  444 ttyl  S    01:05 0:00 /sbin/getty 38400 tty1
root  396  0.0  0.0 1200  444 tty3  S    01:05 0:00 /sbin/getty 38400 tty3
root  397  0.0  0.0 1200  444 tty4  S    0l:05 0:00 /sbin/getty 38400 tty4
root  398  0.0  0.0 1200  444 tty5  S    0l:05 0:00 /sbin/getty 38400 tty5
root  399  0.0  0.0 l200  444 tty6  S    0l:05 0:00 /sbin/getty 38400 tty6
root  423  0.0  0.0 1200  444 tty2  S    01:06 0:00 /sbin/getty 38400 tty2
root  426  0.2  0.3 2880 1964 pts/0 S    0l:07 0:00 -bash
{=ps -au} And this: # CAN tar START WITH A NON-COMMAND? E.G., tar zcf file.tar.gz # Yes. you can do tar zvft /home/nicku/bizcard.tar.gz # They can be permuted in any way. # If count bsd, unix and gnu options, making verbose optional, we have # a vast number of permutations: # 4! * 3 # But this does not include choosing = or ' ' between gnu options and # filename parameter,... QUESTION NO: 74 You wish to archive and compress all the files in your home directory starting with the word projects into a file called myprojects.tar.gz. You're currently in your home directory. Type in the command that would do this. Answer: tar -czr myprojecte.tar.gz projects* tar czf myprojects.tar.gz projects* tar -zcf myprojects.tar.gz projects* tar zcf myprogects.tar.gz projects* tar zcvf myprogects.tar.gz projects* tar cvzf myprogects.tar.gz projects* tar czvf myprogects.tar.gz projects* should translate to: // 1.111.5 // Section B question 74 // CAN tar START WITH A NON-COMMAND? E.G., tar zcf file.tar.gz // Yes. you can do tar zvft /home/nicku/bizcard.tar.gz // They can be permuted in any way. // If count bsd, unix and gnu options, making verbose optional, we have // a vast number of permutations: // 4! * 3 // But this does not include choosing = or ' ' between gnu options and // filename parameter,... [html]You wish to archive and compress all the files in your home directory starting with the word projects into a file called myprojects.tar.gz. You're currently in your home directory. Type in the command that would do this. { =tar -czr myprojecte.tar.gz projects* =tar czf myprojects.tar.gz projects* =tar -zcf myprojects.tar.gz projects* =tar zcf myprogects.tar.gz projects* =tar zcvf myprogects.tar.gz projects* =tar cvzf myprogects.tar.gz projects* =tar czvf myprogects.tar.gz projects* } And: QUESTION NO: 174 With insmod, the paths listed in /etc/modules.conf override the paths defined in environment variable MODPATH. A. true B. false Answer: A Explanation: If the module file name is given without directories or extension, insmod will search for the module in some common default directories. The environment variable MODPATH can be used to override this default. If a module configuration file such as /etc/modules.conf exists, it will override the paths defined in MODPATH. should translate to: // 1.105.1 // Section A question 174 [html]With insmod, the paths listed in /etc/modules.conf override the paths defined in environment variable MODPATH.{TRUE#If the module file name is given without directories or extension, insmod will search for the module in some common default directories. The environment variable MODPATH can be used to override this default. If a module configuration file such as /etc/modules.conf exists, it will override the paths defined in MODPATH.}