#!/usr/bin/perl =head1 NAME coursedb-make - generate the annual directories and associated files for lecture-course materials based on data from coursedb.txt =head1 USAGE coursedb-make [options] coursedb.txt [course ...] coursedb-make [options] -a coursedb.txt =head1 SUMMARY Reads the coursedb.txt file and writes to the current working directory the output files uconfig.txt, chown.sh, index-b.html, lecturers-b.html, $class-b.html. It also produces a subdirectory for each course listed, containing an index-b.html and a Makefile, or for all courses if option -a is used instead. =cut use FindBin qw($RealBin); # find directory where this file is located ... use lib '/anfs/www/tools/share/ucampas/perl-PlexTree', $RealBin; use bytes; use strict; use PlexTree; use CourseDB; sub usage() { print STDERR "usage: coursedb-make [options] coursedb.txt [courses ...]\n\n"; print STDERR "For more documentation run: "; print STDERR "perldoc $0\n\n"; exit 1; } my @generated_html = (); # list of files that ucampas needs to postprocess # write a HTML page (open file, add headers, etc.) sub write_htmlpage { my ($fn, $title, $body, %param) = @_; my $f; open($f, '>', $fn) or die("$fn:$!\n"); print $f '' . "\n"; print $f ("\n\n\n") unless $param{'edit'}; print $f ''.Misc::utf8_to_sgml($title)."\n\n"; print $f '

'.Misc::utf8_to_sgml($param{'h1'} || $title)."

\n\n"; if ($param{'tabs'}) { print $f "\n"; print $f "\n\n"; } print $f @{$body}; close $f or die("$fn:$!\n"); push @generated_html, $fn; return; } # write a plain text file sub write_textfile { my ($fn, @body) = @_; my $f; open($f, '>', $fn) or die("$fn:$!\n"); print $f @body; close $f or die("$fn:$!\n"); return; } my $alldirs; my $verbose; # parse command-line options while ($ARGV[0] =~ /^-/) { my $option = shift @ARGV; if ($option eq '-a') { $alldirs = 1; } elsif ($option eq '-v') { $verbose = 1; } elsif ($option eq '--') { last; } else { usage(); } } usage() unless @ARGV; my $fndb = shift @ARGV; my @makedirs = map { s/\/$//; $_; } @ARGV; my $db = CourseDB::load($fndb); my $myear=$db->get('myear'); unless ($myear > 1950 && $myear < 2020) { die("Please specify the 4-digit calendar year of the Michaelmas term in" . "attribute mterm, e.g. using mterm='2007'\n"); } my $longyear = $db->year('4-2'); my $shortyear = $db->year('22'); my $shortlastyear = $db->year('22', -1); my @terms=('M','L','E'); my %termname = ( M => 'Michaelmas', L => 'Lent', E => 'Easter', V => 'Long vacation'); my %termsort = ( M => 1, L => 2, E => 3, V => 4); # set up main uconfig.txt file my $u = PlexTree->new; $u->setatt(navtop => 1); $u->setatt(section => "Course pages $longyear"); # index pages for each class of student foreach my $class ($db->classes) { next unless $class->param('index') || 1; print $class->title . " (" . $class->code . ")\n" if $verbose; my @body = (); if (defined $class->param('timetable-url')) { push @body, '

Please check the official timetables for timetable details.\n"; } foreach my $term (@terms) { push @body, "\n

$termname{$term} term

\n\n"; push @body, "\n"; } write_htmlpage($class->code . "-b.html", $class->title, \@body); $u->append->set(text($class->code . '.html')) ->setatt(navtitle => $class->title); } # index of all lecturers and their courses my @body = (); if (defined $db->get('timetable-url')) { push @body, '

Please check the official timetables for timetable details.\n"; } foreach my $lecturer ($db->lecturers) { my @courses = $lecturer->teaches; next unless @courses; print $lecturer->fullname . " <" . $lecturer->code . ">\n" if $verbose; push @body, "

"; push @body, $lecturer->html; push @body, "

\n\n"; } write_htmlpage("lecturers-b.html", "Index of courses by lecturer", \@body); $u->append->set(text "lecturers.html")->setatt(navtitle => 'Lecturer index'); # generate chown script my %teaching; # should this CSRID be in the Unix group "teaching"? my @chown = (); push @chown, "function asroot() { cl-onserver --server toton --identity ~/.ssh/id_www_chown \"\$\@\" ; }\n"; foreach my $course ($db->courses) { next unless $course->needs_directory; my @lecturers = ($course->proprietors, $course->contributors); foreach my $lecturer (@lecturers) { $teaching{$lecturer->code} = 1; } my ($lecturer1, $lecturer2, @others) = grep { defined getpwnam($_->code) } @lecturers; my $uid; my $gid = 'teaching'; if (defined $lecturer1) { $uid = $lecturer1->code; } else { $uid = $db->cd('teaching-admin')->getl(0); warn("No lecturer of course '" . $course->code . "' has a user account yet: " . join(', ', map { $_->code } @lecturers) . "\n") if @lecturers; } push @chown, "asroot chown -h -R $uid:$gid '/anfs/www/VH-cl/html/teaching/$shortyear/" . $course->code . "/'\n"; } write_textfile('chown.sh', @chown); # generate membership of Unix group "teaching" and mailing list my $admin = $db->cd(text 'teaching-admin'); if (defined $admin) { foreach my $administrator ($admin->list) { $teaching{$administrator->str_text} = 1 } } delete $teaching{undef}; write_textfile('teaching-group.txt', map { "$_\n" } sort({$a cmp $b} keys %teaching)); # generate mailing list my @mail; push @mail, Misc::mail_header('To', ',', sort { $a cmp $b } map { $_->code } grep { $_->teaches } $db->lecturers ); push @mail, Misc::mail_header('cc', ',', map { $_->str_text } ($admin->list)) if defined $admin; write_textfile('teaching-mail.txt', @mail); # add all course pages to uconfig.txt my $groups = $db->cd('groups'); if (defined $groups) { # generate seperate navigation menus per year group use sort 'stable'; for my $group ($groups->list) { my $uc = $u->append->set($group->str_text); $uc->setatt(stoprecursion => 1); $uc->setatt(navtop => 1); $uc->setatt(nosub => 1); $uc->setatt(nopage => 1); $uc->setatt(breadcrumbskip => 1); $uc->setatt(invisible => 1); $uc->setatt(title => $group->get('title')); my @courses = grep { $_->group->nid eq $group->nid } $db->courses; @courses = grep { $_->needs_directory } @courses; @courses = sort { $termsort{$a->get('term')} cmp $termsort{$b->get('term')} } @courses; foreach my $course (@courses) { $uc->append(text($course->code)); } } } else { # old-style ungrouped list of all courses my $uc = $u->append->set('courses'); $uc->setatt(stoprecursion => 1); $uc->setatt(navtop => 1); $uc->setatt(nosub => 1); $uc->setatt(nopage => 1); $uc->setatt(breadcrumbskip => 1); $uc->setatt(title => 'All courses'); foreach my $course ($db->courses) { next unless $course->needs_directory; $uc->append(text($course->code)); } } # course directories @makedirs = map { $db->course($_) or die("Unknown course code: $_"); } @makedirs; @makedirs = $db->courses if $alldirs; foreach my $course (@makedirs) { next unless $course->needs_directory; print $course->title . "\n" if $verbose; my $cdir = $course->code; if (!mkdir $cdir and !$!{EEXIST}) { warn("Cannot create course directory '$cdir': $!, skipping ...\n"); next; } if (!-w $cdir) { warn("Cannot write course directory '$cdir', skipping ...\n"); next; } # prepare uconfig.txt file for the course directory my $u2 = PlexTree->new; $u2->addkey('tabtop' => '1'); $u2->addkey('navstop' => '1'); $u2->addkey('navtitle' => text($course->title)); my ($lecturer) = $course->proprietors; if (defined $lecturer) { my $webmaster = $lecturer->cd('webmaster'); if ($webmaster) { $u2->addkey('webmaster')->copyfrom($webmaster) unless $webmaster->isempty; } else { $u2->addkey('webmaster')->addkey('name', text $lecturer->fullname) if ($lecturer->fullname); $u2->addkey('webmaster')->addkey('url', text $lecturer->url) if ($lecturer->url); } } # create default page body my @data = ('

'); my @proprietors = $course->proprietors; if (@proprietors) { push @data, 'Principal lecturer'. (@proprietors > 1 ? 's' : '').': '; push @data, join(', ', map {$_->html} @proprietors); push @data, "
\n"; } my @contributors = $course->contributors; if (@contributors) { push @data, 'Additional lecturer'. (@contributors > 1 ? 's' : '').': '; push @data, join(', ', map {$_->html} @contributors); push @data, "
\n"; } push @data, 'Taken by: ' . join(', ', map { $_->html('../') } $course->classes); push @data, "
\n"; push @data, 'Code: ' . $course->code . "
\n" if $course->param('showcode'); my $hours = $course->cd('hours'); if (defined $hours) { push @data, 'Hours:'; push @data, ' ' . $hours->str_text if $hours->str_text; push @data, ' (' . $hours->getl(0) . ')' if $hours->listlen; push @data, "
\n"; } my $prerequisites = $course->cd('prerequisites'); if (defined $prerequisites) { push @data, 'Prerequisites: ' . $prerequisites->str_text; push @data, "
\n"; } # syllabus link my $syllabustype = $course->param('syllabus'); if ($syllabustype eq 'latexhtmllink') { push @data, "Syllabus
\n"; } elsif ($syllabustype eq 'localpdflink') { push @data, "Syllabus
\n"; } # past exam questions link if ($course->param('triposexam')) { my @examlinks = $course->examlinks; if (@examlinks == 2) { push @data, "Past exam questions
\n"; } elsif (@examlinks >= 2) { my @l; while (@examlinks) { my $title = shift @examlinks; my $url = shift @examlinks; push @l, "$title"; } push @data, "Past exam questions: " . join(', ', @l) . "
\n"; } else { print $course->code . ": no past exam questions!\n"; } } # information for supervisors link if ($course->param('supervisions')) { push @data, "Information for supervisors ". "(contact lecturer for access permission)
\n"; } my $mainpage = 'materials'; # materials | syllabus $mainpage = 'syllabus' if $syllabustype =~ /include$/; # syllabus page if ($syllabustype eq 'htmlinclude' || $syllabustype eq 'latexhtmlinclude') { my $fn="syllabus"; my @body = (); my $title = $course->title . ' – Syllabus'; if ($mainpage eq 'syllabus') { push @body, @data; $fn="index"; $u2->addkey('tabtopinclude' => text 'Syllabus'); $title = $course->title; } else { $u2->append((text "$fn.html")->setatt('navtitle' => 'Syllabus')); } if ($syllabustype eq 'htmlinclude') { push @body, ("\n\n"); push @body, "\n

'../syllabi/" . $course->code . ".html'(find(*h2, op=cutbefore))
\n"; } elsif ($syllabustype eq 'latexhtmlinclude') { push @body, ("\n\n"); push @body, "\n
'../CST/" . $course->code . ".html'(find(*h1, op=cutuntil),find(*p(id=lecturer), op=cutuntil),find(*hr, op=cutfrom)),dropdiv=1
\n"; } push @body, ("\n\n") if $mainpage eq 'syllabus'; write_htmlpage("$cdir/$fn-b.html", $title, \@body, tabs => 1, h1 => $course->title); } # course materials page my $fn="materials"; my @body = (); my $title = $course->title . ' – Course materials'; if ($mainpage eq 'materials') { push @body, @data; $fn="index"; $u2->addkey('tabtopinclude' => text 'Course materials'); $title = $course->title; } else { $u2->append((text "$fn.html")->setatt('navtitle' => 'Course materials')); } # placeholder push @body, "\n\n

If you are reading this then no extra material has yet been\n". "placed on the Web for this course."; push @body, "\n
Last year’s course materials are still available." if -d "../$shortlastyear/" . $course->code . "/"; push @body, "
\n"; # write out the course materials page write_htmlpage("$cdir/$fn-b.html", $title, \@body, tabs => 1, h1 => $course->title, edit => 1); # assessment page if ($course->param('assessmentpage')) { my @body = (); # placeholder push @body, "\n\n

The course lecturer will provide here shortly information about assessed work for this course, such as exercise/essay/project deadlines, exam dates, etc."; write_htmlpage("$cdir/assessment-b.html", $course->title . ' – Assessment', \@body, tabs => 1, h1 => $course->title, edit => 1); $u2->append((text 'assessment.html')->setatt('navtitle' => 'Assessment')); } # create restricted supervisors subdirectory if ($course->param('supervisions')) { # placeholder body my @body = ( "\n\n

If you are reading this then no extra material has yet been\n". "placed on the Web for this course."); my $lastfn = "../$shortlastyear/" . $course->code . "/supervisors/"; push @body, "\n
Last year’s course materials are still available." if -d $lastfn; push @body, "
\n"; `mkdir -p "$cdir/supervisors"`; write_htmlpage("$cdir/supervisors/index-b.html", $course->title . ' – Information for supervisors', \@body, tabs => 1, h1 => $course->title, edit => 1); # uconfig.txt $u2->append((text 'supervisors')->setatt('navtitle' => 'Information for supervisors')); # supervisors/.htaccess my @htaccess = ( "# Access control settings for the supervisors subdirectory\n", "# Documentation: http://www.cl.cam.ac.uk/local/web/htaccess.html\n", "Order Allow,Deny\n", "Satisfy Any\n", "# allow access by Lab members and DoS:\n", "Require group all-cl-users undergrad-directors-of-studies\n", "# allow access by other supervisors:\n", "# (uncomment line below, then edit list of Raven IDs of your supervisors):\n", "#Require user abc12 def34 gh567\n", "# allow access by students:\n", "# (uncomment line below after the end of lectures and supervisions):\n", "#Require group ". join(' ', map { "students-" . $_->code . "-$shortyear" } $course->classes) ."\n" ); write_textfile("$cdir/supervisors/.htaccess", @htaccess); } # create makefile write_textfile("$cdir/Makefile", "%.html: %-b.html\n\t/anfs/www/tools/bin/ucampas \$*\n\n", "all:\n\t/anfs/www/tools/bin/ucampas -r\n"); # output uconfig.txt write_textfile("$cdir/uconfig.txt", $u2->print(oneline => 3, implicitlist => 1)) unless $u2->isempty; } # write uconfig.txt file write_textfile('uconfig.txt', $u->print(oneline => 3, implicitlist => 1)); push @generated_html, 'index'; # because we may have changed uconfig.txt system '/anfs/www/tools/bin/ucampas', @generated_html;