#! /usr/local/bin/perl -w use strict; use warnings; use Digest::SHA qw(sha256); use Getopt::Std; my %opts; getopts("c:N:n:", \%opts); my @preamble = (); # Preamble lines my @postamble = (); # Postamble lines my @questions = (); # Array of question hashrefs # Each question hash has keys: # {question}: arrayref with question lines # {answers}: arrayref of arrayrefs with answer lines, correct answer first # {varid}: idnex in qvars array my @qvars = (); # Array of question variants # Each entry is an arrayref of indexes in the questions array my $commonseed = $opts{N} // ""; my $seed = $opts{n} // ""; ### READ INPUT FILE if ( 1 ) { ## Keep following variables local my $in_preamble = 1; my $in_postamble = 0; my $in_qvar = 0; my $curqn; my $listref = \@preamble; my $varid; LINELOOP: while ($_ = <>) { if ( $_ =~ m/^\\def\\seedval\{.*\}$/ ) { $_ = "\\def\\seedval\{$seed\}\n"; } if ( $_ =~ m/\\begin\{qcm\}/ ) { die "wrong placement" unless $in_preamble; die "bad format" unless $_ eq "\\begin\{qcm\}\n"; $in_preamble = 0; $listref = undef; next LINELOOP; } elsif ( $_ =~ m/\\end\{qcm\}/ ) { die "wrong placement" if $in_preamble; die "bad format" unless $_ eq "\\end\{qcm\}\n"; $in_postamble = 0; $listref = \@postamble; next LINELOOP; } elsif ( $_ =~ m/\\begin\{qvar\}/ ) { die "wrong placement" if $in_preamble || $in_postamble || $in_qvar || defined($curqn); die "bad format" unless $_ eq "\\begin\{qvar\}\n"; $in_qvar = 1; push @qvars, []; next LINELOOP; } elsif ( $_ =~ m/\\end\{qvar\}/ ) { die "wrong placement" if $in_preamble || $in_postamble || (!$in_qvar) || defined($curqn); die "bad format" unless $_ eq "\\end\{qvar\}\n"; $in_qvar = 0; next LINELOOP; } elsif ( $_ =~ m/\\begin\{question\}/ ) { die "wrong placement" if $in_preamble || $in_postamble || defined($curqn); die "bad format" unless $_ eq "\\begin\{question\}\n"; my %qn = (); push @qvars, [] unless $in_qvar; $qn{varid} = $#qvars; $qn{question} = []; $qn{answers} = []; push @questions, \%qn; push @{$qvars[$#qvars]}, $#questions; $listref = $qn{question}; $curqn = \%qn; next LINELOOP; } elsif ( $_ =~ m/\\end\{question\}/ ) { die "wrong placement" if $in_preamble || $in_postamble || !defined($curqn); die "bad format" unless $_ eq "\\end\{question\}\n"; $listref = undef; $curqn = undef; next LINELOOP; } elsif ( $_ =~ m/\\(right)?answer/ && $_ !~ /\\newcommand/ && $_ !~ /\\let\\rightanswer/ ) { die "wrong placement" if $in_preamble || $in_postamble || !defined($curqn); die "bad format" unless $_ eq "\\answer\n" || $_ eq "\\rightanswer\n"; die "this is impossible" unless ref($curqn) eq "HASH" && defined $curqn->{answers}; die "right answer should come first" unless ($_ eq "\\rightanswer\n") == (scalar(@{$curqn->{answers}}) == 0); my @ans = (); push @{$curqn->{answers}}, \@ans; $listref = \@ans; ## no next LINELOOP here: include \answer in answer itself! } die "this is impossible" if $in_preamble && $in_postamble; die "this is impossible" if $in_preamble && ($listref ne \@preamble); die "this is impossible" if $in_postamble && ($listref ne \@postamble); push @{$listref}, $_ if defined($listref); } } ### RANDOMIZE my $nbqn = $opts{c} // int((scalar(@qvars)+1)/2); my @questab = (); my @quesanstab = (); if ( 1 ) { ## Keep following variables local my @hashlist; for ( my $u=0 ; $u[$kv]; push @hashlist, sha256("${commonseed}\n${seed}\nQ\n${i}\n"); } my $kv = (sort { $hashlist[$a] cmp $hashlist[$b] } (0..(scalar(@{$qvars[$u]})-1)))[0]; my $i = $qvars[$u]->[$kv]; die "this is impossible" unless $questions[$i]->{varid} == $u; push @questab, $i; } for ( my $i=0 ; $i{answers}; for ( my $j=0 ; $j{question}} ) { print $l; } for ( my $kj=0 ; $kj[$kj]; my $a = $qn->{answers}->[$j]; foreach my $l ( @{$a} ) { print $l; } push @correct, sprintf("%d%s", $k+1, chr(ord("A")+$kj)) if $j==0; } print "\\end\{question\}\n\n"; } print "\\end\{qcm\}\n\n"; printf "\%\% === %s ===\n", join(" ", @correct); printf "\\ifcorrige\\bigskip\\noindent\\textbf{Corrigé.} %s\\fi\n", join(" ", @correct); foreach my $l ( @postamble ) { print $l; }