+a/account.en
+a/account.en.title
+a/index.en
+a/index.en.title
+app.psgi
Changes
-gruntmaster-genallpages
-gruntmaster-genpage
-lib/Gruntmaster/Page/Common.pm
-lib/Gruntmaster/Page/Ct/Entry.pm
-lib/Gruntmaster/Page/Ct.pm
-lib/Gruntmaster/Page/Index.pm
-lib/Gruntmaster/Page/Log/Entry.pm
+css/custom.css
+css/orig/cerulean.css
+css/orig/cosmo.css
+css/orig/cyborg.css
+css/orig/slate.css
+css/themes/cerulean.css
+css/themes/cosmo.css
+css/themes/cyborg.css
+css/themes/slate.css
+js/00-zepto.js
+js/10-bootstrap-dropdown.js
+js/90-custom.js
+js/90-form.js
+lib/Gruntmaster/Page/Base.pm
+lib/Gruntmaster/Page/CSS.pm
+lib/Gruntmaster/Page/Generic.pm
+lib/Gruntmaster/Page/JS.pm
lib/Gruntmaster/Page/Log.pm
+lib/Gruntmaster/Page/Passwd.pm
lib/Gruntmaster/Page/Pb/Entry.pm
-lib/Gruntmaster/Page/Pb.pm
-lib/Gruntmaster/Page.pm
+lib/Gruntmaster/Page/Register.pm
+lib/Gruntmaster/Page/Src.pm
+lib/Gruntmaster/Page/St.pm
lib/Gruntmaster/Page/Submit.pm
+lib/Plack/App/Gruntmaster.pm
+log.conf
Makefile.PL
MANIFEST
README
+run
+static/gm.css
t/Gruntmaster-Page.t
+t/mech.t
+tmpl/ct.en
+tmpl/ct_entry.en
+tmpl/footer.en
+tmpl/header.en
+tmpl/log.en
+tmpl/log_entry.en
+tmpl/pb.en
+tmpl/pb_entry.en
+tmpl/st.en
+tmpl/us.en
+tmpl/us_entry.en
+
use ExtUtils::MakeMaker;
WriteMakefile(
- NAME => 'Gruntmaster::Page',
- VERSION_FROM => 'lib/Gruntmaster/Page.pm',
- ABSTRACT_FROM => 'lib/Gruntmaster/Page.pm',
- AUTHOR => 'Marius Gavrilescu <marius@ieval.ro>',
- MIN_PERL_VERSION => '5.14.0',
- LICENSE => 'perl',
- SIGN => 1,
- PREREQ_PM => {
- qw/Fcntl 0
- File::Basename 0
- IO::File 0
- POSIX 0
+ NAME => 'Gruntmaster::Page',
+ VERSION_FROM => 'lib/Plack/App/Gruntmaster.pm',
+ ABSTRACT_FROM => 'lib/Plack/App/Gruntmaster.pm',
+ AUTHOR => 'Marius Gavrilescu <marius@ieval.ro>',
+ MIN_PERL_VERSION => '5.14.0',
+ LICENSE => 'perl',
+ SIGN => 1,
+ BUILD_REQUIRES => {
+ qw/Test::More 0
+ Test::WWW::Mechanize::PSGI 0/,
+ },
+ PREREQ_PM => {
+ qw/Carp 0
+ Digest::SHA 0
+ List::Util 0
+ POSIX 0
+ constant 0
+ feature 0
+ parent 0
+ strict 0
+ warnings 0
- File::Slurp 0
- HTML::Template::Compiled 0
- IO::Compress::Gzip 0
- YAML::Any 0/,
- },
- META_MERGE => {
- dynamic_config => 0,
- }
-);
+ Apache2::Authen::Passphrase 0
+ Apache2::AuthzCaps 0
+ CSS::Minifier::XS 0
+ File::Slurp 0
+ Gruntmaster::Data 0
+ HTML::Template::Compiled 0
+ HTTP::Negotiate 0
+ JavaScript::Minifier::XS 0
+ JSON 0
+ Log::Log4perl 0
+ LWP::UserAgent 0
+ Plack::Builder 0
+ Plack::Request 0/,
+ },
+ META_MERGE => {
+ dynamic_config => 0,
+ }
+)
#sponsors a.logo{
margin-bottom: 1em;
}
+
+.table-fixed{
+ table-layout: fixed;
+}
use 5.014000;
use strict;
use warnings;
+our $VERSION = '0.001';
use File::Slurp qw/read_file/;
use HTML::Template::Compiled;
use Gruntmaster::Data ();
use List::Util ();
use LWP::UserAgent;
+use Plack::Request ();
+use feature ();
my $ua = LWP::UserAgent->new;
my %templates;
-sub import {
- my $caller = caller;
- my ($self, $name, $title) = @_;
+use Carp qw/cluck/;
+
+sub import_to {
+ my ($self, $caller, $name, $title) = @_;
+ strict->import;
+ feature->import(':5.14');
+ warnings->import;
+ File::Slurp->export_to_level(1, $caller, qw/read_file/);
Gruntmaster::Data->export_to_level(1, $caller);
List::Util->export_to_level(1, $caller, qw/sum/);
no strict 'refs';
+ *{"${caller}::ISA"} = [__PACKAGE__];
+ *{"${caller}::VERSION"} = $VERSION;
*{"${caller}::strftime"} = \&POSIX::strftime;
*{"${caller}::debug"} = sub {
local $Log::Log4perl::caller_depth = $Log::Log4perl::caller_depth + 1;
}
}
+sub import {
+ return unless $_[0] eq __PACKAGE__;
+ splice @_, 1, 0, scalar caller;
+ goto &import_to
+}
+
##################################################
sub generate{
my ($self, $lang, @args) = @_;
- my $htc = HTML::Template::Compiled->new(scalarref => \$templates{$self}{$lang}, default_escape => 'HTML',);
+ my $htc = HTML::Template::Compiled->new(scalarref => \$templates{$self}{$lang}, default_escape => 'HTML', use_perl => 1);
$self->_generate($htc, $lang, @args);
my $out = $htc->output;
utf8::downgrade($out);
sub _generate {}
-sub vary {}
+sub vary { '' }
sub max_age { 60 }
sub variants {
+ return [] unless exists $templates{$_[0]};
[ map { [ $_, 1, 'text/html', undef, undef, $_, undef ]} keys $templates{$_[0]} ]
}
package Gruntmaster::Page::CSS;
-use 5.014000;
-use strict;
-use warnings;
use Gruntmaster::Page::Base;
-our @ISA = qw/Gruntmaster::Page::Base/;
-our $VERSION = '0.001';
-
-use File::Slurp qw/read_file/;
use CSS::Minifier::XS qw/minify/;
sub generate{
[200, ['Content-Type' => 'text/css', 'Cache-Control' => 'public, max-age=604800', 'X-Forever' => 1], [minify $css] ]
}
-sub variants{ [[css => 1, 'text/css', undef, undef, undef, undef]] }
-
1
+++ /dev/null
-package Gruntmaster::Page::Ct;
-
-use 5.014000;
-use strict;
-use warnings;
-use Gruntmaster::Page::Base ct => 'Contests';
-our @ISA = qw/Gruntmaster::Page::Base/;
-our $VERSION = '0.001';
-
-sub _generate{
- my ($self, $htc, $lang, $env) = @_;
- debug $env => "language is '$lang'";
-
- my (@running, @pending, @finished);
- for (sort {contest_start $a <=> contest_start $b}contests) {
- my $ct = { id => $_,
- name => contest_name,
- start => strftime ('%c', localtime contest_start),
- end => strftime ('%c', localtime contest_end),
- owner => contest_owner };
-
- my $time = time;
- push @pending, $ct if time < contest_start;
- push @running, $ct if time >= contest_start && time < contest_end;
- push @finished, $ct if time > contest_end;
- }
-
- $htc->param(running => \@running) if @running;
- $htc->param(pending => \@pending) if @pending;
- $htc->param(finished => \@finished) if @finished;
-}
-
-1
+++ /dev/null
-package Gruntmaster::Page::Ct::Entry;
-
-use 5.014000;
-use strict;
-use warnings;
-use Gruntmaster::Page::Base ct_entry => '<tmpl_var name>';
-our @ISA = qw/Gruntmaster::Page::Base/;
-our $VERSION = '0.001';
-
-sub _generate{
- my ($self, $htc, $lang, $env, $id) = @_;
- debug $env => "language is '$lang' and id is '$id'";
-
- $htc->param(id => $id);
- $htc->param(name => contest_name $id);
- $htc->param(start => strftime '%c', localtime contest_start $id);
- $htc->param(end => strftime '%c', localtime contest_end $id);
- $htc->param(started => time >= contest_start $id);
-}
-
-1
--- /dev/null
+package Gruntmaster::Page::Generic;
+
+use 5.014000;
+use strict;
+use warnings;
+our $VERSION = '0.001';
+
+use Gruntmaster::Data;
+use Gruntmaster::Page::Base;
+use JSON qw/encode_json decode_json/;
+
+sub hgetall {
+ my $hash = shift;
+ my $cp = $Gruntmaster::Data::contest ? "contest.$Gruntmaster::Data::contest." : '';
+ map { { id => $_, HGETALL "$cp$hash.$_" } } SMEMBERS "$cp$hash"
+}
+
+sub putsym {
+ my ($key, $value) = @_;
+ no strict 'refs';
+ *{"$key"} = $value;
+}
+
+sub makepkg {
+ my ($pkg, $id, $title) = @_;
+ my $fn = $pkg =~ s,::,/,gr;
+ return if $INC{"$fn.pm"};
+ $INC{"$fn.pm"} = 1;
+ Gruntmaster::Page::Base->import_to($pkg, $id, $title);
+ 1
+}
+
+sub list {
+ my ($thing, $lang, $env, $ct) = @_;
+ my %thing = %$thing;
+ undef $ct unless $thing{contest};
+ debug $env => "Contest is $ct";
+ local $Gruntmaster::Data::contest = $ct if $ct;
+ my @thing = hgetall $thing{hash};
+ @thing = map { $thing{mangle}->(); $_ } @thing if exists $thing{mangle};
+ @thing = grep { $thing{choose}->() } @thing if exists $thing{choose};
+ @thing = sort { $thing{sortby}->() } @thing if exists $thing{sortby};
+ my %params;
+ $thing{group} //= sub { $thing{id} };
+ for (@thing) {
+ my $group = $thing{group}->();
+ $params{$group} //= [];
+ push $params{$group}, $_
+ }
+ wantarray ? %params : \%params
+}
+
+sub entry {
+ my ($thing, $lang, $env, $id, $ct) = @_;
+ my %thing = %$thing;
+ ($id, $ct) = ($ct, $id) if $thing{contest};
+ local $Gruntmaster::Data::contest = $ct if $ct;
+ debug $env => "Hash is $thing{hash} and id is $id";
+ my %params = HGETALL "$thing{hash}.$id";
+ $thing{mangle}->(local $_ = \%params) if exists $thing{mangle};
+ wantarray ? %params : \%params
+}
+
+sub headers ($) { ['Content-Type' => 'application/json', 'Cache-Control' => 'max-age=' . $_[0]->max_age] }
+
+sub create_thing {
+ my %thing = @_;
+ my $ucid = ucfirst $thing{id};
+ my $pkg = "Gruntmaster::Page::$ucid";
+
+ putsym "${pkg}::_generate", sub { $_[1]->param(list \%thing, @_[2..$#_]) } if makepkg $pkg, @thing{qw/id title/};
+ putsym "${pkg}::Entry::_generate", sub { $_[1]->param(entry \%thing, @_[2..$#_]) } if makepkg "${pkg}::Entry", "$thing{id}_entry", '<tmpl_var name>';
+ putsym "${pkg}::Read::generate", sub { [200, headers shift, [encode_json list \%thing, @_]] } if makepkg "${pkg}::Read";
+ putsym "${pkg}::Entry::Read::generate", sub { [200, headers shift, [encode_json entry \%thing, @_]] } if makepkg "${pkg}::Entry::Read";
+}
+
+sub params;
+sub contest;
+sub choose (&);
+sub sortby (&);
+sub group (&);
+sub mangle (&);
+
+sub thing (&){
+ my %thing;
+ no strict 'refs';
+ local *{"params"} = sub { @thing{qw/id hash title/} = @_ };
+ local *{"choose"} = sub { $thing{choose} = shift };
+ local *{"sortby"} = sub { $thing{sortby} = shift };
+ local *{"mangle"} = sub { $thing{mangle} = shift };
+ local *{"group"} = sub { $thing{group} = shift };
+ local *{"contest"} = sub { $thing{contest} = 1 };
+ use strict 'refs';
+
+ shift->();
+ create_thing %thing
+}
+
+##################################################
+
+thing {
+ params qw/us user Users/;
+ choose { $_->{name} =~ /\w/ };
+ sortby { lc $a->{name} cmp lc $b->{name} };
+};
+
+thing {
+ params qw/pb problem Problems/;
+ contest;
+ sortby { $a->{name} cmp $b->{name} };
+ group { $_->{level} };
+ mangle { $_->{owner_name} = do { local $Gruntmaster::Data::contest; user_name $_->{owner} } }
+};
+
+thing {
+ params qw/ct contest Contests/;
+ sortby { $a->{start} <=> $b->{start} };
+ group { time < $_->{start} ? 'pending' : time > $_->{end} ? 'finished' : 'running' };
+ mangle { $_->{started} = time >= $_->{start}; $_->{owner_name} = do { local $Gruntmaster::Data::contest; user_name $_->{owner} } };
+};
+
+thing {
+ params qw/log job/, 'Job log';
+ contest;
+ mangle { $_->{results} &&= decode_json $_->{results}; $_->{user_name} = do { local $Gruntmaster::Data::contest; user_name $_->{user} } }
+};
+
+1
package Gruntmaster::Page::JS;
-use 5.014000;
-use strict;
-use warnings;
use Gruntmaster::Page::Base;
-our @ISA = qw/Gruntmaster::Page::Base/;
-our $VERSION = '0.001';
-
-use File::Slurp qw/read_file/;
use JavaScript::Minifier::XS qw/minify/;
sub generate{
[200, ['Content-Type' => 'application/javascript', 'Cache-Control' => 'public, max-age=604800', 'X-Forever' => 1], [minify $js] ]
}
-sub variants{ [[js => 1, 'application/javascript', undef, undef, undef, undef]] }
-
1
package Gruntmaster::Page::Log;
-use 5.014000;
-use strict;
-use warnings;
use Gruntmaster::Page::Base log => 'Job log';
-our @ISA = qw/Gruntmaster::Page::Base/;
-our $VERSION = '0.001';
use constant PAGE_SIZE => 10;
date => (job_date() ? strftime ('%c' => localtime job_date) : '?'),
extension => job_extension,
name => problem_name job_problem,
+ user_name => do { local $Gruntmaster::Data::contest; user_name job_user },
problem => job_problem,
result => job_result,
result_text => job_result_text,
+++ /dev/null
-package Gruntmaster::Page::Log::Entry;
-
-use 5.014000;
-use strict;
-use warnings;
-use Gruntmaster::Page::Base log_entry => 'Job <tmpl_var id>';
-our @ISA = qw/Gruntmaster::Page::Base/;
-our $VERSION = '0.001';
-
-sub _generate{
- my ($self, $htc, $lang, $env, $ct, $id) = @_;
- debug $env => "language is '$lang', contest is '$ct' and id is '$id'";
- local $Gruntmaster::Data::contest = $ct if $ct;
-
- my @tests = ();
-
- eval {
- @tests = map {
- $_->{time} = sprintf "%.4fs", $_->{time};
- $_
- } @{job_results $id};
- };
-
- $htc->param(id => $id);
- $htc->param(tests => \@tests);
- $htc->param(errors => job_errors $id)
-}
-
-1
package Gruntmaster::Page::Passwd;
-use 5.014000;
-use strict;
-use warnings;
use Gruntmaster::Page::Base;
-our @ISA = qw/Gruntmaster::Page::Base/;
-our $VERSION = '0.001';
-
use Apache2::Authen::Passphrase qw/pwcheck pwset/;
-use Plack::Request;
sub generate{
my ($self, $format, $env) = @_;
reply 'Password changed successfully';
}
-sub variants{ [[reply => 1, undef, undef, undef, undef, undef]] }
-
1
+++ /dev/null
-package Gruntmaster::Page::Pb;
-
-use 5.014000;
-use strict;
-use warnings;
-use Gruntmaster::Page::Base pb => 'Problems';
-our @ISA = qw/Gruntmaster::Page::Base/;
-our $VERSION = '0.001';
-
-sub _generate{
- my ($self, $htc, $lang, $env, $ct) = @_;
- debug $env => "language is '$lang' and contest is '$ct'";
- local $Gruntmaster::Data::contest = $ct if $ct;
-
- my @problems = sort { $b->{name} cmp $a->{name} } map +{
- id => $_,
- name => problem_name,
- level => problem_level}, problems;
-
- for my $d (qw/beginner easy medium advanced hard/) {
- $htc->param($d => [grep {$_->{level} and $_->{level} eq $d} @problems]);
- }
- $htc->param(levels => grep { $_->{level} } @problems);
- $htc->param(problems => \@problems);
-}
-
-1
package Gruntmaster::Page::Pb::Entry;
-use 5.014000;
-use strict;
-use warnings;
use Gruntmaster::Page::Base pb_entry => '<tmpl_var name>';
-our @ISA = qw/Gruntmaster::Page::Base/;
-our $VERSION = '0.001';
use constant FORMATS => [qw/C CPP JAVA PASCAL PERL PYTHON/];
package Gruntmaster::Page::Register;
-use 5.014000;
-use strict;
-use warnings;
use Gruntmaster::Page::Base;
-our @ISA = qw/Gruntmaster::Page::Base/;
-our $VERSION = '0.001';
-
use Apache2::Authen::Passphrase qw/pwcheck pwset USER_REGEX/;
-use Plack::Request;
sub generate{
my ($self, $format, $env) = @_;
reply 'Registered successfully';
}
-sub variants{ [[reply => 1, undef, undef, undef, undef, undef]] }
-
1
package Gruntmaster::Page::Src;
-use 5.014000;
-use strict;
-use warnings;
use Gruntmaster::Page::Base;
-our @ISA = qw/Gruntmaster::Page::Base/;
-our $VERSION = '0.001';
use constant CONTENT_TYPES => +{
c => 'text/x-csrc',
[200, ['Content-Type' => CONTENT_TYPES->{$ext}, 'Cache-Control' => 'max-age=604800', 'X-Forever' => 1], [job_inmeta($job)->{files}{prog}{content}] ]
}
-sub variants{ [[file => 1, undef, undef, undef, undef, undef]] }
-
1
package Gruntmaster::Page::St;
-use 5.014000;
-use strict;
-use warnings;
use Gruntmaster::Page::Base st => 'Standings';
-our @ISA = qw/Gruntmaster::Page::Base/;
-our $VERSION = '0.001';
use constant LEVEL_VALUES => {
beginner => 100,
int $score + 0.5
}
-sub usname ($) {
- local $Gruntmaster::Data::contest;
- my $user = shift;
- my $name = user_name $user;
- "$name ($user)"
-}
-
-
sub _generate{
my ($self, $htc, $lang, $env, $ct) = @_;
debug $env => "language is '$lang' and contest is '$ct'";
my @st = sort { $b->{score} <=> $a->{score} or $a->{user} cmp $b->{user}} map {
my $user = $_;
+{
- user => usname $user,
+ user => $user,
+ name => do {local $Gruntmaster::Data::contest; user_name $user},
score => sum (values $scores{$user}),
scores => [map { $scores{$user}{$_} // '-'} @problems],
problems => $Gruntmaster::Data::contest,
package Gruntmaster::Page::Submit;
-use 5.014000;
-use strict;
-use warnings;
use Gruntmaster::Page::Base;
-our @ISA = qw/Gruntmaster::Page::Base/;
-our $VERSION = '0.001';
-
-use Apache2::Authen::Passphrase qw/pwcheck pwset USER_REGEX/;
-use File::Slurp qw/read_file/;
-use Plack::Request;
use constant FORMAT_EXTENSION => {
C => 'c',
[303, [Location => $r->path =~ s,/pb/\w+/submit$,/log/,r], ['']]
}
-sub variants{ [[reply => 1, undef, undef, undef, undef, undef]] }
-
1
+++ /dev/null
-package Gruntmaster::Page::Us;
-
-use 5.014000;
-use strict;
-use warnings;
-use Gruntmaster::Page::Base us => 'Users';
-our @ISA = qw/Gruntmaster::Page::Base/;
-our $VERSION = '0.001';
-
-sub _generate{
- my ($self, $htc, $lang, $env) = @_;
- debug $env => "language is '$lang'";
-
- $htc->param(users => [ sort { lc $a->{name} cmp lc $b->{name} }
- map { {id => $_, name => user_name} }
- grep { user_name =~ /\w/ } users ]);
-}
-
-1
+++ /dev/null
-package Gruntmaster::Page::Us::Entry;
-
-use 5.014000;
-use strict;
-use warnings;
-use Gruntmaster::Page::Base us_entry => '<tmpl_var name>';
-our @ISA = qw/Gruntmaster::Page::Base/;
-our $VERSION = '0.001';
-
-sub _generate{
- my ($self, $htc, $lang, $env, $us) = @_;
- debug $env => "language is '$lang', user is '$us'";
- local $_ = $us;
-
- $htc->param(name => user_name);
- $htc->param(town => user_town);
- $htc->param(university => user_university);
- $htc->param(level => user_level);
-}
-
-1
use File::Slurp qw/read_file/;
use HTTP::Negotiate qw/choose/;
use Plack::Request;
+use Gruntmaster::Page::Log;
+use Gruntmaster::Page::Pb::Entry;
+use Gruntmaster::Page::Generic;
my %handlers;
my $word = qr,(\w+),a;
my $ct = qr,(?:\/ct/$word)?,a;
+ sub generic {
+ my ($thing, $ct, $fs) = @_;
+ $ct //= '', $fs //= '';
+ my $pkg = ucfirst $thing;
+ get qr,$ct/$thing/, => $pkg;
+ get qr,$ct/$thing/$word$fs, => "${pkg}::Entry";
+
+ get qr,$ct/$thing/read, => "${pkg}::Read";
+# post qr,$ct/$thing/$word/create, => "${pkg}::Entry::Create";
+ get qr,$ct/$thing/$word/read, => "${pkg}::Entry::Read";
+# post qr,$ct/$thing/$word/update, => "${pkg}::Entry::Update";
+# post qr,$ct/$thing/$word/delete, => "${pkg}::Entry::Delete";
+ }
+
get qr,/css/$word\.css, => 'CSS';
get qr,/js\.js, => 'JS';
- get qr,/ct/, => 'Ct';
- get qr,/ct/$word/, => 'Ct::Entry';
- get qr,/us/, => 'Us';
- get qr,/us/$word, => 'Us::Entry';
+ generic 'us';
+ generic ct => '', '/';
+ generic pb => $ct;
+ #generic log => $ct;
get qr,$ct/log/(\d+)?, => 'Log';
get qr,$ct/log/st, => 'St';
get qr,$ct/log/job/$word, => 'Log::Entry';
+ get qr,$ct/log/job/$word/read, => 'Log::Entry::Read';
get qr,$ct/log/src/$word\.$word, => 'Src';
- get qr,$ct/pb/, => 'Pb';
- get qr,$ct/pb/$word, => 'Pb::Entry';
post qr,$ct/pb/$word/submit, => 'Submit';
post qr,/action/register, => 'Register';
--- /dev/null
+#!/usr/bin/perl -w
+use strict;
+use warnings;
+
+use Test::More tests => 5;
+use Test::WWW::Mechanize::PSGI;
+
+my $mech = Test::WWW::Mechanize::PSGI->new(app => do 'app.psgi');
+$mech->get_ok('/');
+$mech->title_is('Gruntmaster 6000');
+
+$mech->get_ok('/pb/');
+$mech->title_is('Problems');
+$mech->content_contains('Spell');
<tr><th>Name<th>Start date<th>End date<th>Owner
<tbody>
<tmpl_loop running><tr><td><a href="<tmpl_var id>/"><tmpl_var name></a>
-<td><tmpl_var start>
-<td><tmpl_var end>
-<td><tmpl_var owner>
+<td><%perl __OUT__ POSIX::strftime '%c', localtime __CURRENT__->{start}; %>
+<td><%perl __OUT__ POSIX::strftime '%c', localtime __CURRENT__->{end}; %>
+<td><a href="/us/<tmpl_var owner>"><tmpl_var owner></a><tmpl_if owner_name> (<tmpl_var owner_name>)</tmpl_if>
</tmpl_loop>
</table>
</tmpl_if>
<tr><th>Name<th>Start date<th>End date<th>Owner
<tbody>
<tmpl_loop pending><tr><td><a href="<tmpl_var id>/"><tmpl_var name></a>
-<td><tmpl_var start>
-<td><tmpl_var end>
-<td><tmpl_var owner>
+<td><%perl __OUT__ POSIX::strftime '%c', localtime __CURRENT__->{start}; %>
+<td><%perl __OUT__ POSIX::strftime '%c', localtime __CURRENT__->{end}; %>
+<td><a href="/us/<tmpl_var owner>"><tmpl_var owner></a><tmpl_if owner_name> (<tmpl_var owner_name>)</tmpl_if>
</tmpl_loop>
</table>
</tmpl_if>
<tr><th>Name<th>Start date<th>End date<th>Owner
<tbody>
<tmpl_loop finished><tr><td><a href="<tmpl_var id>/"><tmpl_var name></a>
-<td><tmpl_var start>
-<td><tmpl_var end>
-<td><tmpl_var owner>
+<td><%perl __OUT__ POSIX::strftime '%c', localtime __CURRENT__->{start}; %>
+<td><%perl __OUT__ POSIX::strftime '%c', localtime __CURRENT__->{end}; %>
+<td><a href="/us/<tmpl_var owner>"><tmpl_var owner></a><tmpl_if owner_name> (<tmpl_var owner_name>)</tmpl_if>
</tmpl_loop>
</table>
</tmpl_if>
-Contest start time: <tmpl_var start><br>
-Contest end time: <tmpl_var end><p>
+Contest start time: <%perl __OUT__ POSIX::strftime '%c', localtime __CURRENT__->{start}; %><br>
+Contest end time: <%perl __OUT__ POSIX::strftime '%c', localtime __CURRENT__->{end}; %><p>
<tmpl_if started><a href="pb/">Problems</a><br>
<a href="log/">Job log</a><br>
<td><a href="../pb/<tmpl_var problem>"><tmpl_var name></a>
<td><tmpl_var date>
<td><a href="src/<tmpl_var id>.<tmpl_var extension>"<tmpl_if private> data-private</tmpl_if>><tmpl_var size></a>
-<td> <a href="/us/<tmpl_var user>" ><tmpl_var user></a><td class="r<tmpl_var result>"><tmpl_var result_text>
+<td><a href="/us/<tmpl_var user>"><tmpl_var user></a><tmpl_if user_name> (<tmpl_var user_name>)</tmpl_if><td class="r<tmpl_var result>"><tmpl_var result_text>
</tmpl_loop>
</table>
-Compiler output:
+<tmpl_if errors>
+<h2>Compiler output</h2>
<pre><tmpl_var errors></pre>
+</tmpl_if>
-Results:
+<tmpl_if results>
+<h2>Results</h2>
<table border class="table table-border table-striped">
<thead>
<tr><th>Test number<th>Result<th>Time
<tbody>
-<tmpl_loop tests><tr><td><tmpl_var id><td class="r<tmpl_var result>"><tmpl_var result_text><td><tmpl_var time>
+<tmpl_loop results><tr><td><tmpl_var id><td class="r<tmpl_var result>"><tmpl_var result_text><td><%perl __OUT__ sprintf "%.4fs", __CURRENT__->{time}; %>
</tmpl_loop>
</table>
+</tmpl_if>
\ No newline at end of file
-<tmpl_if levels>
+<tmpl_if beginner>
<h2>Beginner</h2>
-<div class="list-group">
-<tmpl_loop beginner><a class="list-group-item" href="<tmpl_var id>"><tmpl_var name></a>
-</tmpl_loop></div>
+<table border class="table table-bordered table-striped table-fixed">
+<thead><tr><th>Name<th>Author<th>Owner
+<tbody>
+<tmpl_loop beginner><tr><td><a href="<tmpl_var id>"><tmpl_var name></a><td><tmpl_var author><td><a href="/us/<tmpl_var owner>"><tmpl_var owner></a><tmpl_if owner_name> (<tmpl_var owner_name>)</tmpl_if>
+</tmpl_loop>
+</table>
+</tmpl_if>
+<tmpl_if easy>
<h2>Easy</h2>
-<div class="list-group">
-<tmpl_loop easy><a class="list-group-item" href="<tmpl_var id>"><tmpl_var name></a>
-</tmpl_loop></div>
+<table border class="table table-bordered table-striped table-fixed">
+<thead><tr><th>Name<th>Author<th>Owner
+<tbody>
+<tmpl_loop easy><tr><td><a href="<tmpl_var id>"><tmpl_var name></a><td><tmpl_var author><td><a href="/us/<tmpl_var owner>"><tmpl_var owner></a><tmpl_if owner_name> (<tmpl_var owner_name>)</tmpl_if>
+</tmpl_loop>
+</table>
+</tmpl_if>
+<tmpl_if medium>
<h2>Medium</h2>
-<div class="list-group">
-<tmpl_loop medium><a class="list-group-item" href="<tmpl_var id>"><tmpl_var name></a>
-</tmpl_loop></div>
+<table border class="table table-bordered table-striped table-fixed">
+<thead><tr><th>Name<th>Author<th>Owner
+<tbody>
+<tmpl_loop medium><tr><td><a href="<tmpl_var id>"><tmpl_var name></a><td><tmpl_var author><td><a href="/us/<tmpl_var owner>"><tmpl_var owner></a><tmpl_if owner_name> (<tmpl_var owner_name>)</tmpl_if>
+</tmpl_loop>
+</table>
+</tmpl_if>
+<tmpl_if hard>
<h2>Hard</h2>
-<div class="list-group">
-<tmpl_loop hard><a class="list-group-item" href="<tmpl_var id>"><tmpl_var name></a>
-</tmpl_loop></div>
-
-<tmpl_else>
-<div class="list-group">
-<tmpl_loop problems><a class="list-group-item" href="<tmpl_var id>"><tmpl_var name></a>
-</tmpl_loop></div>
+<table border class="table table-bordered table-striped table-fixed">
+<thead><tr><th>Name<th>Author<th>Owner
+<tbody>
+<tmpl_loop hard><tr><td><a href="<tmpl_var id>"><tmpl_var name></a><td><tmpl_var author><td><a href="/us/<tmpl_var owner>"><tmpl_var owner></a><tmpl_if owner_name> (<tmpl_var owner_name>)</tmpl_if>
+</tmpl_loop>
+</table>
</tmpl_if>
<table border class="table table-border table-striped">
<thead>
-<tmpl_if problems><tr><th>Rank<th>Username<tmpl_loop problems><th><tmpl_var _></tmpl_loop><th>Total
-<tmpl_else><tr><th>Rank<th>Username<th>Score
+<tmpl_if problems><tr><th>Rank<th>User<tmpl_loop problems><th><tmpl_var _></tmpl_loop><th>Total
+<tmpl_else><tr><th>Rank<th>User<th>Score
</tmpl_if>
<tbody>
-<tmpl_loop st><tr><td><tmpl_var rank><td><tmpl_var user>
+<tmpl_loop st><tr><td><tmpl_var rank><td><tmpl_var user><tmpl_if name> (<tmpl_var_name>)</tmpl_if>
<tmpl_if problems><tmpl_loop scores><td><tmpl_var _>
</tmpl_loop></tmpl_if><td><tmpl_var score>
</tmpl_loop>
-<div class="list-group"><tmpl_loop users><a class="list-group-item" href="<tmpl_var id>"><tmpl_var name></a>
+<div class="list-group"><tmpl_loop us><a class="list-group-item" href="<tmpl_var id>"><tmpl_var name></a>
</tmpl_loop></ol>